Public networks

Multi-network mainnet

One resource offered in USDC on Base, Polygon, Arbitrum One and Solana; the buyer pays one offer.

Real fundsA template for offering one resource on several mainnets. Payments have settled on Base and Solana. Polygon and Arbitrum One are supported but have not run with real funds.

One resource, four offers: USDC on Base, Polygon, Arbitrum One and Solana. The challenge lists all four, and the buyer's proof selects one. Each offer has its own payTo and RPC endpoint, and all of them settle through one facilitator. Config load requires allowMainnet: true and each network's own USDC, and startup refuses an Anvil development address as payTo.

examples/multi-network-mainnet/config.yamlyaml
version: 1

merchant:
  id: multi-network-mainnet-example
  name: Multi-network Mainnet Example
  publicBaseUrl: ${GATEWAY_PUBLIC_BASE_URL:-http://localhost:8080}

server:
  port: ${GATEWAY_PORT:-8080}
  host: 0.0.0.0
  # Operator routes (receipts, events) 404 without this
  adminToken: ${ADMIN_TOKEN}

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 the network the buyer picks.
    input:
      type: object
      properties: {}
      additionalProperties: false
    backend:
      type: http
      method: GET
      url: ${MERCHANT_API_BASE_URL:-http://localhost:3000}/api/report
      timeoutMs: 10000
    pricing:
      type: fixed
      # USD names the networks' USDC at face value; receipts record USDC
      amount: "0.01"
      currency: USD
    expose: [http, mcp]
    payments: [x402]

payments:
  x402:
    enabled: true
    # No asset fields: each network's USDC, as config load requires on mainnet
    options:
      - network: eip155:8453
        payTo: ${MERCHANT_WALLET}
      - network: eip155:137
        payTo: ${MERCHANT_WALLET}
      - network: eip155:42161
        payTo: ${MERCHANT_WALLET}
      # The wallet's USDC token account must exist before a payment arrives
      - network: solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp
        payTo: ${SOLANA_MERCHANT_WALLET}
    # One endpoint per network, for health checks; dedicated endpoints avoid
    # public rate limits
    networks:
      eip155:8453:
        rpcUrl: ${BASE_RPC_URL:-https://base.drpc.org}
      eip155:137:
        rpcUrl: ${POLYGON_RPC_URL:-https://polygon-bor-rpc.publicnode.com}
      eip155:42161:
        rpcUrl: ${ARBITRUM_RPC_URL:-https://arbitrum-one-rpc.publicnode.com}
      solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:
        rpcUrl: ${SOLANA_RPC_URL:-https://api.mainnet-beta.solana.com}
    maxTimeoutSeconds: 600
    # Required, and required to be `true`. Mainnet is never a default
    allowMainnet: ${ALLOW_X402_MAINNET}
    facilitator:
      mode: remote
      # Health fails unless its /supported lists every network above
      url: ${X402_FACILITATOR_URL}
      auth:
        type: bearer
        token: ${X402_FACILITATOR_TOKEN}

What you need

Merchant addressesMERCHANT_WALLET for the EVM networks, SOLANA_MERCHANT_WALLET with an existing USDC token account.
A facilitatorX402_FACILITATOR_URL and X402_FACILITATOR_TOKEN, listing all four networks in /supported.
Solana supportnpm install @x402/[email protected] @solana/[email protected]
An admin tokenADMIN_TOKEN, which protects the receipts and events routes.

Check that the facilitator's /supported lists every network; otherwise health and readiness fail. For a facilitator that takes no credential, replace the auth block with type: none and add allowUnauthenticatedFacilitator: true, as in Base mainnet (PayAI).

Check it

bash
ALLOW_X402_MAINNET=true MERCHANT_WALLET=0x... SOLANA_MERCHANT_WALLET=... \
X402_FACILITATOR_URL=https://... X402_FACILITATOR_TOKEN=... ADMIN_TOKEN=... \
  npm run agent-commerce -- doctor --config examples/multi-network-mainnet/config.yaml

doctor names each network in its mainnet safety check, and compares every live offer with this config.

Prove a route

npm run test:mainnet settles one small payment per named target, with base, base-permit2, solana and near allowlisted; see smoke tests. Polygon and Arbitrum One have no mainnet smoke target yet.

Source: examples/multi-network-mainnet