One paid call, step by step

A $10 deposit, a hundred receipts at 5 cents each, one settlement that paid the seller, its stakers and the network and refunded the rest: behind that money are a few exact messages, the web's own pattern for paying per request and then AIOUM's bytes on the wire. What your agent signs, what the node checks, and why neither side can cheat the other.

The idea in 60 seconds

HTTP has reserved a status code for payments since the 1990s: 402 Payment Required. It was never standardized, because there was no internet-native way to pay. x402, the scheme popularized by Coinbase (Lightning's L402 is a cousin), fills it in with a simple loop:

1. Client requests the resource, pays nothing.
2. Server refuses with 402 + a machine-readable "here's how to pay" challenge.
3. Client constructs the payment and RETRIES THE SAME REQUEST with it attached.
4. Server verifies, serves the resource.

That is the whole protocol, and why your agent never needs an account: the 402 challenge is the onboarding. No signup, no key exchange. A client that has never seen this server reads the challenge, pays, and is served, machine to machine.

sequenceDiagram
    participant C as Client (agent)
    participant S as Server (service)
    C->>S: POST /resource
    S-->>C: 402 Payment Required + accepts[] challenge
    Note over C: construct payment<br/>(sign / transfer per challenge)
    C->>S: POST /resource + payment attached
    S->>S: verify payment
    S-->>C: 200 OK (result)

In generic x402, the payment at step 3 is typically an inline transfer authorization, such as a signed USDC transferWithAuthorization, often verified and settled by a "facilitator" service. That implies one on-chain settlement per purchase: fine for buying an article, hopeless for an agent making 500 tool calls a minute.

AIOUM's twist: the payment is a voucher against escrow

AIOUM keeps the x402 handshake and changes what "payment" means: not an inline transfer but a signature over a running total, backed by a prepaid escrow session, the receipt, which the contracts and the code call a voucher. Chain transactions happen exactly twice per relationship, once to open and once to settle, however many calls come between. That turns a hundred calls into a hundred free signatures and two transactions.

Four layers, bottom up. Settlement: a PaymentManager escrow session, opened once and settled once. The payment primitive: an EIP-712 PaymentVoucher(sessionId, cumulativeAmount) that says "I authorize a cumulative total of X against my session"; monotonic, so replay-safe. The challenge schema: the x402 accepts[] object naming the scheme, the asset, the amount and the exact voucher typed-data to sign. The transport: plain HTTP 402 plus headers. The server keeps no session cookie; every request is self-contained, carrying its voucher and relying on nothing but on-chain state.

The same loop, with AIOUM's actual bytes

Setup happens once: the client has opened session 42 on-chain with a 10 USDC deposit for a web-search service priced at 0.05 USDC/call (opening a session: payments guide). The morning brief's numbers, seen from the wire.

Step 1 — call the tool, no payment yet. The agent calls the node's MCP endpoint like any tool, with nothing attached:

POST /mcp HTTP/1.1
Content-Type: application/json

{"jsonrpc":"2.0","id":1,"method":"tools/call",
 "params":{"name":"web_search","arguments":{"query":"solar eclipse 2027"}}}

Step 2 — the 402 challenge. The node refuses, and the refusal is the onboarding. The shapes are from offchain/src/node/tools.ts:

HTTP/1.1 402 Payment Required
WWW-Authenticate: AIOUMVoucher network="eip155:31337", asset="0xUSDC...", amount="50000", payTo="0xPaymentManager..."
Accept-Payment: aioum-voucher

{"x402Version":1,
 "accepts":[{
   "scheme":"aioum-voucher",
   "network":"eip155:31337",
   "asset":"0xUSDC...",
   "maxAmountRequired":"50000",          // spent-so-far + this call's price, in USDC units
   "payTo":"0xPaymentManager...",
   "resource":"aioum:service:1",
   "description":"Per-call payment via an EIP-712 PaymentVoucher over an escrowed AIOUM session.",
   "extra":{
     "sessionId":"42",
     "voucher":{
       "primaryType":"PaymentVoucher",
       "domain":{"name":"AIOUMPayments","version":"1","chainId":31337,"verifyingContract":"0xPaymentManager..."},
       "types":{"PaymentVoucher":[{"name":"sessionId","type":"uint256"},
                                   {"name":"cumulativeAmount","type":"uint256"}]}},
     "howToPay":"Open a session ..., sign a PaymentVoucher for cumulativeAmount >= maxAmountRequired, then resend with params.payment = { sessionId, cumulativeAmount, signature }."}}]}

Everything a stranger needs is in the challenge: what to sign (the typed-data schema and domain), for how much (maxAmountRequired is the cumulative total that makes this call paid), and where the escrow lives.

Step 3 — sign, no transaction. The step that differs from generic x402: the client signs typed data, no chain interaction, no time, no gas:

signature = sign_EIP712(domain, PaymentVoucher{ sessionId: 42, cumulativeAmount: 50000 })

An EOA client signs directly (signVoucher); a smart-account client's session key signs the enriched form (signEnrichedVoucher). Both live in offchain/src/sdk/voucher.ts.

Step 4 — retry with payment attached. The same request again, with the voucher inside:

{"jsonrpc":"2.0","id":2,"method":"tools/call",
 "params":{"name":"web_search","arguments":{"query":"solar eclipse 2027"},
           "payment":{"sessionId":"42","cumulativeAmount":"50000","signature":"0x..."}}}

Step 5 — the node verifies. verifyPayment makes three checks and one RPC read: session 42 is Open; the signature recovers to the session's client (EOA fast path first, then an ERC-1271 staticcall for smart accounts); and spentSoFar + price ≤ cumulativeAmount ≤ deposit, so the voucher pays for this call and cannot exceed the escrow. Then it serves the result with 200 OK.

Steps 3–5 repeat per call, and only the number goes up. A hundred calls later the client signs cumulativeAmount = 5_000_000 (5 USDC). Each voucher supersedes the last, so a replayed one is a number the node has already passed. The flow is stateless for the server: a fresh node process verifies call #101 knowing nothing but chain state and the voucher in hand.

Step 6 — settle (the second and last transaction). The node submits the latest voucher once: PaymentManager.settle(42, 5_000_000, signature). The contract re-verifies the signature on-chain (SignatureChecker) and makes the split: 0.25 USDC protocol fee (5%), 0.50 USDC to the operator's delegators (10%), 4.25 USDC to the operator, and the unspent 5 USDC refunds to the client. Money is conserved exactly (payments.md). With the session settled, the agent may leave its one rating of the operator on-chain, scored from how the calls went.

Why this design, in three questions

Why not a transaction per call? Because 500 calls at ~$0.01+ of L2 gas each would dwarf the $0.05 price of the service. Signatures are free, so the chain is consulted only where money moves in aggregate: open and settle.

Why can't the node cheat? It can only settle a total the client signed: vouchers are unforgeable and the escrow caps the maximum. Its worst move is to serve badly and settle honestly-signed totals, which is exactly what disputes and slashing punish (arbitration.md) and what the agent's rating records for everyone after.

Why can't the client cheat? It can refuse to sign the next voucher, but then the node refuses the next call, which is just leaving. It cannot reuse a voucher (the total is monotonic), cannot spend beyond the deposit (the escrow bounds it), and its signed total settles without its cooperation.

Try it live

Watch the whole loop on the local development network, with balances printed before and after:

cd offchain && npm install
anvil                 # terminal 1
npm run deploy        # terminal 2
npm run node          #   :8546
npm run facilitator   #   :8547 — relays the gasless session open
npm run demo          # terminal 3 — full open → 402 → pay → settle loop with balances printed

Or point Claude at it via the MCP package: mcp-server.md.

Next: Questions people ask — the questions that come up once you have seen the messages, before you commit.