Core concepts

Payment flow

The paid round trip end to end, why money cannot be redirected, and every way it fails closed.

Roles

RoleHolds a key?Where it runs
Buyer agentyes — its ownthe agent's machine
Gatewaynomerchant infrastructure
Facilitatora gas-paying signerlocal dev chain in the demo; external in production
Merchantdestination address onlyconfiguration (payTo)

The gateway is in the middle of the protocol and outside the custody.

The round trip

text
 buyer                          gateway                        chain / backend
   │ 1. tools/call market_report   │                                   │
   ├──────────────────────────────►│ resolve, validate, price → paid   │
   │◄──────────────────────────────┤ PaymentRequiredEnvelope           │
   │ 2. isError + envelope         │                                   │
   │ 3. sign EIP-3009 authorisation│                                   │
   │    (to = merchant payTo)      │                                   │
   │ 4. tools/call + _payment      │                                   │
   ├──────────────────────────────►│ verify signature, recipient,      │
   │                               │   amount, window, network, asset  │
   │                               │ reserve replayKey                 │
   │                               │ settle ──────────────────────────►│ transferWithAuthorization
   │                               │◄──────────────────────────────────┤ tx receipt
   │                               │ call merchant backend ───────────►│ GET /api/report
   │                               │ save receipt (txHash)             │
   │◄──────────────────────────────┤                                   │
   │ 5. result + receipt           │                                   │

Over plain HTTP the steps are the same: the challenge arrives as a 402 body and in the base64 PAYMENT-REQUIRED header, the proof travels in the PAYMENT-SIGNATURE header instead of the _payment tool input, and the settlement result comes back in PAYMENT-RESPONSE.

Why the money cannot be redirected

The buyer signs an EIP-3009 TransferWithAuthorization whose to field is the merchant destination. The signature covers from, to, value, validAfter, validBefore and nonce, bound to the token contract and chain id through the EIP-712 domain. Whoever broadcasts it can only execute exactly that transfer, or nothing.

Fail-closed matrix

ConditionResultDelivered?
no proof supplied402 + envelopeno
malformed proof, bad signaturePAYMENT_INVALIDno
wrong amount, recipient, network or assetPAYMENT_INVALIDno
authorisation expired or not yet validPAYMENT_INVALIDno
insufficient balancePAYMENT_INVALIDno
authorisation already seenPAYMENT_REPLAYEDno
provider or RPC unreachablePAYMENT_PROVIDER_UNAVAILABLE (retryable)no
settlement transaction failsPAYMENT_SETTLEMENT_FAILEDno
backend fails after settlementBACKEND_ERROR / BACKEND_TIMEOUTno — payment recorded, delivery failed
Settlement is finalA backend failure after payment is a reconciliation problem, not a rollback. It is recorded as a payment_attempt with status settled and a backend.failed event sharing the same requestId.

Replay: two independent defences

  1. On-chain

    EIP-3009 marks authorizationState[from][nonce] used; a second transfer with the same nonce reverts. This prevents a double spend.

  2. In the gateway

    A replayed authorisation could still unlock a second delivery before the first settles. So the pipeline reserves a replayKey derived only from (chainId, asset, payer, nonce) under a UNIQUE constraint before calling settle. The same authorisation replayed against a different request still collides.

A resource that also requires an AP2 mandate gets a third, independent reservation in its own database. See AP2 mandates.

Amounts

Canonical amounts are decimal strings in display units — "0.01" — never floats. Conversion to base units (6 decimals for USDC) happens inside the payment provider, deterministically. An amount with more precision than the asset supports is a configuration error, not a rounding opportunity.

Local deterministic settlement

  • Anvil with --chain-id 84532, advertised as eip155:84532. That id is shared with Base Sepolia, so nothing infers “public network” from it.
  • MockUSDC: 6 decimals, EIP-3009, EIP-712 domain ("MockUSDC", "2").
  • Anvil's well-known accounts as facilitator, merchant and buyer — local development only, do not fund.
  • The end-to-end test asserts the buyer's balance falls and the merchant's rises by exactly the price, with a real transaction hash in the receipt.

Public networks use the same pipeline with a remote facilitator — see Networks & facilitators.