Spend real money

x402 / USDC rail

Wrap the x402 rail: on-chain USDC settlement on base mainnet by default, metered into the same cross-rail cap as Stripe.

The x402 rail settles a real USDC payment on base mainnet — on-chain USDC settlement — and returns the SettlementResponse + PaymentRequirements the gateway extracts the metered cost from, metering into the same cross-rail cap as Stripe. This is the second rail and the moat. Real money is the default: with no --test-mode flag the rail resolves to base mainnet and moves real USDC on-chain.

Real money is the default

With no --test-mode, this rail settles real USDC on base mainnet. On-chain USDC transfers are irreversible — there is no chargeback. The --budget cap is the hard ceiling — an agent provably cannot spend past it — and it is mandatory. Start with the smallest cap that proves the point, and prove the cap in testnet mode first.

x402 needs a funded wallet + facilitator — not just a key

Unlike Stripe, x402's on-chain settle needs a funded base-mainnet USDC wallet and an x402 facilitator URL. A key alone does not fund the transfer. This is real setup; budget for it.

Prerequisites

  • A base mainnet wallet funded with real USDC (private key, 0x…), plus a little ETH on base mainnet for gas.
  • A mainnet x402 facilitator URL (the service that brokers the settle).

Adapter paths in the commands below are relative to a checkout of github.com/auths-dev/auths-mcp.

Setup

The live tab expects the funded mainnet wallet and facilitator from the prerequisites; the test tab expects a funded base-sepolia testnet wallet and a base-sepolia facilitator (see testnet mode).

bash
export X402_WALLET_PRIVATE_KEY=0x...
export X402_FACILITATOR_URL=https://...
auths-mcp wrap \
--test-mode \
--scope paid.call --budget '$5' --ttl 30m \
--custody-credential X402_WALLET_PRIVATE_KEY \
--custody-credential X402_FACILITATOR_URL \
-- node examples/payments/adapters/x402-adapter/server.mjs

Two things are non-negotiable here:

  • The cap is mandatory. Omit --budget and the gateway refuses to wrap the rail at all — budget-required, fail-closed, before any rail is touched. Real money with a skippable cap is not an allowed shape. See budgets.
  • The mode is disclosed. Every payment-rail wrap prints a mode=real banner and a mode=real … machine line at startup, so a live rail is never silent. Run auths-mcp wrap --show-mode … to resolve and disclose the mode without serving the proxy or settling on-chain.

Custody — the agent never holds the wallet

The wallet private key and facilitator URL live in the gateway's custody vault (--custody-credential), never with the agent. The agent holds only its scoped, budget-bound delegation. An agent that tries to settle without going through the gateway has no wallet — so the boundary is unbypassable.

How it works

text
agent ──tools/call (x402)──▶ gateway ──tools/call──▶ x402-adapter ──▶ facilitator (base mainnet)
│ reserve ceiling BEFORE the rail, ACROSS rails ◀── SettlementResponse
│ extract requirements.maxAmountRequired (atomic USDC → cents)
│ settle into the SAME cap the Stripe rail meters into
receipt: rail=x402, mode=real, tx=0x…, cross-rail total

Atomic USDC → cents

USDC has 6 decimals, so x402 amounts are atomic: 1500000 atomic = 1.50 USDC = 150 cents. The conversion is atomic / 10000, exact only — a sub-cent residue is refused, not truncated, so the metered cost equals the settled amount exactly. The receipt names the on-chain settlement tx (0x…).

Every metered call carries its amount

On an x402 wrap, every tools/call is metered — and the call itself must declare the amount it intends to pay, in atomic USDC, or the gateway refuses it metered-amount-required before the rail is touched (a metered call with no bounded amount is not an allowed shape):

json
{ "method": "tools/call",
"params": { "name": "paid_call", "arguments": { "amount_atomic": 30000 } } }

30000 atomic = $0.03. The declared amount is the reserve ceiling; the amount actually settled is read back from the rail's own response, never from the agent's claim.

The cross-rail moat

A $1.50 Stripe charge + a $1.50 x402 settle = $3.00 of one $5 cap. A later $0.60 x402 settle is tiny in a per-rail x402 silo (x402-alone is only $2.10) — but summed cross-rail onto $4.50 it would reserve to $5.10 > $5, so the gateway refuses it usage-cap-exceeded before the adapter settles. Two siloed per-rail budgets would each wave it through; the one cross-rail counter refuses it.

Never faked

With no wallet + facilitator the adapter runs against the recorded settlement shape (not a fabricated on-chain settle), so it stays runnable and self-checkable (node examples/payments/adapters/x402-adapter/test.mjs). If the live path is reached without a reachable facilitator it throws rather than inventing a transaction.

Troubleshooting

SymptomCause
gateway refuses to wrap (budget-required)no --budget — the cap is mandatory for a payment rail (both modes)
adapter runs but never settles on-chainno wallet + facilitator → it returns the recorded settlement shape
live path throws instead of settlingfacilitator URL unset/unreachable — by design, it never fabricates a tx
usage-cap-exceeded on a tiny x402 callthe cross-rail moat — the shared cap is already spent (expected)
sub-cent amount refusedx402 atomic→cents is exact-only; align the amount to whole cents
metered-amount-required on every callthe call declared no amount_atomic — see every metered call carries its amount
unsure which network is liverun auths-mcp wrap --show-mode … — it prints `mode=real

Testnet mode

Get testnet USDC at faucet.circle.com — select Base Sepolia as the network. We recommend MetaMask with the Base Sepolia network added (or whichever wallet you already use) to hold the funded address whose private key you export below.

To exercise the rail without real money, opt into sandbox rails with --test-mode (or AUTHS_MCP_TEST_MODE=1 for the adapter) — the test tab under Setup shows the full command. Testnet mode resolves the x402 rail to base-sepolia with testnet USDC — no real money. The cap is still mandatory, and the mode is disclosed as mode=test.

A real testnet settle needs both pieces, same as mainnet: a funded base-sepolia USDC wallet (X402_WALLET_PRIVATE_KEY, plus a little base-sepolia ETH for gas) and a base-sepolia X402_FACILITATOR_URL. With either missing the adapter returns the recorded settlement shape instead — it never fabricates an on-chain transaction — and with an unfunded wallet the facilitator declines the settle. Fund first, then wrap.

Use testnet mode to prove the cap refuses the over-cap call before you ever point a funded mainnet wallet at the rail. Then walk the full live path: Walkthrough: real money.