Reference
Networks & facilitators
Built-in EVM, Solana and NEAR networks, local development, facilitators, mainnet guardrails and public-network smoke tests.
Change the network and configure a remote facilitator to use a public network; the gateway code and pipeline stay the same.payments.x402 and experimental payments.mpp share the facilitator model and mainnet guardrails. MPP accepts only eip155:31337, Base Sepolia and Base.
Supported networks
| network | Network | Kind | Notes |
|---|---|---|---|
eip155:31337 | Local Anvil | local | Anvil's default chain id; no default asset; the only network for the local facilitator |
eip155:84532 | Base Sepolia | testnet | settled, EIP-3009 and Permit2 |
eip155:8453 | Base | mainnet | settled, EIP-3009 and Permit2 |
eip155:1 | Ethereum | mainnet | supported; no payment run |
eip155:137 | Polygon | mainnet | supported; no payment run |
eip155:42161 | Arbitrum One | mainnet | supported; no payment run |
eip155:421614 | Arbitrum Sepolia | testnet | supported; smoke target not yet run |
eip155:43114 | Avalanche C-Chain | mainnet | supported; no payment run |
solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp | Solana | mainnet | remote facilitator only; settled |
solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1 | Solana Devnet | testnet | remote facilitator only; settled |
near:mainnet | NEAR | mainnet | partial; remote facilitator only |
near:testnet | NEAR Testnet | testnet | partial; remote facilitator only |
Every built-in EVM network is supported: the signing code is the same on each, and on 2026-10-10 each network's USDC signing domain matched the chain and Permit2 and its x402 proxy were deployed. Every network also needs a facilitator that lists it, or health fails. NEAR has settled on testnet and mainnet only through the SDK facilitator the test suite runs, so it stays partial. Other tokens on built-in EVM networks also remain partial: the settlement test uses a local chain with a built-in network's id (see the settlement asset). Solana and NEAR specifics are in Configuration.
Declared networks
Declare another EVM network with kind for mainnet guardrails, displayName and rpcUrl:
payments:
x402:
networks:
eip155:10: { kind: mainnet, displayName: OP Mainnet }A declared network has no default asset: supply the token's address, decimals and, for EIP-3009, its EIP-712 domain. Support is partial because the chain and token are operator-supplied. A built-in network cannot be redeclared; its entry may set rpcUrl only. Any other unknown id is CONFIG_INVALID at load: the chain id is part of the buyer's signed EIP-712 domain and must resolve explicitly.
Deployment mode
local, testnet or mainnet comes from the network: eip155:31337 is local, and every other network is the testnet or mainnet it names. Offers on several networks must share one mode. The mode is reported by doctor, by the payment provider's health check, and at /.well-known/agent-commerce. In local mode the health check also requires the RPC to answer Anvil's anvil_nodeInfo, because an RPC can report any chain id.
The facilitator
A facilitator verifies the buyer's authorization and broadcasts the transfer, paying gas on EVM, transaction fees through its Solana fee payer, or gas through its NEAR relayer. With remote, the gateway holds no facilitator signing key.
| facilitator.mode | Who signs | Where it is allowed |
|---|---|---|
local | this process, with a key you configure | eip155:31337 only; refused at config load anywhere else |
remote | an HTTP facilitator you point at | anywhere; required on every public network |
A config that still pairs the local facilitator with eip155:84532, the local chain's id before 2.1, is refused with a pointer to eip155:31337.
| auth.type | What it sends | Needs |
|---|---|---|
none | nothing | on mainnet, allowUnauthenticatedFacilitator: true |
bearer | a static token | nothing installed |
cdp | a fresh Coinbase Developer Platform JWT per request | @coinbase/x402, and url set to https://api.cdp.coinbase.com/platform/v2/x402 |
Omitting auth is the same as type: none. Any other type is refused at config load, and so is cdp with any other url, since it signs each request for CDP's.
A facilitator cannot redirect your money: the buyer's signature fixes the token, amount and recipient. What it can do is see every authorization you handle, delay or drop a broadcast until the authorization expires, and stop answering. The gateway accepts its reported success once it names the offer's network and a transaction in that family's form, without reading the chain.
In remote mode, health checks require the facilitator to list the scheme on every offer's network. One failing offer fails the x402 provider and /ready. With several offers, /.well-known/agent-commerce publishes each status anddoctor names the failing offers.
Configuration by network
payments:
x402:
enabled: true
network: ${X402_NETWORK:-eip155:31337} # CAIP-2; the local chain
rpcUrl: ${X402_RPC_URL:-http://127.0.0.1:8545}
asset: ${X402_ASSET} # ERC-20 with EIP-3009
assetName: ${X402_ASSET_NAME} # EIP-712 domain name
assetVersion: ${X402_ASSET_VERSION}
assetDecimals: ${X402_ASSET_DECIMALS}
payTo: ${MERCHANT_WALLET} # merchant-controlled. NEVER the gateway's.
maxTimeoutSeconds: 120
paymentFlow: authorization # default; or upfront
facilitator:
mode: local # eip155:31337 only
signerPrivateKey: ${X402_FACILITATOR_PRIVATE_KEY}signerPrivateKey must be a 32-byte hex key. Provider construction refuses a well-known Anvil key against an RPC that is not local or private, and the local health check requires the RPC to answer as Anvil.
payments:
x402:
enabled: true
network: eip155:84532
rpcUrl: https://base-sepolia-rpc.publicnode.com # health checks and reconcile
payTo: ${MERCHANT_WALLET} # no asset fields: the network's USDC
maxTimeoutSeconds: 300
facilitator:
mode: remote
url: https://x402.org/facilitator
auth: { type: none }No asset fields: the offer settles in the network's USDC, and the registry fills in the address, EIP-712 domain and decimals. Test USDC from faucet.circle.com; the buyer needs no ETH. doctor reports TESTNET on Base Sepolia (eip155:84532).
payments:
x402:
enabled: true
network: eip155:8453
rpcUrl: ${X402_RPC_URL:-https://base.drpc.org} # health checks and reconcile
payTo: ${MERCHANT_WALLET} # no asset fields: USDC on Base
maxTimeoutSeconds: 600
allowMainnet: ${ALLOW_X402_MAINNET} # must resolve to true
facilitator:
mode: remote
url: ${X402_FACILITATOR_URL} # https only
auth:
type: bearer # or: cdp, with apiKeyId and apiKeySecret (needs @coinbase/x402 and CDP's url)
token: ${X402_FACILITATOR_TOKEN}doctor prints LIVE MAINNET MODE - REAL FUNDS and names each network in its mainnet safety check.
Solana and NEAR take the same shape with their own network id and payTo. To accept several networks at once, see Several options.
Mainnet guardrails
All of these are refused at config load, before the gateway starts:
| Refused | Because |
|---|---|
allowMainnet absent or false | mainnet is never a default |
facilitator.mode: local | a funded gas key inside the resource server |
facilitator.auth.type: none | unless allowUnauthenticatedFacilitator accepts it by name |
| a non-HTTPS facilitator.url | authorizations and settlement results in the clear |
| a well-known Anvil payTo or MPP recipient | its private key is public knowledge |
| an asset other than USDC for MPP, Solana or NEAR | they settle only in USDC |
| the wrong USDC assetName or assetVersion | the buyer signs that EIP-712 domain |
A declared mainnet has no default asset to check. The same rules apply to any non-local deployment where they make sense: plain HTTP is allowed only to a local or private host, and a development payTo is refused on testnet too.
"USDC"; Base mainnet's reports "USD Coin". The buyer signs that string, so the wrong one gets every payment refused with invalid_exact_evm_token_name_mismatch after they signed. The network registry pins the domain of each built-in network's USDC: omit the asset fields to use it, and config load refuses an override that does not match.Recorded settlements
Each of these moved 0.01 USDC from a buyer to a merchant through a remote facilitator:
| Rail | Network | Method | Transaction |
|---|---|---|---|
| x402 | Base Sepolia | EIP-3009 | 0xea41b234c4… |
| x402 | Base Sepolia | Permit2 | 0xa66f56cf39… |
| x402 | Base | EIP-3009 | 0x57ec81c2a3… |
| x402 | Base | Permit2 | 0x79d3da1145… |
| x402 | Solana Devnet | SPL transfer | 5289xbeyHJ… |
| x402 | Solana | SPL transfer | E7gAuG8pMA… |
| x402 | NEAR Testnet | NEP-141 transfer | 5tdtGLv4TB… |
| x402 | NEAR | NEP-141 transfer | 7hU3imHepV… |
| MPP | Base Sepolia | EIP-3009 | 0x5d9fcd111b… |
| MPP | Base | EIP-3009 | 0xf4255bd7ec… |
Facilitators broadcast these payments and paid their ETH, SOL or NEAR fees. Permit2 needs a one-time, gas-paying token approval from the buyer. The NEAR runs used the suite's SDK facilitator and relayer, so NEAR support stays partial until a hosted facilitator settles. Buyer and merchant keys stayed outside the gateway.
Smoke tests
npm run test:testnet spends test USDC and npm run test:mainnet real USDC on public x402 payments, then verifies settlement on chain. Run them by hand on the wallet holder's machine, never in CI, where funded keys would enter repository secrets. Without settings, both skip and list what each target needs.
| Target | Network | Default facilitator | Runs unnamed |
|---|---|---|---|
base-sepolia | eip155:84532, EIP-3009 | x402.org | yes |
base-sepolia-permit2 | eip155:84532, Permit2 | x402.org | no |
arbitrum-sepolia | eip155:421614, EIP-3009 | PayAI | no |
solana-devnet | Solana Devnet | x402.org | yes |
near-testnet | near:testnet | the SDK's, started by the suite | yes |
base | eip155:8453, EIP-3009 | none: X402_FACILITATOR_URL | no |
base-permit2 | eip155:8453, Permit2 | none: X402_FACILITATOR_URL | no |
solana | Solana mainnet | none: X402_SOLANA_MAINNET_FACILITATOR_URL | no |
near | near:mainnet | the SDK's, started by the suite | no |
Set X402_SMOKE_TARGETS to a comma-separated list for a release check: an unknown target or a missing setting fails before signing, so the check cannot pass by skipping. Without it, test:testnet runs the configured targets marked “runs unnamed” and test:mainnet runs nothing. Mainnet also needs ALLOW_X402_MAINNET=true. Each target pays X402_SMOKE_AMOUNT USDC once, 0.01 by default, and the suite refuses a payment above 1.00 on testnet or 0.10 on mainnet.
For each target, a run proves in order:
- on mainnet, the same config without
allowMainnetis refused; - discovery reports the target's network and mode without the facilitator's URL, and the provider's health check passes;
- the buyer holds enough USDC, and the target's preparation is in place: a Permit2 approval, the merchant's Solana token account or the NEAR buyer's full-access key;
- the SDK client pays once, and the gateway answers 200 with a successful
PAYMENT-RESPONSEon the target's network; - the chain shows the transfer the settlement reported: the token's
Transferon EVM, the token balances of the confirmed Solana transaction, or the buyer'sft_transferand the token's successful receipt on NEAR; - exactly one gateway receipt names the transaction;
- the same payment presented again gets 402 or 409, and the backend is not called again;
- nothing the gateway logged contains a key or a credential.
Balance changes alone cannot prove settlement; an unrelated transfer could explain them. Each settled target prints an evidence record, and X402_SMOKE_EVIDENCE_FILE appends them as JSON lines. The full settings table is in docs/smoke-tests.md.
Worked configurations
- Base Sepolia, Base mainnet and Base mainnet (PayAI), which uses an unauthenticated facilitator;
- Arbitrum Sepolia, not yet run;
- Solana devnet and NEAR testnet;
- Multi-network mainnet: Base and Solana, which have settled, with Polygon and Arbitrum One, which have not run.