# Spendkit AI Agent Reference Spendkit is a non-custodial AI Agent Spending Policy Firewall. Spendkit Server handles MCP, OAuth, Policy evaluation, authorization, signatures, payment recording, and reconciliation. The Agent Payment Wallet holds USDG, submits the Router transaction, and pays gas itself. Coding Agents such as Codex, Claude Code, or Gemini CLI may help developers build an integration. The Runtime Agent is the running client that uses OAuth + MCP and submits authorized transactions from its bound Agent Payment Wallet. ## Wallet roles ```text Spendkit Admin + Authorization Signer -> Router administration, Policy management, pause control, EIP-712 signing Agent Payment Wallet -> holds USDG, transaction.from, gas payer, token source Demo Recipient -> receives USDG ``` Hackathon Demo Admin and Authorization Signer address: `0x3EA76F9b8489AD51D5e00dcb8bE5706049fC89F1`. Agent Payment Wallet: `0x8F2ab541a145671542c2ff9c7be8a46813ff83dc`. Demo Recipient: `0x33017f263aDb598a5E4Cc0c66F986b9893746F5C`. The Admin/Signer does not submit or pay for Agent payments. The Payment Wallet does not administer the Router or sign Spendkit authorizations. The personal wallet has no Spendkit role. ## Payment flow ```text User intent -> Local AI Agent -> Spendkit MCP -> Preview ALLOW / DENY -> EIP-712 authorization for the bound Payment Wallet -> Payment Wallet submits Router transaction and pays gas -> SpendkitPolicyRouter -> transferFrom(Payment Wallet, Demo Recipient, amount) -> record and reconcile payment ``` The signed-authorization-v3 source binds `authorizationId`, `policyId`, `agentId`, `paymentWallet`, `token`, `recipient`, `amount`, and `deadline` using EIP-712 domain version 2. The Router requires `msg.sender == authorization.paymentWallet`, validates the current Policy, limits, allowlist, expiry and replay state, then transfers tokens from that same wallet. Spendkit Server never broadcasts payment transactions, pays gas, or receives the Payment Wallet private key. The bound Agent Payment Wallet submits the Router transaction and pays gas. ## Public discovery - Open House Judge Brief: https://spendkit-alpha.vercel.app/open-house/ - Docs: https://spendkit-alpha.vercel.app/docs/ - First Agent setup: https://spendkit-alpha.vercel.app/docs/quickstart/ - Plain-language concepts: https://spendkit-alpha.vercel.app/docs/concepts/ - Dashboard guide: https://spendkit-alpha.vercel.app/docs/dashboard/ - Runtime guide: https://spendkit-alpha.vercel.app/docs/runtime/ - Copyable JavaScript, Python, Go, and C++ examples: https://spendkit-alpha.vercel.app/docs/examples/ - HTTP and MCP request examples: https://spendkit-alpha.vercel.app/docs/api-quickstart/ - MCP input/output reference: https://spendkit-alpha.vercel.app/docs/mcp-tools/ - Payment flow: https://spendkit-alpha.vercel.app/docs/payment-flow/ - Security: https://spendkit-alpha.vercel.app/docs/security/ - Machine JSON: https://spendkit-alpha.vercel.app/integration.json ## Runtime endpoints and authentication ```text Base: https://spendkit-alpha.vercel.app MCP: https://spendkit-alpha.vercel.app/mcp OAuth: https://spendkit-alpha.vercel.app/oauth/token ``` Runtime Access uses OAuth 2.0 Client Credentials and short-lived Bearer tokens. The server resolves the bound Payment Wallet and Policy through the authenticated Agent Connection; the model must not supply either address or a Policy ID. Configuration sources: `SPENDKIT_BASE_URL` is the public service URL above (the examples default to it). The developer signs in at https://spendkit-alpha.vercel.app/app/agents, creates an Agent, binds and verifies a Payment Wallet, sets an onchain Spending Policy, then opens that Agent's **Connect Agent** settings to copy Client ID and Client Secret for the trusted Runtime. A payment Runtime needs Full Runtime Access. The wallet private key, if a local signer requires one, comes from the developer's own wallet or protected key store; Spendkit Dashboard neither creates nor reveals it. Never paste a private key into the Dashboard, source code, or an AI prompt. The proposed recipient and amount come from the application's payment request; the idempotency key comes from its stable order/payment ID. Request `POST /oauth/token` with `Content-Type: application/x-www-form-urlencoded`, `grant_type=client_credentials`, `client_id`, and `client_secret` (or HTTP Basic client credentials). Omit `scope` for the connection's granted scopes, or request only a subset. The JSON response contains `access_token`, `token_type: Bearer`, `expires_in: 3600`, and a space-delimited `scope` string. Refresh the token before expiry. A read-only connection lacks `payment:authorize` and `payment:record`; a payment Runtime needs Full Runtime Access. The preferred MCP transport is JSON-RPC 2.0 over `POST /mcp` with a Bearer token, `Content-Type: application/json`, `Accept: application/json`, `MCP-Protocol-Version: 2026-07-28`, and `Mcp-Method` equal to the JSON-RPC method. For modern calls, `params._meta` must contain `io.modelcontextprotocol/protocolVersion: 2026-07-28`, `io.modelcontextprotocol/clientInfo` (name and version), and `io.modelcontextprotocol/clientCapabilities: {}`. Call `server/discover`, then `tools/list`. For `tools/call`, also send `Mcp-Name` matching `params.name`; put tool inputs in `params.arguments`. Legacy compatible clients can use `2025-11-25` `initialize`. Successful tool calls return a JSON-RPC `result` with `structuredContent` and equivalent JSON text in `content`. Read `result.isError` and `result.structuredContent.allowed` or `accepted`: a DENY can use HTTP 200. Invalid JSON-RPC requests use a top-level `error`. Every structured tool result includes `spendkit.traceId`; some include `spendkit.nextAction` or `spendkit.recovery`. Fetch current input schemas from `tools/list` rather than assuming these descriptions are exhaustive. ## MCP tools ```text spendkit_get_connection spendkit_get_policy spendkit_preview_payment spendkit_authorize_payment spendkit_record_payment spendkit_get_payment_status spendkit_list_payments ``` `spendkit_authorize_payment` accepts amount, recipient, optional token symbol such as `USDG`, and an idempotency key. It returns a one-time authorization and Router transaction with `from` set to the bound Payment Wallet. Keep `spendkit_record_payment({intentId,txHash})` immediately after wallet submission to link the transaction for prompt tracking and Payment Activity updates, then keep `spendkit_get_payment_status({intentId})` to reconcile the final result. If recording fails, retry with the same intent ID and hash or query status; never resubmit the wallet transaction just to retry recording. Status and chain reconciliation remain available if the Agent stops between submission and recording, so the dashboard may lag temporarily; the onchain Router still enforces spending limits. Tool contract summary (inputs are `params.arguments`; outputs are `result.structuredContent`): | Tool | Required scope | Inputs | Principal output fields | | --- | --- | --- | --- | | `spendkit_get_connection` | Authenticated connection | `{}` | `connection`, `agent`, `network`, `authentication`, `paymentModel` | | `spendkit_get_policy` | `policy:read` | `{}` | `ready`, `reason` if unready; when ready, `policyId`, `token`, `decimals`, `perTransactionLimit`, `dailyLimit`, `spentToday`, `remainingDaily`, state flags, `_raw` | | `spendkit_preview_payment` | `payment:preview` | `{amount: decimal string, recipient: EVM address, token?: symbol}` | `allowed`, `reason`; on ALLOW, `amountAtomic`, `token`, `policy`, `wallet` | | `spendkit_authorize_payment` | `payment:authorize` | `{amount, recipient, token?, idempotencyKey: string}` | `allowed`, `intentId`, `duplicate`, `status`, `authorization`, `signature`, `transaction` | | `spendkit_record_payment` | `payment:record` | `{intentId, txHash}` | `accepted`, `intentId`, `status`, `txHash`; on rejection, `error` | | `spendkit_get_payment_status` | `payment:read` | `{intentId}` | `intentId`, `status`, `txHash`, `failureCode`, `amountAtomic`, `token`, timestamps; missing intent: `status: not_found` | | `spendkit_list_payments` | `payment:read` | `{limit?: integer 1..100}` | `rows[]` with status, atomic amount, recipient, tx hash, failure and reconciliation state | `amount` must be a positive decimal string, not a JSON number; `recipient` must be a nonzero 20-byte EVM address; `token` is a symbol and defaults to the Agent token. `idempotencyKey` identifies one immutable logical payment. The authorization's `transaction` contains `chainId`, `from`, `to`, `data`, and `value`; the wallet Runtime must check these against the bound wallet, intended payment, current chain, and Router, then submit exactly once. Never interpret authorization or `submitted` as final success; inspect `spendkit_get_payment_status` for `confirmed` or a failure state. ## Local Agent payment flow The Agent requests a live Policy preview and authorization, displays the authorized amount, recipient, bound Payment Wallet and chain, then asks the user to confirm before submission. After confirmation, the local Payment Wallet submits the exact Router transaction and pays gas; the Agent records and reconciles the result. Payment limits come from the live Policy and Router, not local environment variables. The local Payment Wallet key must derive `PAYMENT_WALLET_ADDRESS`; that address must match the Dashboard binding and the signed authorization. The key stays in local Runtime configuration and is never sent to Spendkit Server. Use one stable unique idempotency key for one logical immutable payment. Never blindly retry an uncertain transaction broadcast; inspect payment status and reconciliation first. ## Security boundary MCP cannot create or modify Policy, raise limits, increase Router allowance, rebind a Payment Wallet, or disable protection. The Router independently enforces signature, Agent, Policy, Payment Wallet caller, token, recipient, amount, limits, expiry, and replay protection. ## Interoperability and bridge Preferred MCP protocol is `2026-07-28` using stateless discovery; compatible clients may use the legacy `2025-11-25` initialize flow. `npm run mcp:bridge` provides local stdio-to-remote-MCP OAuth token handling. The bridge never submits EVM transactions. ## Open House evidence - Network: Arbitrum Sepolia, chain ID 421614. - Asset: Paxos Test USDG, `0xFFC95faa3d63Cde504a05B567C600B78C0b41892`. - Current Router: `0xe388644B4115fbE029FA4acC3b20a47600c4bA16`. - Historical payment evidence used Router `0x7F3c2c570122501B276680148B0C88AAe0550153`. - Latest verified payment: 0.01 USDG, receipt SUCCESS, block 312101737. - Payment Wallet delta: -0.01 USDG; recipient delta: +0.01 USDG; Router balance delta: 0 USDG. - Reconciliation: CONFIRMED, authorizationUsed=true. - Over-limit preview: 0.11 USDG -> DENY. - Server, Local Agent, and contract test suites are tracked in regression reports. - Full official MCP conformance is not claimed; the public surface is a focused tools-only API. ## Deployment status The registered Arbitrum Sepolia Router is signed-authorization-v3 at `0xe388644B4115fbE029FA4acC3b20a47600c4bA16` and uses EIP-712 domain version 2. The server checks the deployed authorization typehash and stays fail-closed on mismatch. The published confirmed payment used the earlier Router; no new confirmed payment is claimed for the current Payment Wallet sender path.