Server data from the Official MCP Registry
x402 HTTP 402 + MCP paywall: charge USDC per agent call. Self-host via npx or Docker.
About
x402 HTTP 402 + MCP paywall: charge USDC per agent call. Self-host via npx or Docker.
Security Report
Valid MCP server (1 strong, 0 medium validity signals). 1 known CVE in dependencies Package registry verified. Imported from the Official MCP Registry. Trust signals: trusted author (3/3 approved).
5 files analyzed · 2 issues found
Security scores are indicators to help you make informed decisions, not guarantees. Always review permissions before connecting any MCP server.
What You'll Need
Set these up before or after installing:
Environment variable: CDP_API_KEY_ID
Environment variable: CDP_API_KEY_SECRET
Environment variable: X402_PAY_TO
Environment variable: UPSTREAM_URL
Environment variable: PRICE
Environment variable: NETWORK
Environment variable: X402_ENVIRONMENT
How to Install
Add this to your MCP configuration file:
{
"mcpServers": {
"io-github-kevin2003050666-coder-x402-micro-tollgate": {
"env": {
"PRICE": "your-price-here",
"NETWORK": "your-network-here",
"X402_PAY_TO": "your-x402-pay-to-here",
"UPSTREAM_URL": "your-upstream-url-here",
"CDP_API_KEY_ID": "your-cdp-api-key-id-here",
"X402_ENVIRONMENT": "your-x402-environment-here",
"CDP_API_KEY_SECRET": "your-cdp-api-key-secret-here"
},
"args": [
"-y",
"x402-micro-tollgate"
],
"command": "npx"
}
}
}Documentation
View on GitHubFrom the project's GitHub README.
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.
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
- Price tag — unpaid traffic gets HTTP 402 with the amount due (conversion, not rejection)
- Pay — the agent settles a USDC micropayment in seconds via Coinbase CDP
- 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_max → Agent 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
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:
- Deploy
FeeSplitterwithseller= merchant wallet,feeCollector=0xa922F38041B5ee227c96A547F106F1330447e30E,feeBps= 10, and the chain’s native USDC asasset. - Add an entry to
MERCHANTS_JSON(ormerchants.json/ copy frommerchants.example.json):{ "acme": { "seller": "0xMerchantWallet…", "payTo": "0xDeployedFeeSplitter…", "label": "Acme API" } } - Call gated APIs with
?merchant=acmeor headerx-merchant-id: acme(case-insensitive). Agents SHOULD always send merchant id. If omitted, the gateway falls back toDEFAULT_MERCHANT(demo) — that means traffic (and USDC) can silently land on the demo FeeSplitter. SetREQUIRE_MERCHANT=trueto reject missing merchant with400 { "error": "merchant_required" }. Unknown merchant on gated paths →400{ "error": "unknown_merchant" }. - Free listing:
GET /merchants(also/v1/merchants). - Agent yellow pages:
GET /x402/discover(aliasGET /discover) — stable JSON catalog derived from the same registry /SELLER/ demo. JSON config stays supported; there is no on-chainRegistry.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 /health → gasFloor.
Security (gateway hardening)
Four defenses buyers and sellers should know about:
- 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 get409 { "error": "payment_already_used" }. Durable mark happens only after settle success; verify failure releases the pending reservation so honest retries are not bricked. - SSRF on
/v1/fetch-md— Onlyhttp/https; DNS resolve rejects private/bogon/link-local/metadata; DNS is re-checked before fetch (rebinding defense); redirects are disabled. - Upstream bypass — Optional
UPSTREAM_SHARED_SECRET: after payment, proxy injectsX-Tollgate-Secret/X-Tollgate-Paid/X-Tollgate-Timestamp. Your upstream must require them (see snippet below). This is a shared-secret MVP, not mTLS. - Base congestion / settle latency —
X402_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 pending → settled → consumed:
| 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:
createX402Serverauto-injects bazaar; we override withdiscoverable: true, descriptions, and input/output schemas (GET /v1/quote+ gated prefix proxies). - MCP:
x402ResourceServerdoes not auto-declare Bazaar — we registerbazaarResourceServerExtensionand passdeclareDiscoveryExtension({ toolName, inputSchema, … })onget_quote/proxy_request/fetch_md. Resource URL isPUBLIC_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:
- Set
PUBLIC_BASE_URLto a public https origin (localhost listings are a no-op for real crawlers). - Run with live CDP credentials +
X402_PAY_TO. - 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
- Connect / create a Coinbase Smart Wallet via the official
@x402/paywallEVM UI (Coinbase Wallet connector — Passkey Smart Wallet, no MetaMask required). - Sign the x402
exactEIP-3009 payment on Base (or Base Sepolia in development) and retry withPAYMENT-SIGNATURE. - Get USDC (optional): when server CDP keys are set, the paywall shows a Get USDC button that calls
POST /x402/session-tokenand 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 strictscript-srcwithout 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.jsonmcpName=io.github.kevin2003050666-coder/x402-micro-tollgateserver.jsonname= same string
Flow (needs tokens on a machine that has them — not this VM):
- Public GitHub mirror at
kevin2003050666-coder/x402-micro-tollgate npm publish(or tagv*→.github/workflows/publish-mcp.yml)mcp-publisher publish(OIDC) → official MCP Registry- 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
Reviews
No reviews yet
Be the first to review this server!
More Developer Tools MCP Servers
Paperclip
Freeby Paperclipai · Developer Tools
Trending hip-hop artist momentum scores across four cultural dimensions.
Git
Freeby Modelcontextprotocol · Developer Tools
Read, search, and manipulate Git repositories programmatically
Toleno
Freeby Toleno · Developer Tools
Toleno Network MCP Server — Manage your Toleno mining account with Claude AI using natural language.
mcp-creator-python
Freeby mcp-marketplace · Developer Tools
Create, build, and publish Python MCP servers to PyPI — conversationally.
MCP Marketplace
Freeby mcp-marketplace · Developer Tools
Search and install MCP servers from inside your AI client.
MarkItDown
Freeby Microsoft · Content & Media
Convert files (PDF, Word, Excel, images, audio) to Markdown for LLM consumption
