Public networks

Base mainnet

Real funds on Base, with every guardrail spelled out and nothing defaulted.

Real fundsRead Networks & facilitators first. Every ${VAR} here has no fallback, so a missing one fails config loading instead of resolving to something plausible.
examples/base-mainnet/config.yamlyaml
version: 1

merchant:
  id: base-mainnet-example
  name: Base Mainnet Example
  publicBaseUrl: ${GATEWAY_PUBLIC_BASE_URL}

server:
  port: ${GATEWAY_PORT:-8080}
  host: 0.0.0.0
  # Operator routes carry the commerce ledger. Without this they 404.
  adminToken: ${GATEWAY_ADMIN_TOKEN}
  allowedOrigins: []

storage:
  receipts:
    driver: sqlite
    path: ${RECEIPT_STORE_PATH:-./data/receipts.sqlite}

protocols:
  http:
    enabled: true
  mcp:
    enabled: true
    mountPath: /mcp

resources:
  premium_report:
    name: Premium Report
    description: One paid endpoint, settled in USDC on Base.
    input:
      type: object
      properties: {}
      additionalProperties: false
    backend:
      type: http
      method: GET
      url: ${MERCHANT_API_BASE_URL}/api/report
      timeoutMs: 10000
    pricing:
      type: fixed
      amount: "0.01"
      currency: USDC
    expose: [http, mcp]
    payments: [x402]

payments:
  x402:
    enabled: true
    network: eip155:8453                         # required. Base mainnet.
    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}                    # required. Your wallet.
    maxTimeoutSeconds: 600
    allowMainnet: ${ALLOW_X402_MAINNET}          # required, and required to be true
    facilitator:
      mode: remote                               # required. `local` is refused.
      url: ${X402_FACILITATOR_URL}               # required, and must be https
      auth:
        type: cdp                                # needs @coinbase/x402
        apiKeyId: ${CDP_API_KEY_ID}
        apiKeySecret: ${CDP_API_KEY_SECRET}
      # For any facilitator that takes a static token:
      #   auth:
      #     type: bearer
      #     token: ${X402_FACILITATOR_TOKEN}

What you need

Variable
MERCHANT_WALLETyour wallet — address only
ALLOW_X402_MAINNET=truethe explicit opt-in; never a default
X402_FACILITATOR_URLhttps, and authenticated
CDP_API_KEY_ID, CDP_API_KEY_SECRETor a bearer token with auth.type: bearer
GATEWAY_ADMIN_TOKENwithout it you cannot read your own ledger
GATEWAY_PUBLIC_BASE_URLwhere agents reach you

Validate before anything else

bash
ALLOW_X402_MAINNET=true \
MERCHANT_WALLET=0xYourWallet \
GATEWAY_PUBLIC_BASE_URL=https://your.gateway \
GATEWAY_ADMIN_TOKEN=... \
MERCHANT_API_BASE_URL=http://localhost:3000 \
X402_FACILITATOR_URL=https://... \
CDP_API_KEY_ID=... CDP_API_KEY_SECRET=... \
  npm run agent-commerce -- validate --config examples/base-mainnet/config.yaml
Drop any one of themconsole
FAIL  CONFIG_INVALID: Unresolved environment variable "${ALLOW_X402_MAINNET}"
      referenced at config path "$.payments.x402.allowMainnet"

doctor then reports the deployment as LIVE MAINNET MODE.

Prove it settles

bash
export ALLOW_X402_MAINNET=true
export X402_MAINNET_BUYER_PRIVATE_KEY=0x...   # funded with USDC on Base
export X402_MAINNET_MERCHANT_ADDRESS=0x...
export X402_FACILITATOR_URL=https://...
export CDP_API_KEY_ID=... CDP_API_KEY_SECRET=...   # or X402_FACILITATOR_TOKEN
npm run test:mainnet

Every run spends X402_MAINNET_AMOUNT (default 0.01) of real USDC. It never runs in CI — a workflow would mean a mainnet key in repository secrets.

Source: examples/base-mainnet