Skip to main content
Glama

x402-micro-tollgate

Visa for AI agents that can’t hold a card.

Second-scale settlement bridge for agent traffic — the Web3 tollbooth that clears USDC micropayments and takes 0.1%.

为千万级无支付能力的 AI Agent 提供秒级过桥清算,抽成 0.1% 的 Web3 版 Visa 收费站

Security backed by Coinbase CDP. We don't touch your keys or settle payments on custom cryptography — EIP-3009 authorization nonces are single-use at the CDP facilitator (source of truth for on-chain uniqueness). The gateway hardens four buyer/seller trust surfaces: payment-proof replay/race, SSRF on URL fetch, upstream bypass (shared-secret trust header), and Base congestion / settle-latency (pending + retry same proof — never treat HTTP timeout alone as “payment failed”).

What it is

A thin self-hosted tollgate in front of your API or MCP tools. When an agent calls a paid route, it gets HTTP 402 as a price tag (not a hard deny). It pays a small USDC (or configured USDT) amount; you deliver. Default clearing is Base + USDC via Coinbase CDP; multi-network accepts are env-driven (see matrix below). Humans can still hit a free path; agents convert at the booth.

Self-host is free (MIT). Repo: github.com/kevin2003050666-coder/x402-micro-tollgate · Questions: 2767111713@qq.com

Not an official Coinbase product. Not a full agent marketplace. Not a billing SaaS. A sharp Visa-style tollbooth. Not fiat custody.

Why this exists: MANIFESTO.md — pain, engineering, on-chain proof, MIT.

Related MCP server: pontage

Network × asset matrix

Default deploy profile: Base USDC only (dev: Base Sepolia). Enable multi with NETWORKS + ASSETS or ACCEPTS_JSON / X402_* aliases. Browser HTML 402 shows a chain + asset picker when more than one accept is configured.

Network

CAIP-2

USDC

USDT

Status

Facilitator / notes

Base

eip155:8453

native Circle

bridged

live

CDP exact. FeeSplitterFactory live (deployments/base.json)

Base Sepolia

eip155:84532

test USDC

live

CDP testnet

Optimism

eip155:10

native

bridged

config-ready

Addresses wired; not on current CDP matrix

Arbitrum One

eip155:42161

native

bridged

live

CDP exact; factory stub

Polygon PoS

eip155:137

native (not USDC.e)

PoS USDT

live

CDP exact; factory stub

BNB Smart Chain

eip155:56

peg 18 dec

peg 18 dec

config-ready

Not on CDP list

Ethereum

eip155:1

native

Tether

config-ready

Facilitator-dependent

Avalanche C-Chain

eip155:43114

native

USDT

config-ready

In @x402/evm defaults

Celo / Sei

eip155:42220 / 1329

USDC

config-ready

Catalog extras

Solana

solana:5eykt…

SPL USDC

SPL USDT

experimental

CDP lists Solana; gateway stubs + optional SVM paywall (SOLANA_PAY_TO)

TRON

tron:mainnet

TRC-20

planned

No scheme in deps — never in accepts[]

USDT on EVM typically uses extra.assetTransferMethod: "permit2" (not EIP-3009). FeeSplitter production path remains Base + USDC; other chains are config stubs under contracts/deployments/.

Browser wallets

Wallet

Role

Coinbase Smart Wallet (Passkey)

Primary CTA

MetaMask

Via wagmi injected target

Injected

Other browser wallets

WalletConnect

When WALLETCONNECT_PROJECT_ID set

Solana paywall

Experimental, behind Solana accepts / PAYWALL_SVM

TronLink

Not offered (TRON planned only)


Who it’s for

  • API sellers who want agents to pay per call — without building a billing console or selling monthly seats

  • Tool builders shipping paid MCP endpoints into Cursor / Claude

  • Operators who want Visa-like take-rate economics (0.1%), not hosting invoices

How money works

  1. Price tag — unpaid traffic gets HTTP 402 with the amount due (conversion, not rejection)

  2. Pay — the agent settles a USDC micropayment in seconds via Coinbase CDP

  3. Unlock — the tollgate proxies to your upstream; you keep ~99.9%, protocol take 0.1%

No protocol monthly fee. Monetization is the toll — like interchange on card rails.


1-minute quickstart

Requires Node.js 22+.

git clone https://github.com/kevin2003050666-coder/x402-micro-tollgate
cd x402-micro-tollgate && cp .env.example .env
# set CDP_API_KEY_ID, CDP_API_KEY_SECRET, X402_PAY_TO (optional: PUBLIC_BASE_URL, UPSTREAM_URL)
npm i && npm start
# curl http://127.0.0.1:8402/health   → 200
# curl http://127.0.0.1:8402/x402/discover → 200 (free agent yellow pages)
# curl http://127.0.0.1:8402/v1/quote → 402
# curl "http://127.0.0.1:8402/v1/fetch-md?url=https://example.com" → 402 (paid HTML→Markdown demo)

npx one-liners (no clone):

npx x402-micro-tollgate@0.3.3
npx x402-micro-tollgate@0.3.3 --seller 0xYourReceivingAddress --stdio

--seller / -s sets X402_PAY_TO before boot (env vars still work). Default without --stdio is HTTP + /mcp on port 8402.

Agent / crawler docs: llms.txt (also GET /llms.txt and GET /.well-known/llms.txt) · OpenAPI 3.1: docs/openapi.yaml · well-known: GET /.well-known/x402.json, GET /.well-known/agent-card.json

Alternative one-liner: docker compose up --build

Without CDP keys the process runs in demo mode (protocol-shaped 402 / MCP PaymentRequired, no on-chain settle).

Cursor mcp.json:

{
  "mcpServers": {
    "x402-micro-tollgate": {
      "url": "http://127.0.0.1:8402/mcp"
    }
  }
}

Stdio:

{
  "mcpServers": {
    "x402-micro-tollgate": {
      "command": "npx",
      "args": [
        "x402-micro-tollgate@0.3.3",
        "--seller",
        "0xYourReceivingAddress",
        "--stdio"
      ],
      "env": {
        "CDP_API_KEY_ID": "...",
        "CDP_API_KEY_SECRET": "...",
        "PUBLIC_BASE_URL": "https://your.public.host"
      }
    }
  }
}

(X402_PAY_TO in env still works if you omit --seller.)

Library drop-in (permissionless seller)

import express from "express";
import { x402Tollgate } from "x402-micro-tollgate";

const app = express();
app.use("/v1", await x402Tollgate({ seller: process.env.SELLER! }));

Or set SELLER / X402_SELLER in .env and run npm start. See Environment for the $10 threshold, factory address, and fee-release details.

Buyer / Agent client

Thin TypeScript helper that wraps official @x402/fetch + @x402/evm ExactEvmScheme. On HTTP 402 it parses PAYMENT-REQUIRED, checks budgets, signs EIP-3009 once, and retries once with PAYMENT-SIGNATURE (never loops).

npm i x402-micro-tollgate@0.3.3
import { createX402Fetch } from "x402-micro-tollgate/client";
const fetch402 = createX402Fetch({ privateKey: process.env.BUYER_KEY as `0x${string}` });
const res = await fetch402("https://x402-micro-tollgate.onrender.com/v1/quote");

Defaults: maxSingleSpendUsdc = 0.05, maxTotalSpendUsdc = 1.00 (clear Error if exceeded). Circuit breaker (before sign): max 10 paid 402s / 0.05 USDC per rolling 60s, plus fingerprint dead-loop halt (maxPaidRequestsPerMinute, maxSpendUsdcPerMinute, enableFingerprintBreaker). Hot-wallet warning: keep only ~$5–$10 USDC on the signing key — treat it as an agent spend faucet, not a treasury.

Human-delegated agent credit (CLI)

Thin PoC: Human sets B_maxAgent loops createX402Fetch against a paid URL (auto 402 → pay once → retry). Chrome / Telegram / Session Key / Passkey UI are deferred.

# SAFETY: fund BUYER_PRIVATE_KEY with ≤ $5–$10 USDC only. Never commit keys.
export BUYER_PRIVATE_KEY=0xYourHotWalletKey
# optional: TARGET_URL MAX_SINGLE_USDC MAX_TOTAL_USDC ROUNDS
npm run poc:buyer-agent
# or: npx tsx scripts/buyer-agent-poc.ts

Env

Default

Notes

BUYER_PRIVATE_KEY

(required)

0x… EOA used to sign EIP-3009

TARGET_URL

https://x402-micro-tollgate.onrender.com/v1/quote

Paid route to hit

MAX_SINGLE_USDC

0.05

Per-call cap (maxSingleSpendUsdc)

MAX_TOTAL_USDC

1.00

Session cap / human B_max

ROUNDS

3

Stop after N calls or when budget exhausted

The script imports the local client (../src/client) so repo CI does not need a live chain pay. For a live demo against the published package, swap the import to x402-micro-tollgate/client (same API as 0.3.2+). Each round prints status, autoPaid, and sessionSpendUsdc on stdout; the safety banner goes to stderr.


Deploy

Deploy to Render

Uses render.yaml: Node 22, npm start, health /health, env names from .env.example. Set CDP_API_KEY_ID, CDP_API_KEY_SECRET, and X402_PAY_TO in the dashboard for live settlement; set PUBLIC_BASE_URL to your https://….onrender.com origin for Bazaar.

Ops bump: redeploy discover + well-known (force Render GitHub auto-deploy so production picks up GET /x402/discover, GET /.well-known/x402.json, GET /.well-known/agent-card.json, and MCP fetch_md). If auto-deploy does not fire within a few minutes after this lands on main, use the Render dashboard → Manual Deploy → Deploy latest commit.

Self-host production: Docker — docker compose up --build (see Dockerfile / docker-compose.yml).


Environment

Variable

Default

Notes

PORT

8402

HTTP listen port

UPSTREAM_URL

(unset → mock)

Your API origin

UPSTREAM_SHARED_SECRET / X402_UPSTREAM_SECRET

Optional. After settle, tollgate injects X-Tollgate-Secret + X-Tollgate-Paid (HMAC). Upstream must require them and reject public direct hits (not mTLS)

X402_PAY_TO

EVM receive address (live mode SDK init)

SELLER / X402_SELLER

Permissionless seller EOA (EIP-55 validated; invalid → startup fail)

FACTORY_ADDRESS

Operator-set FeeSplitterFactory for CREATE2 predict when amount ≥ threshold (Base live address in contracts/deployments/base.json; do not hardcode secrets)

FEE_FREE_BELOW_USDC / X402_FEE_FREE_BELOW_USDC

10000000

Atomic USDC (6 decimals). < $10 → payTo=seller; ≥ $10 → FeeSplitter

CDP_API_KEY_ID / CDP_API_KEY_SECRET

CDP facilitator (+ Onramp session tokens)

CDP_CLIENT_API_KEY

Public CDP client key for browser Smart Wallet paywall (safe in frontend). Alias: CDP_CLIENT_KEY

X402_FACILITATOR_URL / CDP_FACILITATOR_URL

CDP default

Optional alternate facilitator base URL for MCP (createCdpFacilitatorClient({ baseUrl })). Single-vendor; no multi-facilitator routing yet

PRICE

$0.001

Network default USDC/USDT price tag

NETWORK / X402_NETWORK

eip155:84532 / 8453

Primary CAIP-2 (aliases: base, optimism, …)

NETWORKS / X402_NETWORKS

(primary only)

Comma/JSON list of networks for multi-accept

ASSETS / X402_ASSETS

USDC

Cross with NETWORKS → accepts (USDC, USDT)

ACCEPTS_JSON / X402_ACCEPTS_JSON

Explicit accepts array (overrides NETWORKS×ASSETS)

FACTORY_ADDRESSES

JSON map caip2 → FeeSplitterFactory (Base live; others optional)

SOLANA_PAY_TO

Base58 payTo for experimental Solana accepts

PAYWALL_SVM

false

Force @x402/paywall SVM UI (also auto if Solana in accepts)

WALLETCONNECT_PROJECT_ID

Enables WalletConnect in browser paywall

X402_MIN_PRICE_USDC

Optional static minimum accept amount (atomic USDC). Effective price = max(PRICE, this, gas floor)

X402_DYNAMIC_MIN_ENABLED

false

When true, Base base-fee oracle may bump min accept / feeFreeBelow if gas would eat too much of the payment

X402_GAS_COST_MAX_FRACTION

0.5

Bump when estimated gas USD > this fraction of the payment

X402_GAS_ORACLE_TTL_MS

30000

baseFee cache TTL (clamped 15–60s)

X402_GAS_RPC_URL / BASE_RPC_URL

public Base RPC

JSON-RPC for eth_getBlockByNumber baseFee

X402_GAS_USED_ESTIMATE

100000

Rough L2 gas units for settle cost estimate

X402_ETH_USD

4000

Conservative ETH/USD floor (no live FX required)

X402_ENVIRONMENT

development

or production

GATED_PREFIX

/v1

HTTP paths that require payment

PUBLIC_BASE_URL

http://127.0.0.1:$PORT

Public https:// origin for Bazaar resource URLs

CONTACT_EMAIL

2767111713@qq.com

Landing contact mailto (not a SaaS CTA)

FEE_BPS

10

Documented operator fee (0.1%). Live settle still pays 100% to payTo until release()

FEE_COLLECTOR

0xa922…7e30E

Fixed operator wallet for the 0.1% slice after FeeSplitter.release()

MERCHANTS_JSON

Optional hosted multi-tenant registry (not required when SELLER is set)

MERCHANTS_FILE

merchants.json

File path; falls back to merchants.example.json / built-in demo when no seller

DEFAULT_MERCHANT

demo

Used when ?merchant= / x-merchant-id omitted — agents SHOULD always send merchant id

REQUIRE_MERCHANT

false

When true, gated paths reject missing merchant id with 400 {error:"merchant_required"} (no demo fallback)

PAYMENT_DEDUPE_TTL_MS

600000

In-memory payment-proof idempotency window (10 min). CDP facilitator nonce remains source of truth

PAYMENT_DEDUPE_MAX_ENTRIES

10000

Max keys in the gateway PaymentDedupeStore LRU (no Redis; interface is Redis-ready)

X402_VERIFY_TIMEOUT_MS

15000

Documented verify-phase budget (local / facilitator verify)

X402_SETTLE_TIMEOUT_MS

180000

Wait for facilitator/on-chain settle before 202 payment_pending (default 3 min — Base congestion)

KEEPER_ENABLED

false

Optional FeeSplitter release() keeper — never on by default

KEEPER_DRY_RUN

(see notes)

true logs keeper_would_release without sending txs

KEEPER_PRIVATE_KEY

EVM key for live release() only (never commit)

KEEPER_RPC_URL

Base public RPC

JSON-RPC endpoint for balance + release

KEEPER_INTERVAL_MS

3600000

Poll interval (1h)

KEEPER_MIN_USDC

1000000

Min USDC balance (atomic) before release() — default $1

Permissionless seller + $10 threshold (0.3.0)

0.3.0: set SELLER (or use x402Tollgate({ seller })) and FACTORY_ADDRESS for ≥ $10 CREATE2 routing. No MERCHANTS_JSON required.

Amount (USDC atomic, 6 decimals)

accepts[].payTo

< 10_000_000 ($10)

seller EOA — 0 protocol fee

10_000_000 ($10)

CREATE2-predicted FeeSplitter for that seller

x402 exact + EIP-3009 only credits payTo — it does not execute FeeSplitter / factory code and does not split in the same transaction. For ≥ $10: set FACTORY_ADDRESS to the operator-deployed factory (Base reference: contracts/deployments/base.json), call getOrCreate(seller) before the first such settle, then release() later (99.9% / 0.1%). Address typos send funds to the wrong place — the gateway checksum-validates SELLER at startup. Do not hardcode private keys or operator secrets in source.

Merchant registry (optional hosted multi-tenant)

Operator FEE_COLLECTOR is fixed: 0xa922F38041B5ee227c96A547F106F1330447e30E. Each merchant gets their own FeeSplitter (seller = merchant wallet, feeCollector = operator, feeBps = 10). The registry maps merchantId → splitter address (payTo) + seller for display. When SELLER is set, the registry is optional; ?merchant= still works if you provide MERCHANTS_JSON.

Register a merchant:

  1. Deploy FeeSplitter with seller = merchant wallet, feeCollector = 0xa922F38041B5ee227c96A547F106F1330447e30E, feeBps = 10, and the chain’s native USDC as asset.

  2. Add an entry to MERCHANTS_JSON (or merchants.json / copy from merchants.example.json):

    {
      "acme": {
        "seller": "0xMerchantWallet…",
        "payTo": "0xDeployedFeeSplitter…",
        "label": "Acme API"
      }
    }
  3. Call gated APIs with ?merchant=acme or header x-merchant-id: acme (case-insensitive). Agents SHOULD always send merchant id. If omitted, the gateway falls back to DEFAULT_MERCHANT (demo) — that means traffic (and USDC) can silently land on the demo FeeSplitter. Set REQUIRE_MERCHANT=true to reject missing merchant with 400 { "error": "merchant_required" }. Unknown merchant on gated paths → 400 { "error": "unknown_merchant" }.

  4. Free listing: GET /merchants (also /v1/merchants).

  5. Agent yellow pages: GET /x402/discover (alias GET /discover) — stable JSON catalog derived from the same registry / SELLER / demo. JSON config stays supported; there is no on-chain Registry.sol $5 stake in this release.

Note: The Base demo splitter has seller = feeCollector = operator — fine for demo. Real merchants need their own splitter with their wallet as seller.

CDP createX402Server uses a single global payTo for SDK init (X402_PAY_TO or the default merchant splitter). Per-request merchant routing rewrites PAYMENT-REQUIRED accepts[].payTo to the resolved FeeSplitter (same pattern as the https resource.url rewrite).

If X402_PAY_TO is still an EOA and you only have one merchant, behavior stays simple. Multi-merchant production should point each registry payTo at a deployed splitter — x402/exact credits that splitter via EIP-3009; call release() later (manually or via the optional keeper) to send 99.9% / 0.1%. Same contract on Base / Arbitrum / Polygon — see the multi-chain FeeSplitter matrix. This is receive → later release(), not an atomic same-transaction split and not OpenZeppelin PaymentSplitter.

Discovery (live) + liquidity roadmap (planned)

Ship now — Discovery only. Free route GET /x402/discover (alias /discover) returns agent-readable yellow pages from existing sources (MERCHANTS_JSON / merchants file / SELLER / built-in demo) + PUBLIC_BASE_URL. No 402. Example shape:

{
  "version": 1,
  "network": "eip155:8453",
  "updatedAt": "2026-09-03T00:00:00.000Z",
  "source": "merchants",
  "services": [{
    "id": "demo",
    "label": "demo (operator is also seller)",
    "endpoint": "https://your-host/v1/quote?merchant=demo",
    "mcp": "https://your-host/mcp",
    "capabilities": ["quote", "proxy", "fetch-md"],
    "price": "$0.001",
    "asset": "USDC",
    "payTo": "0x…",
    "seller": "0x…",
    "status": "demo"
  }]
}

status is health-honest: live only when CDP facilitator credentials + payTo are configured; otherwise demo (or config when accepts are non-live). This is not an on-chain Agent Registry.

Planned (documented only — do not treat as shipped):

Track

Why not now

Flash Liquidity Pool (0-confirm advance across chains)

Capital-intensive: a solo founder cannot hold multi-chain USDC float. 0-confirm advance = credit risk. Need a circuit-breaker when the hot wallet balance is insufficient. If Superchain / AggLayer gets sub-second native interoperability, cross-chain friction (S_{cross}) → 0 and this track may shrink.

Reverse Bounty (pay agents to call)

Sybil drain if the seller subsidizes calls. Require rate-limit / per-agent identity / IP+fingerprint gates before any claimBounty. The current ~$20 Discord/X bounty stays a manual operator payout — not an on-chain claim.

Full notes: docs/ROADMAP-LIQUIDITY.md. No depositBounty / claimBounty on FeeSplitter and no fake “live” cross-chain clearing claims in this release.

发现层已上线(GET /x402/discover);闪电流动性池与反向赏金仅为规划,见 roadmap。

Gas vs micropayment floor (optional)

Micropayments ($0.001–$0.01) assume Base low fees. When L2 gas spikes, estimated settle cost can eat most of a tiny payment. Opt in with X402_DYNAMIC_MIN_ENABLED=true: a cached Base baseFee oracle (TTL 15–60s, no RPC spam) estimates gas USD (gasUsedEstimate * baseFee * X402_ETH_USD) and, if that exceeds X402_GAS_COST_MAX_FRACTION of the payment (default 50%), bumps the displayed/enforced minimum accept amount and may raise feeFreeBelow so FeeSplitter+release() isn’t used until larger amounts. Default is OFF so demo $0.001 still works under normal conditions. Operators can also set a static X402_MIN_PRICE_USDC. Current effective mins are on GET /healthgasFloor.

Security (gateway hardening)

Four defenses buyers and sellers should know about:

  1. Payment signature replay / race — Idempotency key = SHA-256(PAYMENT-SIGNATURE). An in-process mutex + PaymentDedupeStore (memory today; Redis-shaped interface) does set-if-absent before upstream. Concurrent losers get 409 { "error": "payment_already_used" }. Durable mark happens only after settle success; verify failure releases the pending reservation so honest retries are not bricked.

  2. SSRF on /v1/fetch-md — Only http/https; DNS resolve rejects private/bogon/link-local/metadata; DNS is re-checked before fetch (rebinding defense); redirects are disabled.

  3. Upstream bypass — Optional UPSTREAM_SHARED_SECRET: after payment, proxy injects X-Tollgate-Secret / X-Tollgate-Paid / X-Tollgate-Timestamp. Your upstream must require them (see snippet below). This is a shared-secret MVP, not mTLS.

  4. Base congestion / settle latencyX402_SETTLE_TIMEOUT_MS (default 3 minutes) is separate from the short verify budget. If settle is still in progress when the waiter expires → 202 { "error": "payment_pending", "retry_with_same_proof": true }. Do not create a new payment and do not treat the buyer as failed solely because HTTP timed out — retry the same proof; the gateway resumes without double-settling.

CDP facilitator is a single-vendor settle dependency by default; see SECURITY.md for X402_FACILITATOR_URL (alternate facilitator when available — no multi-facilitator routing yet).

Payment proof idempotency + settle pending

CDP x402 exact + EIP-3009 authorizations are single-use at the facilitator (nonce). The gateway additionally tracks each proof fingerprint as pendingsettledconsumed:

Store state

Client sees

pending (settle in flight / after settle-wait timeout)

202 payment_pending + retry_with_same_proof: true

settled (paid; upstream not delivered yet)

Skip re-settle; proxy once; mark consumed

consumed

409 payment_already_used

Process-local only by default (restart clears). Facilitator remains authoritative for on-chain nonce uniqueness.

Upstream trust header (Express example)

import { createHmac, timingSafeEqual } from "node:crypto";
import type { RequestHandler } from "express";

const SECRET = process.env.UPSTREAM_SHARED_SECRET!;

export const requireTollgate: RequestHandler = (req, res, next) => {
  const secret = req.header("x-tollgate-secret") ?? "";
  const paid = req.header("x-tollgate-paid") ?? "";
  const ts = req.header("x-tollgate-timestamp") ?? "";
  const expected = createHmac("sha256", SECRET)
    .update(`${ts}.${req.method.toUpperCase()}.${req.path}`, "utf8")
    .digest("hex");
  const okSecret =
    secret.length === SECRET.length &&
    timingSafeEqual(Buffer.from(secret), Buffer.from(SECRET));
  const okPaid =
    paid.length === expected.length &&
    timingSafeEqual(Buffer.from(paid), Buffer.from(expected));
  if (!okSecret || !okPaid) {
    res.status(401).json({ error: "tollgate_required" });
    return;
  }
  next();
};

// app.use(requireTollgate); // reject public direct hits

Or import verifyUpstreamTrustHeaders from x402-micro-tollgate in your upstream process.

Optional FeeSplitter release keeper

Scaffold in src/keeper.ts. Off by default (KEEPER_ENABLED unset/false). When enabled, every KEEPER_INTERVAL_MS (default 1h) it checks USDC balances of registry payTo FeeSplitter addresses and calls release() when balance ≥ KEEPER_MIN_USDC (default $1).

Warning: Gas for release() can exceed the 0.1% operator fee on sub-cent payments — hence the min-balance gate. Do not turn this on by default on Render. Prefer KEEPER_DRY_RUN=true (logs keeper_would_release) before using a real KEEPER_PRIVATE_KEY. Never commit keys.


Bazaar discovery (agents find sellers)

Bazaar is the x402 discovery catalog.

  • HTTP: createX402Server auto-injects bazaar; we override with discoverable: true, descriptions, and input/output schemas (GET /v1/quote + gated prefix proxies).

  • MCP: x402ResourceServer does not auto-declare Bazaar — we register bazaarResourceServerExtension and pass declareDiscoveryExtension({ toolName, inputSchema, … }) on get_quote / proxy_request / fetch_md. Resource URL is PUBLIC_BASE_URL/mcp (a real http(s) URL, not a display name).

  • Origin well-known: GET /.well-known/x402.json (alias /.well-known/x402) mirrors discover + lists live paid HTTP/MCP resources for facilitator-independent crawlers.

To appear in Bazaar:

  1. Set PUBLIC_BASE_URL to a public https origin (localhost listings are a no-op for real crawlers).

  2. Run with live CDP credentials + X402_PAY_TO.

  3. Complete one successful settlement through the CDP facilitator (empty-body probes on gated routes return 402, not 400).

Launch tip: share your /mcp or /v1/quote URL in Discord #x402 after that first settlement.


Pay in browser (Smart Wallet)

When a browser hits a gated route (Accept: text/html + Mozilla UA), the gateway returns a thin HTML paywall instead of JSON. Agents / MCP / curl keep the JSON 402 body.

What you get

  1. Connect / create a Coinbase Smart Wallet via the official @x402/paywall EVM UI (Coinbase Wallet connector — Passkey Smart Wallet, no MetaMask required).

  2. Sign the x402 exact EIP-3009 payment on Base (or Base Sepolia in development) and retry with PAYMENT-SIGNATURE.

  3. Get USDC (optional): when server CDP keys are set, the paywall shows a Get USDC button that calls POST /x402/session-token and opens Coinbase Onramp. Apple Pay / card appear only if Onramp supports them for your domain — this repo does not fake Apple Pay outside Onramp, and does not claim a guaranteed “10 second Apple Pay” without Portal production access.

Setup (buyer UX)

# .env — server secrets stay on the host
CDP_API_KEY_ID=…
CDP_API_KEY_SECRET=…
X402_PAY_TO=0x…          # or SELLER=0x…

# Public client key only (browser-safe)
CDP_CLIENT_API_KEY=…     # from portal.cdp.coinbase.com

# Optional but recommended for live demo / Bazaar
PUBLIC_BASE_URL=https://your.host
X402_ENVIRONMENT=development   # Base Sepolia (eip155:84532)
# X402_ENVIRONMENT=production  # Base mainnet (eip155:8453)

Piece

Role

CDP_CLIENT_API_KEY

Injected into paywall HTML as window.x402.cdpClientKey

CDP_API_KEY_ID + CDP_API_KEY_SECRET

Facilitator settle and POST /x402/session-token (Onramp JWT)

POST /x402/session-token

Free path; body { "addresses": [{ "address": "0x…", "blockchains": ["base"] }], "assets": ["USDC"] }{ token }

Honesty / production notes

  • MVP networks: Base Sepolia (development) and Base mainnet (production / NETWORK=eip155:8453).

  • Onramp production: enable Onramp in CDP Portal, allowlist your domain, and complete any Apple Pay domain verification Coinbase requires. Until then, Smart Wallet pay + sign + retry still works; Get USDC may error or omit card rails.

  • CSP: the paywall ships inline scripts (same as upstream @x402/paywall). Prefer not to set a strict script-src without nonces on 402 HTML responses.

  • Security: wallet private keys never touch the frontend; only the public client key is embedded. Session tokens are minted server-side.

Try it: open https://your-host/v1/quote in Chrome after configuring the keys above.


What it does

Agents / clients
   ├─ HTTP  /v1/*          → x402 402 JSON (agents) or Smart Wallet HTML paywall (browsers)
   ├─ GET   /v1/fetch-md   → paid HTML→Markdown demo (same x402 gate)
   ├─ GET   /x402/discover → free agent yellow pages (alias /discover; from merchants JSON)
   ├─ GET   /.well-known/x402.json → free Bazaar-friendly origin manifest (alias /.well-known/x402)
   ├─ GET   /.well-known/agent-card.json → free agent card (alias /.well-known/agent.json)
   ├─ GET   /llms.txt      → free AI-crawler summary (alias /.well-known/llms.txt)
   ├─ GET   /openapi.yaml  → free OpenAPI 3.1 (alias /docs/openapi.yaml)
   ├─ POST  /x402/session-token → free Onramp session token (when CDP server keys set)
   ├─ GET   /merchants     → free merchant registry (id, label, seller, payTo)
   ├─ GET   /health        → free (+ paywall config flags)
   ├─ GET   /              → developer landing (EN / 中文)
   └─ MCP   /mcp           → server_info (free), get_quote + proxy_request + fetch_md (paid + bazaar)

Surface

Stack

HTTP

createX402Server + paymentMiddlewareFromHTTPServer + @x402/paywall (browser)

MCP

x402ResourceServer + createCdpFacilitatorClient + createPaymentWrapper + Bazaar extension

CLI: npx x402-micro-tollgate@0.3.3 | --seller 0x… / -s | --stdio | --port N

Agent SEO: llms.txt · docs/openapi.yaml


Publishing

Package identity (must stay in sync):

  • package.json mcpName = io.github.kevin2003050666-coder/x402-micro-tollgate

  • server.json name = same string

Flow (needs tokens on a machine that has them — not this VM):

  1. Public GitHub mirror at kevin2003050666-coder/x402-micro-tollgate

  2. npm publish (or tag v*.github/workflows/publish-mcp.yml)

  3. mcp-publisher publish (OIDC) → official MCP Registry

  4. PulseMCP auto-ingests from the official registry — do not submit to PulseMCP directly

Update server.json remotes[0].url to your real public /mcp before publishing remotes.


Tests

npm test

No live CDP credentials required.


License

MIT · See CONTRIBUTING.md · SECURITY.md

Available Tools

3 tools
get_quoteA

Fetch a sample quote from the upstream API (mock when UPSTREAM_URL is unset). Requires payment of $0.001 USDC.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the behavioral burden. It discloses the upstream dependency, the mock fallback, and the payment requirement—valuable traits beyond the tool name and schema. It does not cover error cases or response format, but for a zero-parameter tool this is still strong disclosure.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two compact sentences, front-loaded with the primary action and object, followed by the two key behavioral caveats. Every sentence earns its place with no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple zero-parameter tool with no output schema, the description covers the essential facts: what is fetched, where it comes from, mock behavior, and cost. It does not explicitly describe the response shape, but the simplicity of the tool and the 'sample quote' wording make the definition adequately complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, so the baseline is 4. There is no parameter meaning to clarify; the description appropriately focuses on behavior rather than parameter details.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: 'Fetch a sample quote from the upstream API.' It also adds the mocking nuance, which helps distinguish this from the sibling tools server_info and proxy_request. The name get_quote is reinforced rather than merely restated.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies this tool is used to retrieve a quote and mentions environment-dependent behavior (mock when UPSTREAM_URL is unset). However, it does not explicitly say when to use get_quote versus siblings or provide exclusion criteria. Usage context is present but not framed as alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

proxy_requestB

Forward method/path/query/headers/body to UPSTREAM_URL (or mock). Requires payment of $0.001 USDC.

ParametersJSON Schema
NameRequiredDescriptionDefault
bodyNoOptional JSON body for non-GET requests
pathYesUpstream path, e.g. /v1/quote
queryNoOptional query string parameters
methodNoHTTP method to forward (GET, POST, PUT, PATCH, DELETE, …)GET
headersNoOptional request headers (payment headers stripped)

TDQS

B3.4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries more burden, and it does disclose two important behaviors: it forwards an arbitrary request and it requires payment of $0.001 USDC. It still omits details like response formatting, timeout/error behavior, authentication, and whether the call has side effects on the upstream service, so it is only partially transparent.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

One sentence leads with the verb and resource, then adds the critical payment constraint. There is no filler, redundancy, or buried information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no output schema, no annotations, and a non-trivial five-parameter interface, the description leaves gaps: return value shape, payment mechanism, upstream URL resolution, and sibling-tool selection are all unaddressed. The description is a good summary but not a complete guide for an agent to invoke this tool confidently.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all five parameters. The description merely lists parameter groups (method/path/query/headers/body) without adding new detail about formats, constraints, or interactions, matching the baseline for high schema coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names a specific verb ('Forward') and resource ('method/path/query/headers/body to UPSTREAM_URL'), making the core operation clear. It does not explicitly contrast with sibling tools server_info or get_quote, but the generic proxy nature is distinct enough that an agent can tell it apart.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies this is the generic HTTP-forwarding tool and notes the payment requirement, which is a deciding factor. However, it never states when to choose this over server_info or get_quote, nor does it give any when-not-to-use guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

server_infoA

Gateway status: mode, network, price, gatedPrefix, upstream. No payment required.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A3.5/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description adds useful behavioral context by stating 'No payment required' and implying a read-only status endpoint through the word 'status.' However, with no annotations provided, it leaves uncertainty about side effects, authentication requirements beyond payment, and response format.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is extremely concise, front-loaded with the core purpose, and every piece of information (status fields and payment requirement) earns its place. No filler or redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-parameter status tool, the description covers the returned fields and a key behavioral requirement (no payment). It is mostly complete, though it could briefly mention that this is a read-only operation or how the output is structured.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, so the schema already fully covers inputs. The description appropriately focuses on outputs instead of parameters, which is sufficient for a parameterless tool.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly identifies the tool as returning gateway status and enumerates the available fields (mode, network, price, gatedPrefix, upstream). The verb is implied rather than explicit, but the resource and scope are clear and distinguish it from sibling tools like get_quote and proxy_request.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance is given about when to use this tool versus get_quote or proxy_request. The phrase 'No payment required' hints at a prerequisite, but there is no explicit usage context or mention of alternative tools.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 3 tool updatesv0.1.0
    • First observedget_quote
    • First observedproxy_request
    • First observedserver_info

TDQS

A4/5.0

Scored across 3 tools

Disambiguation5/5

Each tool serves a completely distinct function: server_info provides gateway status, get_quote fetches a sample quote, and proxy_request forwards requests. No overlap or ambiguity between them.

Naming Consistency5/5

All tools use lowercase snake_case with clear verb_noun or noun_verb patterns (server_info, get_quote, proxy_request). The naming style is uniform and predictable.

Tool Count5/5

With 3 tools, the set is well-scoped for a micro-tollgate gateway. Each tool earns its place: info, quote, and proxy access. No surplus or deficiency for the stated purpose.

Completeness5/5

The tool surface covers the core workflow: inspect gateway state, request a quote, and proxy an authenticated request. There are no obvious dead ends or missing operations for this narrow domain.

Maintenance

ActivityMaintained
ResponsivenessUnresponsive

Related MCP Connectors

Related MCP Servers