Skip to content

Wallet Authentication

Agents authenticate with an EVM wallet signature. First-time wallets are auto-provisioned with a prepaid tenant.

  1. Agent creates or loads an EVM wallet (any chain, any client).
  2. Agent signs a login message using EIP-191 personal_sign.
  3. Agent sends the signature as MCP wallet headers, or calls GraphQL walletLogin to receive a bearer JWT for direct API calls.
  4. Server verifies the signature and creates a prepaid tenant if this is a new wallet.

The signed message must match this exact format:

FrameWorks Login
Timestamp: 2025-01-15T12:00:00Z
Nonce: 12345
  • Timestamp: ISO 8601 UTC. Must be no more than 5 minutes old and no more than 1 minute in the future.
  • Nonce: Any random string, unique per request.
import { createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
const account = privateKeyToAccount("0x...");
const client = createWalletClient({ account, transport: http() });
const message = [
"FrameWorks Login",
`Timestamp: ${new Date().toISOString()}`,
`Nonce: ${crypto.randomUUID()}`,
].join("\n");
const signature = await client.signMessage({ message });
from eth_account import Account
from eth_account.messages import encode_defunct
import os
from datetime import datetime, timezone
import uuid
message = "\n".join([
"FrameWorks Login",
f"Timestamp: {datetime.now(timezone.utc).isoformat().replace('+00:00', 'Z')}",
f"Nonce: {uuid.uuid4()}"
])
signed = Account.sign_message(
encode_defunct(text=message),
private_key=os.environ["FRAMEWORKS_WALLET_PRIVKEY"]
)
signature = signed.signature.hex()

For MCP and HTTP requests, pass wallet credentials in headers:

HeaderValue
X-Wallet-Address0x-prefixed Ethereum address
X-Wallet-SignatureEIP-191 personal_sign signature
X-Wallet-MessageThe signed message (includes timestamp + nonce)

For direct GraphQL or browser integrations, use the GraphQL walletLogin mutation to exchange the same address/message/signature fields for a JWT, then use Authorization: Bearer <jwt> on subsequent requests. The REST POST /auth/wallet-login endpoint is cookie-oriented for first-party sessions; it sets HttpOnly cookies and only returns user metadata.

{
"mcpServers": {
"frameworks": {
"url": "https://bridge.frameworks.network/mcp",
"headers": {
"X-Wallet-Address": "0x...",
"X-Wallet-Signature": "0x...",
"X-Wallet-Message": "FrameWorks Login\nTimestamp: 2025-01-15T12:00:00Z\nNonce: 12345"
}
}
}
}
Terminal window
claude mcp add frameworks https://bridge.frameworks.network/mcp \
--header "X-Wallet-Address: 0x..." \
--header "X-Wallet-Signature: 0x..." \
--header "X-Wallet-Message: FrameWorks Login..."

If you prefer a traditional token flow:

{
"mcpServers": {
"frameworks": {
"url": "https://bridge.frameworks.network/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_TOKEN"
}
}
}
}

Get API tokens from Developer → API in the dashboard.

When a new wallet authenticates for the first time:

  1. A new tenant is created with billing_model = 'prepaid'.
  2. A new user is created (email is NULL).
  3. Prepaid balance is initialized at $0.
  4. A wallet identity record links the wallet address to the user and tenant.

The agent can immediately read account://status, which will show blockers like INSUFFICIENT_BALANCE or BILLING_DETAILS_MISSING.

Account TypeBilling ModelTrust Level
Wallet-onlyprepaid (mandatory)Low — must fund balance first
Email (verified)postpaid (invoiced)High — use now, pay later
Wallet + verified emailUser choiceHigh — can upgrade to postpaid
  • Store private keys locally. Never send them to any API.
  • Only send wallet headers to *.frameworks.network domains.
  • Wallet signatures are verified server-side using ecrecover (EIP-191).
  • The GraphQL API also accepts wallet auth via the walletLogin mutation with the same address/message/signature fields.