Shipping a $0.008-per-call AML API on Coinbase x402 Bazaar
On 2026-04-28, ChainAnalyzer cleared its first production mainnet settlement on Coinbase x402 Bazaar. An AI agent autonomously paid $0.008 USDC on Base, and in under 30 seconds got back a CRITICAL risk verdict with 64 detections on a Tornado Cash address — 76+ rules, ML anomaly scoring, Neo4j graph traversal. No account, no API key, fully settled on-chain.
Highlights
- Full x402 v2 spec compliance — verify + settle both running on real mainnet via EIP-3009
transferWithAuthorizationthrough the Coinbase CDP facilitator - Coinbase Bazaar auto-indexing — no submission form, no fee. The first successful settlement is the cataloging trigger
- Tornado Cash demo returned CRITICAL — 64 detections (Address Poisoning, Peel Chain, Fake Token URL Phishing, Sybil Aggregation, Token Relay)
- Nine chains — Bitcoin, Ethereum, Polygon, Arbitrum, Optimism, Base, Avalanche, Solana. Bitcoin coverage is unusual in this space
- No subscription needed — any AI agent that holds USDC can call the AML API in a single
fetch()
Why x402 actually matters for AI agents
Most API products assume a three-step onboarding: create an account, generate a key, set up a subscription. For AI agents that's a fatal source of friction — the agent wants to act autonomously, but it has to wait for a human to register and provision credentials before it can do anything.
x402 turns HTTP 402 (Payment Required) from a curiosity into a real protocol. The server returns payment requirements in the 402 response; the client signs USDC authorization, replays the request with the signature in an X-PAYMENT header; the Coinbase CDP facilitator verifies and settles on-chain; the server returns the actual response after settlement clears.
Net effect: any AI agent that holds a wallet can call ChainAnalyzer's AML primitives one request at a time, without a subscription contract or an API key. Coinbase Bazaar / Agentic.Market is the marketplace that catalogs every endpoint built this way and lets agents discover, compare, and call them.
Five things that broke (and how we fixed them)
The x402 v2 spec is new enough that rolling your own implementation has multiple failure modes. Sharing the ones we hit, in case they save someone a day.
1. The verify wire format is nothing like the legacy x402 examples
The first hand-written implementation POSTed {"payment": "<base64>", "requirements": {scheme, price}} to the facilitator. That has nothing to do with v2. The correct shape is:
{
"x402Version": 2,
"paymentPayload": {
"x402Version": 2,
"payload": { "signature": "0x...", "authorization": { ... } },
"accepted": {
"scheme": "exact",
"network": "eip155:8453",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"amount": "8000",
"payTo": "0xe8e26183...708959",
"maxTimeoutSeconds": 60,
"extra": { "name": "USD Coin", "version": "2" }
},
"resource": { "url": "...", "description": "...", "mimeType": "application/json" }
},
"paymentRequirements": {
"scheme": "exact",
"network": "eip155:8453",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"amount": "8000",
"payTo": "0xe8e26183...708959",
"maxTimeoutSeconds": 60
}
} The load-bearing field is paymentPayload.accepted — the client echoes back the exact requirement they signed for. Without it the facilitator can't bind the signature to a network / asset / payTo, which is the whole point of the protocol. Hand-rolling this is a trap; use the official x402 Python or TypeScript SDK and let the lib own the schema.
2. There was no settle call
Verify only checks "is this signature valid for this requirement." Actually moving USDC requires a second POST to /settle. The original implementation called verify, treated success as final, and returned the API response — meanwhile no USDC had moved on-chain. We replaced the whole pipeline with the official x402 Python lib's HTTPFacilitatorClient, then wrapped the middleware as verify → handler → settle.
3. The SvelteKit proxy was stripping X-PAYMENT
Calls to /api/v1/x402/... looped on 402 forever because the catch-all proxy in front of FastAPI only forwarded X-API-Key and Content-Type. X-PAYMENT and PAYMENT-SIGNATURE have to be forwarded explicitly, and the matching X-PAYMENT-RESPONSE exposed on the response side.
4. esm.sh transitive dep hell
Trying to wire up a browser demo via esm.sh, x402-fetch@1.2.0 internally imports @solana/rpc-parsed-types@5.5.1, and that subpath isn't published on esm.sh — it returns text/plain 404, which Firefox surfaces as "MIME type not allowed" (a CSP-flavored error message that sends you down the wrong rabbit hole). The fix that worked: drop x402-fetch entirely and sign EIP-3009 directly with viem's signTypedData_v4. About 50 lines.
5. Self-transfer (from == payTo) silently fails verification
The first end-to-end test connected Phantom to the same address used as the treasury payTo. The signed message was transferWithAuthorization(from=A, to=A, value=8000) — technically valid, semantically a no-op. The facilitator rejects it without a useful invalid_reason. Add a client-side from != payTo assertion before signing; it saves an hour of staring at HTTP 402 responses.
What it actually returned — Tornado Cash, 64 detections
For the end-to-end smoke test we hit the Tornado Cash 0.1 ETH pool address (0x0000db5c8b...e70000) and got back:
- risk_level:
CRITICAL - detection_count:
64 - ml_anomaly_score:
0.4381(3-model ensemble — Isolation Forest + AutoEncoder + GraphSAGE) - Top detector classes: 30+ Peel Chain (E8), 13 Fake Token URL Phishing (E16), Address Poisoning Received (E6), Sybil Aggregation (E12), Token Relay Chain (E15)
- scan_duration_ms: ~30s (running all heuristics + ML + Neo4j across 9 chains)
Settlement is on-chain: the receiving address chainanalyzer.eth (0xe8e26183...708959) shows the +0.008 USDC transfer on Basescan.
Try it
See the 402 (no payment, just the price tag)
curl -i https://chain-analyzer.com/api/v1/x402/address/0x0000db5c8b030ae20308ac975898e09741e70000/risk-score?chain=ethereum
# HTTP/2 402 Payment Required
# {
# "x402Version": 2,
# "accepts": [
# {
# "scheme": "exact",
# "network": "eip155:8453",
# "price": "$0.008",
# "payTo": "0xe8e26183...708959",
# "asset": {
# "address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
# "decimals": 6,
# "eip712": { "name": "USD Coin", "version": "2" }
# }
# },
# { "scheme": "exact", "network": "solana:5eykt4Us...", ... }
# ],
# "extensions": { "bazaar": { "discoverable": true, ... } }
# } Pay with Phantom in the browser (no code)
Open chain-analyzer.com/x402-demo.html, connect Phantom on Base, click "Pay $0.008 USDC and scan". EIP-3009 prompt fires, ~30 seconds later the risk-score JSON drops in. Solana works the same way.
Call from an AI agent (TypeScript)
import { createWalletClient, custom } from "viem"
import { base } from "viem/chains"
// Browser: connect Phantom (or any EIP-1193 wallet) to Base mainnet
const wallet = createWalletClient({
chain: base,
transport: custom(window.phantom.ethereum),
})
const [account] = await wallet.requestAddresses()
// Sign EIP-3009 transferWithAuthorization for $0.008 USDC, replay
// the request with X-PAYMENT — middleware verifies + settles via the
// Coinbase CDP facilitator, then returns the risk-score JSON
const res = await fetch(
"https://chain-analyzer.com/api/v1/x402/address/" +
"0x0000db5c8b030ae20308ac975898e09741e70000/risk-score?chain=ethereum",
{ headers: { "X-PAYMENT": buildPayment(account, /* ... */) } },
)
const score = await res.json()
// score.risk_level === "CRITICAL", score.detection_count === 64
// score.detections[i].detector_name === "ADDRESS_POISONING_RECEIVED" ... The full menu (six x402-native endpoints)
- GET /api/v1/x402/address/{address}/risk-score — $0.008 USDC. AML risk score + detections + ML anomaly. 8 chains live + BSC coming soon.
- GET /api/v1/x402/address/{address}/sanctions — $0.003. OFAC / FATF / JFSA / ScamDB cross-reference.
- GET /api/v1/x402/tx/{tx_hash}/trace — $0.015. Fund-flow tracing with ML anomaly detection.
- GET /api/v1/x402/tx/{tx_hash}/coinjoin — $0.01. CoinJoin / mixing / tumbling detection on Bitcoin.
- GET /api/v1/x402/wallet/{address}/cluster — $0.02. Neo4j graph clustering / entity resolution.
- POST /api/v1/x402/batch/screening — $0.05. Batch AML screening across up to 50 addresses.
The bigger picture
With this in place, an AI agent can run an AML check on a counterparty wallet, evaluate the result, and decide on-chain whether to send funds — entirely without human intervention. Compliance and fraud detection move from "a human looking at a dashboard" to "an agent verifying autonomously." That's a meaningful step.
As far as we can tell, ChainAnalyzer is the first production AML API to clear a real mainnet settlement under the x402 v2 spec on Coinbase Bazaar. Auto-indexing on agentic.market should land within 24-48 hours of the first settlement.
Links
- x402 Live Demo — settle directly from the browser via Phantom
- x402 Service Manifest (the JSON Bazaar reads)
- x402 Discovery Endpoint (all priced routes)
- x402 Protocol
- Coinbase CDP x402 Docs
- ChainAnalyzer MCP server (npm)