Local chain

Free and premium

A free resource and a paid one on the same backend, over HTTP and MCP.

The most common real shape: a free resource that proves the gateway fronts your existing API, plus a paid one behind x402 — both over HTTP and MCP.

examples/free-and-premium/config.yamlyaml
version: 1

merchant:
  id: free-and-premium-example
  name: Free and Premium Example
  publicBaseUrl: ${GATEWAY_PUBLIC_BASE_URL:-http://localhost:8080}

server:
  port: ${GATEWAY_PORT:-8080}
  host: 0.0.0.0

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

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

resources:
  # --- free: an unpaid endpoint an agent can call with no proof at all -----
  basic_weather:
    name: Basic Weather
    description: Current basic weather for a city. Free — no payment required.
    input:
      type: object
      properties:
        city:
          type: string
          description: City name, e.g. "Berlin"
      required: [city]
      additionalProperties: false
    backend:
      type: http
      method: GET
      url: ${MERCHANT_API_BASE_URL:-http://localhost:3000}/api/weather/{city}
      timeoutMs: 5000
    pricing:
      type: free
    expose: [http, mcp]

  # --- paid: the same backend's premium endpoint, gated by x402 ------------
  premium_report:
    name: Premium Market Report
    description: Latest premium market analysis. 0.01 USDC per call.
    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
      amount: "0.01"
      currency: USDC
    expose: [http, mcp]
    payments: [x402]

payments:
  x402:
    enabled: true
    network: ${X402_NETWORK:-eip155:84532}
    rpcUrl: ${X402_RPC_URL:-http://127.0.0.1:8545}
    # Placeholder — replace with the deployed MockUSDC address from
    # `.deploy/local.json` after `npm run chain:deploy`.
    asset: ${X402_ASSET:-0x000000000000000000000000000000000000dEaD}
    assetName: ${X402_ASSET_NAME:-MockUSDC}
    assetVersion: ${X402_ASSET_VERSION:-2}
    assetDecimals: ${X402_ASSET_DECIMALS:-6}
    # Merchant-controlled settlement destination. NEVER a gateway-owned wallet.
    # LOCAL DEVELOPMENT ONLY - DO NOT FUND (Anvil's well-known account #1).
    payTo: ${MERCHANT_WALLET:-0x70997970C51812dc3A010C7d01b50e0d17dc79C8}
    maxTimeoutSeconds: 120
    facilitator:
      mode: local
      # LOCAL DEVELOPMENT ONLY - DO NOT FUND (Anvil's well-known account #0).
      signerPrivateKey: ${X402_FACILITATOR_PRIVATE_KEY:-0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d}

What it demonstrates

  • Two resources on one gateway and one backend: free needs no payments; fixed requires at least one.
  • Both declare closed input schemas; the free one still validates its one field.
  • expose: [http, mcp] on both — one definition drives both adapters.

Run it

From a clone of the repository:

  1. Local chain and mock USDC
    bash
    npm run chain:start
    npm run chain:deploy
  2. The “existing backend” this example fronts
    bash
    npm run dev:merchant
  3. The gateway, with this example's config
    bash
    AGENT_COMMERCE_CONFIG=examples/free-and-premium/config.yaml \
    X402_ASSET=$(node -p "require('./.deploy/local.json').asset") \
      npx tsx src/gateway/main.ts
  4. Verify
    bash
    npm run agent-commerce -- validate --config examples/free-and-premium/config.yaml
    npm run agent-commerce -- doctor --config examples/free-and-premium/config.yaml
  5. Call it
    bash
    # free — no payment proof needed
    curl -s http://localhost:8080/api/resources/basic_weather/invoke \
      -X POST -H 'content-type: application/json' -d '{"city":"berlin"}'
    
    # paid — 402 without a proof
    curl -i http://localhost:8080/api/resources/premium_report/invoke -X POST
The config validates standalone with no environment set: every ${VAR:-default} falls back to a value that works against the local demo stack.

Source: examples/free-and-premium