Wallet Authentication
Agents authenticate with an EVM wallet signature. First-time wallets are auto-provisioned with a prepaid tenant.
How It Works
Section titled “How It Works”- Agent creates or loads an EVM wallet (any chain, any client).
- Agent signs a login message using EIP-191
personal_sign. - Agent sends the signature as MCP wallet headers, or calls GraphQL
walletLoginto receive a bearer JWT for direct API calls. - Server verifies the signature and creates a prepaid tenant if this is a new wallet.
Message Format
Section titled “Message Format”The signed message must match this exact format:
FrameWorks LoginTimestamp: 2025-01-15T12:00:00ZNonce: 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.
Signing Examples
Section titled “Signing Examples”TypeScript (viem)
Section titled “TypeScript (viem)”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 });Python (eth-account)
Section titled “Python (eth-account)”from eth_account import Accountfrom eth_account.messages import encode_defunctimport osfrom datetime import datetime, timezoneimport 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()Headers
Section titled “Headers”For MCP and HTTP requests, pass wallet credentials in headers:
| Header | Value |
|---|---|
X-Wallet-Address | 0x-prefixed Ethereum address |
X-Wallet-Signature | EIP-191 personal_sign signature |
X-Wallet-Message | The 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.
Client Configuration
Section titled “Client Configuration”Claude Desktop
Section titled “Claude Desktop”{ "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" } } }}Claude Code CLI
Section titled “Claude Code CLI”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..."Bearer Token (Alternative)
Section titled “Bearer Token (Alternative)”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.
Auto-Provisioning
Section titled “Auto-Provisioning”When a new wallet authenticates for the first time:
- A new tenant is created with
billing_model = 'prepaid'. - A new user is created (email is
NULL). - Prepaid balance is initialized at $0.
- 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.
Trust Model
Section titled “Trust Model”| Account Type | Billing Model | Trust Level |
|---|---|---|
| Wallet-only | prepaid (mandatory) | Low — must fund balance first |
| Email (verified) | postpaid (invoiced) | High — use now, pay later |
| Wallet + verified email | User choice | High — can upgrade to postpaid |
Security
Section titled “Security”- Store private keys locally. Never send them to any API.
- Only send wallet headers to
*.frameworks.networkdomains. - Wallet signatures are verified server-side using
ecrecover(EIP-191). - The GraphQL API also accepts wallet auth via the
walletLoginmutation with the same address/message/signature fields.