Docs / x402

x402

Pay for an HTTP resource with native USDC. No account, no API key, no gas token. Not the faucet and not write RPC.

Pay from a wallet

  1. Unpaid GET https://poa.net/77/x402/demo returns HTTP 402. The JSON body is the quote (PAYMENT-REQUIRED is that same JSON in Base64 — use the body).
  2. Hold native USDC (bridge in, or the invite faucet for an assigned wallet).
  3. An EOA signs TransferWithAuthorization locally against NativeTransferAuth at 0x2d00000000000000000000000000000000000006 (EIP-712 name USDC, version 2 — not Ethereum mainnet USDC 0xa0b86991…, not Permit2, not an ERC-20).
  4. Retry the same URL with a PAYMENT-SIGNATURE header. The demo returns 200 only after the settler tx is included with status 0x1.
curl -sS -D - https://poa.net/77/x402/demo -o /tmp/x402-demo.json

Network is CAIP-2 eip155:77, scheme exact. Flow flags: extra.assetTransferMethod=eip3009, paymentFlow=authorization, kind=native. x402 amount and extra.decimals are 6 (same as explorer balances: 10000 is 0.01 USDC). JSON-RPC eth_getBalance uses 18 decimals on the wire — 1 USDC is 10^18 there, 10^6 here. See Network.

Replay. A missing receipt is settlement_pending (402), not success. Worker 502/504 after settle does not mean the payment failed: the settler may already have broadcast. Public JSON-RPC does not allow eth_call, so do not probe authorizationState that way. Retry the same PAYMENT-SIGNATURE (or POST /facilitator/settle with the same payload). A replay of (from, nonce) cannot debit twice. Do not sign a fresh authorization until that retry returns 200, settlement_pending, or a definitive payment-failed reason.

PAYMENT-SIGNATURE envelope

PAYMENT-SIGNATURE is Base64 of this envelope — not the EIP-712 digest, and not payload.authorization alone. Copy resource from the 402 body as-is: resource.url is whatever the server put there and may not be the public poa.net URL you requested. Copy the selected accepts[i] object as accepted. authorization.to must equal accepted.payTo, authorization.value must equal accepted.amount. validAfter / validBefore are unix seconds; keep validBefore within accepted.maxTimeoutSeconds of now. nonce is 32 random bytes. Missing resource.url or accepted is invalid_payload in the 402 JSON error field (and in PAYMENT-RESPONSE after a failed settle).

{
  "x402Version": 2,
  "resource": { "url": "<from the 402 body>", "mimeType": "application/json", "description": "…" },
  "accepted": {
    "scheme": "exact",
    "network": "eip155:77",
    "asset": "0x2d00000000000000000000000000000000000006",
    "amount": "10000",
    "payTo": "0x…",
    "maxTimeoutSeconds": 180,
    "extra": {
      "assetTransferMethod": "eip3009",
      "paymentFlow": "authorization",
      "kind": "native",
      "name": "USDC",
      "version": "2",
      "decimals": 6,
      "symbol": "USDC"
    }
  },
  "payload": {
    "signature": "0x…",
    "authorization": {
      "from": "0x…",
      "to": "0x…",
      "value": "10000",
      "validAfter": "0",
      "validBefore": "…",
      "nonce": "0x…"
    }
  }
}

Facilitator

GET  https://poa.net/77/x402/facilitator/supported
POST https://poa.net/77/x402/facilitator/verify
POST https://poa.net/77/x402/facilitator/settle

POST /77/x402/facilitator/verify and settle take {"x402Version":2,"paymentPayload":<envelope>,"paymentRequirements":<accepted>}. The facilitator cannot mint. Anyone may submit transferWithAuthorization to 0x2d…06: the signature binds to (payTo) and a one-use nonce, so a front-run cannot redirect funds. The HTTP resource still needs the PAYMENT-SIGNATURE header. validBefore farther than maxTimeoutSeconds from now is rejected at verify/settle. Default timeout is 180 seconds.

Assigned wallets

Assigned wallets sign through POST /77/agent/v1/x402/authorize (invite bearer; the key stays in 2d-hsm). Then retry the resource with that header.

The Agent Gateway MCP does not wrap x402 yet — it has network, balance, faucet, and transfer only.