Core concepts
Payment flow
The paid round trip end to end, why money cannot be redirected, and every way it fails closed.
Roles
| Role | Key material | Where it runs |
|---|---|---|
| Buyer agent | its own payment key | the agent's machine |
| Gateway | no buyer or merchant key required | merchant infrastructure |
| Facilitator | a gas-paying signer key; a fee payer on Solana, a relayer on NEAR | the gateway process in local mode (eip155:31337 only); a remote service otherwise |
| Merchant | an address in configuration; no key required | payTo for x402, recipient for MPP |
The gateway coordinates the protocol without taking custody. Local mode keeps the facilitator signer in the gateway process, which is why it serves only the local chain, eip155:31337, and never a public network.
The x402 round trip
The diagram shows an EIP-3009 payment under the x402 default authorization flow: the backend answers before the payment settles, so a backend failure settles nothing, and a refused settlement withholds the backend's response. Under the upfront flow, set per rail or per resource, settle moves above the backend call. See Configuration.
buyer gateway chain / backend
│ 1. tools/call market_report │ │
├──────────────────────────────►│ resolve, validate, price → paid │
│◄──────────────────────────────┤ PaymentRequiredEnvelope │
│ 2. isError + envelope │ │
│ 3. sign EIP-3009 authorization│ │
│ (to = merchant payTo) │ │
│ 4. tools/call + _payment │ │
├──────────────────────────────►│ verify signature, recipient, │
│ │ amount, window, network, asset │
│ │ reserve replayKey │
│ │ call merchant backend ───────────►│ GET /api/report
│ │◄──────────────────────────────────┤ 200 + body
│ │ settle ──────────────────────────►│ transferWithAuthorization
│ │◄──────────────────────────────────┤ tx receipt
│ │ 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. MCP and A2A carry the same proof in the reserved _payment input field. MCP also accepts the selected rail's own _meta carrier, and A2A accepts the x402 extension's message metadata; see MCP and A2A.
Other x402 methods follow the same steps with their own signed payload: a Permit2 transfer, a Solana token transfer that the facilitator's fee payer co-signs, or a NEAR delegate action that the facilitator's relayer submits. When a resource offers several networks or tokens, the challenge lists each offer in accepts, and the proof's accepted echo selects one before verification. Zero or several matches are refused as invalid_payment_requirements.
With experimental ERC-8409 quotes enabled, the challenge also carries a signed quote for each EVM offer. Quotes are informational: verification and settlement never read the echoed copy.
MPP
An experimental MPP payment runs the same verification and replay checks, with these differences:
- The challenge is HMAC-bound to its terms and resource, and the buyer's EIP-3009 nonce must equal the challenge hash, so an authorization answers exactly one challenge. Since 2.4.1, the challenge also includes the canonical Permit2 address in
methodDetails.permit2Address, following thedraft-evm-charge-00format. - Verification runs local checks, then the x402 settlement provider's read-only facilitator check. Both happen before the pipeline reserves the replay key, and neither broadcasts the payment.
- Settlement uses an x402 facilitator. MPP derives the same replay key as x402, so one receipt store cannot reserve the same authorization through both rails.
- MPP takes the same flow values as x402 but defaults to
upfront, as itschargeintent requires. Underauthorization, the backend must answer before the challenge expires.
Over HTTP, MPP uses WWW-Authenticate, Authorization: Payment … and Payment-Receipt. See Invoke a resource for the header table.
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.
The other methods bind the same terms. A Permit2 signature covers the token, amount, nonce and deadline, names the x402 exact proxy as the only spender and the merchant as witness recipient. On Solana the buyer signs the transaction message, and the facilitator adds its fee payer signature without changing it. On NEAR the relayer wraps the buyer's signed delegate in its own transaction without changing the token, recipient or amount.
Fail-closed matrix
| Condition | Result | Delivered? |
|---|---|---|
| no proof supplied | 402 + envelope | no |
| malformed proof, bad signature | PAYMENT_INVALID | no |
| wrong amount, recipient, network or asset | PAYMENT_INVALID | no |
| authorization expired or not yet valid | PAYMENT_INVALID | no |
| insufficient balance | PAYMENT_INVALID | no |
| authorization spent on chain, no unfinished attempt | PAYMENT_INVALID | no |
| authorization spent on chain while its attempt is unfinished | PAYMENT_REPLAYED (409, no challenge) | no |
| a copy reaches the gateway's reservation before the original settles | PAYMENT_REPLAYED | no |
| provider or RPC unavailable during verification | PAYMENT_PROVIDER_UNAVAILABLE (retryable) | no |
| settlement transaction fails | PAYMENT_SETTLEMENT_FAILED | no |
backend fails before settlement (authorization flow) | BACKEND_ERROR / BACKEND_TIMEOUT | no; nothing settled; x402 can retry |
backend fails after settlement (upfront flow) | BACKEND_ERROR | no — payment recorded, delivery failed |
payment_attempt with status settled and a backend.failed event sharing the same requestId and transactionId. A settlement can end with no verdict, such as after a timeout following a possible broadcast. The gateway then withholds delivery and tries to record settlement-uncertain. A failed store write can leave the attempt reserved. See Reconciliation. Under the authorization flow that means the backend has already run, so its work can go unpaid.Replay: two independent defenses
- On-chain
EIP-3009 marks
authorizationState[from][nonce]used; a second transfer with the same nonce reverts. Permit2 marks the owner's nonce used in its own bitmap, across every token. A Solana cluster refuses a transaction it already processed, or one whose blockhash expired. NEAR refuses a delegate whose nonce is at or below its access key's. This prevents a double spend. - In the gateway
Concurrent submissions could still unlock a second delivery before the first settles. So the pipeline reserves a
replayKeyunder aUNIQUEconstraint before callingsettle. A duplicate getsPAYMENT_REPLAYED:409while the first attempt is unfinished or its status cannot be read, and402with a fresh challenge once it has settled or, under MPP, been rejected. The key comes from the authorization, not the request, so a replay against a different request still collides.A spent proof can fail verification before replay reservation. Since 2.6.0, the pipeline looks up the attempt by replay key when a rejected proof carries one. An unfinished attempt returns
409without a challenge; a finished or absent attempt returns402 PAYMENT_INVALIDwith a fresh challenge. This also covers expired MPP challenges. Lookup failures returnSTORAGE_ERRORwithout a challenge.Method replayKey EIP-3009 (chainId, asset, payer, nonce)Permit2 (chainId, Permit2, owner, nonce)Solana the network and SHA-256 of the transaction message, signatures excluded NEAR (network, sender, public key, nonce)A rejected x402 attempt settled nothing: the backend failed, settlement was refused, or the facilitator never submitted it. The same proof can reserve it again and settles at most once; the replaced row stays in
payment_attempt_history. MPP keeps the402, because a credential is single-use per challenge.
A resource that also requires an AP2 mandate gets a third, independent reservation, in its own table and on its own key. It is claimed before the payment replay key and released only by a failure that provably moved no money. See AP2 mandates.
Transactions
Once the pipeline resolves the resource, it allocates a transactionId for the call and puts it in events, returned outcomes and receipts. A paid call that reaches replay reservation also gets a payment attempt and a journal row with that ID, inserted in one SQLite transaction; if the journal insert fails, the call ends with STORAGE_ERROR before settlement. Free calls, 402 challenges and refused replays carry the ID without a row.
The row links the payment attempt, any AP2 hold, the backend call and the receipt through recorded steps. It stores a digest when the input can be canonicalized. Journal entries must contain references and digests rather than input bodies, payment proofs, signatures, mandates or secrets. Their state, from payment-pending to delivered, is derived from the saved facts on read; see the states.
After reservation, a failed store write never undoes delivery or replaces the original commerce error; it is logged at error. Delivered outcomes and terminal events list the persistenceFailures known at that point. The final completion write happens afterward, so its failure cannot appear in those snapshots. A failed receipt write adds receiptCommitted: false to the MCP and A2A delivery summary. Operators can list open purchases at GET /api/transactions?unresolved=true. Since 2.6.0, the Transaction type, the journal store methods and that response shape are supported.
Reconciliation
agent-commerce reconcile asks each payment provider for chain evidence on uncertain attempts. The built-in providers make read-only RPC calls and never contact a facilitator; the command records resolutions and hold updates locally and never broadcasts, refunds or retries delivery.
- settled requires a final transfer matching the authorization.
- not settled requires final chain state showing the authorization unused after its last valid moment. Missing logs, receipts or history entries alone do not prove nonpayment, and Solana never returns this result.
- Inconclusive evidence stays unknown, with a reason, for a later run or for an operator to investigate and record with
agent-commerce resolve.
A resolution records its method, actor, reason, time and any evidence. The write requires the attempt's expected status and no existing resolution, so competing resolvers cannot overwrite each other, and a late pipeline write to a resolved attempt fails with STORAGE_ERROR. Later runs skip resolved payments and, with AP2 enabled, can retry pending mandate consume or release updates and release orphan holds: reserved mandates left without a recorded payment attempt.
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.
An x402 payment in the network's default USDC records currency: USDC; a resource priced in USD keeps that price as metadata.quote on settlement. Other configured tokens use the resource's currency label.
Local deterministic settlement
- Anvil on its default chain id 31337, advertised as
eip155:31337. The local health check also probesanvil_nodeInfo, because an RPC can report any chain id. 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.
- A second end-to-end test settles on Anvil chains 31338 and 31339 through a loopback SDK facilitator, exercising the remote path used for public networks. Offline suites also settle Permit2 on Anvil, Solana on
solana-test-validatorand NEAR on a local sandbox.
Both x402 and MPP have settled on Base Sepolia and Base mainnet through the same pipeline and a remote facilitator. x402 has also settled on Solana devnet and mainnet, and on NEAR testnet and mainnet through the test suite's own facilitator. See Networks & facilitators.