PolicyGuard has an optional x402 paywall. It is bypassed by default so teammates can develop without a wallet. Set X402_MODE=live locally to enforce payment.
Live mode uses the Coinbase Developer Platform (CDP) Facilitator and declares Bazaar metadata, so successful settlements can be indexed by Bazaar and agentic.market.
The demo proves the payment-gated HTTP flow works end to end:
- A client calls a protected route without payment.
- The middleware returns
402 Payment Requiredwith x402 payment requirements. - A wallet client signs and submits a Base Sepolia USDC payment.
- The client retries with an
X-PAYMENTheader. - The x402 facilitator verifies and settles the payment.
- If the settlement succeeds through CDP, the facilitator can index the route metadata for Bazaar / agentic.market.
- The route returns
200 OKwith the protected JSON response.
If the payer and receiver are the same address, this is a self-payment. That is useful as a smoke test, but it does not prove revenue from another user.
In live mode, middleware protects:
GET /api/paid-demo
POST /api/evaluate
POST /api/research
The route matcher is path-based, so GET /api/evaluate and GET /api/research are also behind the paywall in live mode. In mock mode, all routes bypass the paywall.
After payment, /api/paid-demo returns:
{
"ok": true,
"paid": true,
"service": "PolicyGuard paid demo",
"message": "x402 payment accepted; this response is behind the paywall.",
"payment": {
"asset": "USDC",
"network": "eip155:84532",
"price": "$0.001",
"payTo": "0x6B842e0F980EE89182e6aD0C4FFE36Df8D544a4a"
}
}The route is intentionally simple. It does not provide the real PolicyGuard verdict yet; it proves that access to a response can be unlocked by x402 payment.
Payment config lives in src/lib/x402-payment.ts.
Wallet, price, network, and facilitator are hard-coded:
payTo: 0x6B842e0F980EE89182e6aD0C4FFE36Df8D544a4a
price: $0.001
network: eip155:84532 / base-sepolia
facilitator: https://api.cdp.coinbase.com/platform/v2/x402
The only env flag is:
X402_MODE=mock
# X402_MODE=liveUnset or any value other than live behaves as mock and bypasses the paywall.
Live mode also requires CDP credentials:
CDP_API_KEY_ID=...
CDP_API_KEY_SECRET=...Get these from the Coinbase Developer Platform portal. Do not commit real values.
The route metadata is attached with the x402 Bazaar extension in src/lib/x402-payment.ts.
To get indexed:
- Run the app with
X402_MODE=liveand validCDP_API_KEY_ID/CDP_API_KEY_SECRET. - Serve the app from a public HTTPS URL.
- Make a successful x402 payment through the CDP Facilitator.
- Wait for Bazaar indexing; discovery results can take a few minutes to refresh.
There is no separate manual registration step. The successful CDP settlement is what triggers indexing.
Start the app in live mode:
X402_MODE=live npm run devThen call the route without payment:
curl -i http://localhost:3000/api/paid-demoExpected result:
HTTP/1.1 402 Payment Required
The body includes an accepts array with the price, network, receiver address, USDC asset address, and resource URL.
In default mock mode, the same request returns 200 OK because the paywall is bypassed.
The Coinbase payments MCP requires a public HTTPS URL, not localhost. For local testing, expose the dev server with a tunnel:
X402_MODE=live npm run dev
npx localtunnel --port 3000Make sure the shell running npm run dev also has CDP_API_KEY_ID and CDP_API_KEY_SECRET.
Discover the payment requirements:
x402_discover_payment_requirements
baseURL: https://your-tunnel.loca.lt
path: /api/paid-demo
method: GET
Make the paid request:
make_http_request_with_x402
baseURL: https://your-tunnel.loca.lt
path: /api/paid-demo
method: GET
preferredNetwork: base-sepolia
maxAmountPerRequest: 1000
maxAmountPerRequest is in USDC atomic units. 1000 means 0.001 USDC.
For Base Sepolia, open the transaction on BaseScan:
https://sepolia.basescan.org/tx/<transaction-hash>
Check the ERC-20 transfer:
Fromis the payer wallet.ToisX402_PAY_TO, the receiver wallet.- The token is USDC on Base Sepolia.
- The amount matches
X402_PRICE.
If From and To are the same address, you paid yourself. The useful proof is that the route only returned 200 OK after the x402 payment was verified.
The real product routes are already listed in src/lib/x402-payment.ts and src/middleware.ts. Keep X402_MODE=mock for normal team development, and use X402_MODE=live when you want to verify the x402 flow.