Merchant setup
Accept payments
Configure x402 settlement for a local chain, Base Sepolia or Base mainnet — funds go straight to your wallet.
Payments use x402 v2, scheme exact, on EVM: the buyer signs an EIP-3009 authorisation that names your payTo as recipient, and a facilitator broadcasts it. Funds move buyer → merchant directly on chain. The gateway holds neither the money nor a key.
npm install @devlab.group/agent-commerce @x402/core @x402/evm viemConfigure the rail
A private Anvil chain with a mock USDC — deterministic, free, and what the demo and CI use.
npm run chain:start # Anvil, chain id 84532
npm run chain:deploy # MockUSDC, funded buyer, writes .deploy/local.jsonpayments:
x402:
enabled: true
network: ${X402_NETWORK} # CAIP-2; eip155:84532 locally
rpcUrl: ${X402_RPC_URL}
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
facilitator:
mode: local # local dev chain only
signerPrivateKey: ${X402_FACILITATOR_PRIVATE_KEY}validate and doctor read .deploy/local.json and fill the local X402_* and MERCHANT_WALLET placeholders for you.Real USDC on a public testnet through the public x402.org facilitator. No key anywhere in this file.
payments:
x402:
enabled: true
network: eip155:84532
rpcUrl: https://base-sepolia-rpc.publicnode.com # health checks only
asset: "0x036CbD53842c5426634e7929541eC2318f3dCF7e" # Circle USDC
assetName: USDC
assetVersion: "2"
assetDecimals: 6
payTo: ${MERCHANT_WALLET}
maxTimeoutSeconds: 300
facilitator:
mode: remote
url: https://x402.org/facilitator
auth: { type: none }MERCHANT_WALLET=0xYourMerchantAddress \
npx agent-commerce validate --config config.yamlGet test USDC for the buyer at faucet.circle.com. The buyer needs no ETH: EIP-3009 is signed offline and the facilitator pays the gas.
Real funds. Nothing is defaulted, and every line below is checked at load.
payments:
x402:
enabled: true
network: eip155:8453
rpcUrl: ${X402_RPC_URL:-https://base.drpc.org} # health checks only
asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" # USDC on Base
assetName: "USD Coin" # the EIP-712 domain, not "USDC"
assetVersion: "2"
assetDecimals: 6
payTo: ${MERCHANT_WALLET}
maxTimeoutSeconds: 600
allowMainnet: ${ALLOW_X402_MAINNET} # must resolve to true
facilitator:
mode: remote
url: ${X402_FACILITATOR_URL} # https only
auth:
type: bearer # or: cdp (needs @coinbase/x402)
token: ${X402_FACILITATOR_TOKEN}| Required | Why |
|---|---|
allowMainnet: true | mainnet is never a default |
facilitator.mode: remote | no gas key inside the gateway |
| an HTTPS facilitator.url | no authorisations in the clear |
| facilitator auth, or allowUnauthenticatedFacilitator | an explicit counterparty decision |
| a non-development payTo | Anvil keys are public knowledge |
asset = USDC on Base, assetName: "USD Coin" | the EIP-712 domain the buyer signs |
doctor then reports LIVE MAINNET MODE - REAL FUNDS.
Put a price on a resource
pricing:
type: fixed
amount: "0.01" # decimal string in display units — never a float
currency: USDC
payments: [x402]Amounts are converted to base units inside the provider (6 decimals for USDC). More precision than the asset supports is a configuration error, not a rounding opportunity.
Facilitator authentication
| auth | Config | Installs |
|---|---|---|
none | auth: { type: none } | nothing |
bearer | auth: { type: bearer, token: ${X402_FACILITATOR_TOKEN} } | nothing |
cdp | auth: { type: cdp, apiKeyId: ${CDP_API_KEY_ID}, apiKeySecret: ${CDP_API_KEY_SECRET} } | @coinbase/x402 |
What an agent receives
An unpaid call to a paid resource answers 402 (HTTP) or an isError tool result (MCP) carrying this envelope:
{
"status": "payment-required",
"code": "PAYMENT_REQUIRED",
"requestId": "…",
"resourceId": "market_report",
"message": "Payment of 0.01 USDC is required for resource \"market_report\". …",
"payment": {
"provider": "x402",
"version": "2",
"amount": "0.01",
"currency": "USDC",
"destination": "0x…",
"network": "eip155:84532",
"asset": "0x…",
"expiresAt": "…",
"accepts": [ /* x402 v2 PaymentRequirements, verbatim */ ],
"envelope": { /* x402 v2 PaymentRequired, verbatim */ }
}
}The agent side of that exchange is in Handle a payment.
Where the money went
On startup the gateway prints the effective settlement destination, so an operator can confirm it at a glance:
x402 settlement
network eip155:84532 via http://127.0.0.1:8545
asset 0x… (MockUSDC v2)
pays to 0x7099…79C8 <- merchant-controlled, not the gateway
facilitator localThe same destination is published at GET /.well-known/agent-commerce and printed by doctor.