verify402-mcp
Provides pre-signing safety and on-chain truth checks for Ethereum and EVM networks. Validates x402 payment challenges and transaction intents, including chain ID, recipient, data, value, asset, amount, payer balance, and block freshness, before signing. Also uses Ethereum on-chain data to measure swap routes, depth, safety, and quote fairness via Uniswap V3 QuoterV2 and V2 router getAmountsOut.
Supports preflight validation of Solana x402 payment challenges, checking scheme, network, asset, amount, payTo, resource, timeout, and authorization window before signing.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@verify402-mcppreflight this x402 payment challenge before I sign it"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
verify402
Machine-payable on-chain truth & pre-signing checks for AI agents. Free fail-closed preflight gate + five verifiable truth checks paid per call via x402 in USDC. No API key, no account, no wallet required for the free gate.
Demo scope: the public demo settles payments in Base Sepolia testnet USDC. The analysis itself reads mainnet chain data (Ethereum, Arbitrum, Base). This is not a mainnet payment service, and no testnet payment is mainnet revenue.
30-Second Quickstart
1. Free Preflight Gate (No wallet, no key)
Verify an x402 payment challenge before signing it:
curl -sS -X POST https://verify402.onrender.com/preflight \
-H 'content-type: application/json' \
-d '{
"kind": "payment",
"payment": {
"scheme": "exact",
"network": "eip155:84532",
"asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"amount": "10000",
"payTo": "0xAeEF086DD4e436c69d6d0F684b4c9F43392df9cb",
"resource": "https://verify402.onrender.com/verify"
},
"policy": { "maxAmountUsd": 0.25, "allowedResourceHosts": ["verify402.onrender.com"] }
}'This example uses the public demo's Base Sepolia testnet USDC (eip155:84532), not mainnet. The free gate is only validating the request shape; it is not a payment. The paid analysis routes read mainnet chain data (Ethereum, Arbitrum, Base), but the demo does not settle mainnet payments.
Returns PASS / CAUTION / BLOCK, typed reasons, exact checked and notChecked lists, pinned block number, and a verifiable 90-second HMAC receipt.
A PASS means the listed checks passed at that block. It does not mean the
transaction is safe or cannot drain funds, and the paid token check is a
quote-based sellability check, not a honeypot proof. Read notChecked in the response.
Test the free gate (3 testers wanted)
I’m looking for three developers building AI agents, MCP tools, or x402 payment integrations to run 3–5 real intents and report what breaks. Demo payments use Base Sepolia testnet only; analysis reads mainnet chain data; no wallet or paid test is required.
Open the tester kit — three intents: one expected pass, one uncertain, one expected block
Run it in the browser — paste a token address, or run the raw intent; no terminal needed
Field notes — scope, receipts, and failure modes
The most useful finding is a false PASS: an intent the gate allowed that you believe is unsafe.
Related MCP server: chainverdict
🤖 Using verify402 with AI Agents (Step-by-Step)
The MCP server is live on npm as verify402-mcp and indexed on the official Model Context Protocol Registry (io.github.a32116150-ctrl/verify402-mcp).
It works with Claude Desktop, Cursor, Windsurf, Cline, OpenCode, and any MCP client over stdio.
Step 1: Add the MCP Server to Your Agent
Option A: Free Mode (Zero Config, No Wallet, No Key)
Gives your AI agent the free preflight_gate tool. It checks payment challenges, token approvals, payee/amount, and transaction calldata at a fresh block before signing. The paid check_token_safety check adds a quote-based sellability check; it does not execute a transfer and is not a honeypot guarantee.
Paste this into your client config file:
Claude Desktop:
~/Library/Application Support/Claude/claude_desktop_config.json(Mac) or%APPDATA%\Claude\claude_desktop_config.json(Windows)Cursor:
.cursor/mcp.jsonor Cursor Settings → MCPWindsurf:
~/.codeium/windsurf/mcp_config.jsonCline:
cline_mcp_settings.json
{
"mcpServers": {
"verify402": {
"command": "npx",
"args": ["-y", "verify402-mcp@latest"],
"env": {
"VERIFY402_URL": "https://verify402.onrender.com"
}
}
}
}Option B: Paid Mode (Unlocks 5 Deep Truth Checks)
Unlocks the 5 on-chain truth checks (verify_arbitrage, check_token_safety, get_best_route, verify_quote_fairness, get_trade_depth). The agent pays per call via x402 in testnet USDC on the public demo. The analysis still reads mainnet chain data; only the payment rail is testnet.
{
"mcpServers": {
"verify402": {
"command": "npx",
"args": ["-y", "verify402-mcp@latest"],
"env": {
"VERIFY402_URL": "https://verify402.onrender.com",
"EVM_PRIVATE_KEY": "0xYOUR_HOT_WALLET_PRIVATE_KEY",
"MAX_PAYMENT_USD": "0.25",
"MAX_DAILY_USD": "1.00",
"VERIFY402_NETWORKS": "eip155:84532",
"VERIFY402_PAY_TO": "0xAeEF086DD4e436c69d6d0F684b4c9F43392df9cb"
}
}
}
}VERIFY402_NETWORKS=eip155:84532 is the Base Sepolia testnet rail the live demo offers. Do not change it to eip155:8453 to make a test pass: the bridge would refuse the live 402 challenge rather than spend mainnet funds. Mainnet settlement is an explicit future deployment choice, not a demo default.
Step 2: How to Set Up the Agent's Hot Wallet
If you want your agent to use the paid truth tools, follow these 4 steps to set up its wallet safely:
Create a Dedicated Hot Wallet:
Generate a brand new EVM address using MetaMask, Rabby, or CLI (
cast wallet new).⚠️ NEVER USE YOUR MAIN WALLET OR COLD VAULT. Treat this wallet like physical petty cash.
Fund with testnet USDC on Base Sepolia (EIP-155:84532):
Send a small amount of Base Sepolia testnet USDC to your new hot wallet. Do not fund it with mainnet assets for the public demo.
The tools cost tiny fixed amounts per call:
$0.03(route),$0.05(arbitrage/quotecheck/depth),$0.10(token safety).
Fund with testnet Gas (Base Sepolia ETH):
Send a small amount of Base Sepolia testnet ETH for gas overhead if your client needs it.
Export the Private Key:
Copy the wallet's private key (hex string starting with
0x) into theEVM_PRIVATE_KEYfield in your MCP config.Client-Side Guardrails: The bridge enforces
MAX_PAYMENT_USD(hard cap per call, default $0.25) andMAX_DAILY_USD(daily budget cap) before any signature can be created. This caps what the bridge can authorize; it is not protection against a compromised private key.
Mainnet is explicit (not the demo default)
The public demo is testnet-only for payments. A future mainnet deployment must opt in deliberately, with a mainnet facilitator, an always-on paid host, and a mainnet-funded wallet:
# Only for a future mainnet deployment. Do not use these values against the public demo.
X402_MODE=mainnet
VERIFY402_NETWORKS=eip155:8453
VERIFY402_PAY_TO=0xAeEF086DD4e436c69d6d0F684b4c9F43392df9cbSetting VERIFY402_NETWORKS=eip155:8453 while pointing at the public demo is a configuration error: the live 402 offers eip155:84532, so the bridge fails closed instead of paying. See MAINNET_SWITCH_CHECKLIST.md before any mainnet deployment.
Step 3: Prompting the AI Agent to Use the Tools
Once configured, restart your AI client. The agent automatically has access to the tools. You can prompt it naturally:
What you want to do | Example Prompt to your AI Agent | Tool Called | Cost |
Verify a transaction before signing | "Before signing, run |
| Free |
Check measured token safety / sellability | "Run |
| $0.10 |
Verify an arbitrage opportunity | "Use |
| $0.05 |
Audit a swap quote | "I was quoted 0.35 WETH for 1,000 USDC. Call |
| $0.05 |
Check market depth & slippage | "Check |
| $0.05 |
Find true on-chain swap route | "Query |
| $0.03 |
Step 4: Install the preflight skill
The free agent skill lives at skills/verify402-preflight. It is the operating manual for the gate: exact payment and transaction payloads, how to read PASS / CAUTION / BLOCK, how to verify the receipt before an executor signs, and when the five paid checks are worth a call. It is built around the MCP server above; the references document the non-MCP fallback.
The skill is explicit about the limits: a PASS is not a guarantee, the token check is not a honeypot proof, and no result is a profit forecast.
Machine-Readable Discovery
Service Manifest:
/.well-known/verify402.json(Documentation)OpenAPI 3.1 Specification:
/openapi.jsonMCP Registry Manifest:
server.json(Publishing Guide)x402 Bazaar Discovery:
docs/x402-discovery.mdPlain Text Contract:
/llms.txtDiscovery Audit:
docs/discovery-audit.mdDirectory Submission Checklist:
docs/distribution-directory-checklist.md
Human Pages: Home · Start · Tester Kit · Blog · Preflight Gate · MCP Server · Checks · Live Findings · Proof & Receipts · Docs · Donate
Why an agent would pay for this
Models hallucinate profit. Most arb "opportunities" an LLM surfaces are stale, ignore fees, or come from a quote that was never checked against the venue that would execute the trade. This service exists to be the check an agent cannot do itself.
The measurement discipline
Quotes come from the canonical on-chain sources — never our own reimplementation of venue math:
V3: Uniswap's deployed
QuoterV2(its answers are the router's answers by construction)V2: each router's own
getAmountsOut
Both legs are measured against one block number, so the round trip reflects a single instant rather than two prices that never coexisted.
Three bugs were caught by the test suite before this ever served a paid response, and they are the reason the tests exist:
Invented profit (the big one).
decodeFunctionResultongetAmountsOut— a single declared output, which viem unwraps — was indexed withdecoded[0], silently grabbing the array's first element: the input amount. On the sell leg that turned 41 WETH into 41 trillion USDC of "profit". Now the decoder finds theuint256[]structurally and refuses a result equal to the input.Basis points off by 100x. −292 USDC on a 100,000 USDC round trip rendered as −0.29 bps instead of −29. A rounding trick multiplying by
10_000ninstead of1_000_000nwhen keeping two decimals of precision.Venue that doesn't exist still gets reported. Started as an unasserted assumption;
all configured venues exist on-chainis now a test.
node test/measure.test.mjs # 5 checks, needs a mainnet RPCOne check is the headline property: a 100k USDC round trip on Ethereum must NOT come out profitable. If it ever does, the engine is wrong until proven otherwise.
Run it
npm install
npm test # full suite (RPC checks need network)
node src/server.mjs # testnet payment mode
BYPASS_PAYMENT=1 node src/server.mjs # local dev only - loud warning, no billing
X402_MODE=mainnet FACILITATOR_URL=... AGENT_URL=https://verify402.example node src/server.mjsMainnet refuses to start without AGENT_URL: the agent card is a public
identity document and must not derive its origin from the request's Host
header. In testnet/local mode the card falls back to a Host allowlist
(TRUSTED_HOSTS, plus localhost / 127.0.0.1); an unlisted Host gets
400 untrusted_host instead of being reflected.
Endpoints
Route | Price | What it does |
| free | pre-signing gate: validate a payment request or transaction, return PASS / CAUTION / BLOCK + a short-lived receipt |
| free | verify a preflight receipt (HMAC, expiry, optional intent + request bindings) |
| $0.05 (x402) | the measurement, with verdict + evidence |
| $0.10 (x402) | pre-trade token check: TRUST / CAUTION / AVOID + evidence; not a code audit |
| $0.03 (x402) | what would the chain ACTUALLY pay for this swap? best route + every quoted venue |
| $0.05 (x402) | audit a quote someone else gave you: fair / overpaying / too_good_to_be_true |
| $0.05 (x402) | position sizing: how big can I trade before the round trip eats me? |
| free | live no_edge verdicts on Ethereum majors |
| free | machine-readable service description |
| free | liveness |
POST /depth is the pre-trade sizing question nothing on-chain answers.
Pool-reserve "liquidity: $2.7M" tells an agent nothing about the block it
will actually trade in. The depth engine buys the target with the best route
at each rung of a USD ladder, immediately sells the exact position back, and
reports per-rung impact %, round-trip cost %, and capacityUsd — the largest
size that stays under the caller's cost tolerance. Live example (SHIB,
2.5% tolerance): "$2.7M liquidity" resolves to a real capacity of $1,000
($100 → 1.20% round trip, $10k → 6.57%). The ladder is the honest answer.
The preflight gate
POST /preflight is the free control an agent runs immediately before signing.
It accepts one of two intents:
payment — an x402 402 challenge (EVM or Solana): scheme, CAIP-2 network, asset, raw amount,
payTo, resource, timeout, authorization window.transaction —
chainId,to,data,value, optionalfrom(EVM: Ethereum 1, Arbitrum 42161, Base 8453).
Response: PASS / CAUTION / BLOCK, reasons[], checked[],
notChecked[], and a receipt:
{
"verdict": "CAUTION",
"sign_allowed": true,
"requires_ack": true,
"reasons": [{ "code": "unlimited_approval", "severity": "caution", "message": "..." }],
"checked": ["scheme", "network", "asset", "amount", "payee", "resource_url", "resource_host", "payer_balance", "block_freshness"],
"notChecked": ["recipient reputation", "sanctions screening", "payment signature / signed payload", "facilitator settlement", "off-chain promises", "counterparty identity / tool metadata", "payTo ownership / proven seller payout wallet", "obligation state (idempotency key, cumulative spend, grant balance)", "prompt injection / hidden Unicode in tool output"],
"receipt": {
"spec": "verify402/preflight-receipt@1",
"intentHash": "…",
"requestHash": "…",
"blockNumber": 48277717,
"blockHash": "0x…",
"expiresAt": "2026-10-19T…Z",
"signature": "…"
}
}The receipt is HMAC-SHA256 signed, bound to the canonical normalized intent, the exact request body the gate received, a fresh pinned block where the chain is readable, and an expiry (default 90s, max 300s).
POST /preflight/verifyrecomputes the signature and, when supplied, the caller'sintentandrequesthashes. Passrequestto bind the receipt to the original fields without reimplementing normalization.requestHashis an additive field withinverify402/preflight-receipt@1; receipts issued before it existed remain signature-valid but are not caller-bound, and verify withmissing_request_hashwhenrequestis supplied. A receipt with nointentHashreturnsmissing_intent_hash. Receipt-only verification stays compatible but reportsintentBound:falseandrequestBound:false; the reference executor requires both bindings.Fresh block, not just any block: on EVM chains, a latest block older than 30s is
CAUTION; older than 120s isBLOCK. The response carriesblock.timestampandblock.ageSeconds. A block with no timestamp is a503, not a PASS.Fail-closed: if the chain cannot be read, the endpoint returns
503and no receipt. An executor must refuse to sign without a verifiable receipt.Optional caller-supplied state: the gate has no cross-process payment ledger, so it can only check obligation state the caller supplies:
payment.idempotencyKeypluspolicy.obligation.settledKeyscatches a re-presented key (obligation_already_settled).policy.obligation.spentUsdplusgrantUsdcatches cumulative spend over the grant (cumulative_spend_over_grant).policy.sellerPayoutWalletsis a caller-supplied allowlist, not provenance. A match addspayee_payout_allowlist; a mismatch ispayee_not_payout_wallet. It never addspayee_payout_binding.policy.sellerPayoutManifestfetches a seller-signed manifest from a caller-pinned HTTPS origin outside the 402. Only a verified manifest whosepayTolist contains the payment payee addspayee_payout_binding. The response includespayeeBindingwithsource,origin,signer,expiresAt, andmanifestHash. A manifest that is missing, expired, invalid, for another chain, or signed by the wrong key is CAUTION/BLOCK. These are caller bindings, not a server-side ledger and not independent proof that the seller controls the wallet. Without them, the response says so innotChecked.
Seller manifest shape (
verify402/seller-payout@1):
{
"spec": "verify402/seller-payout@1",
"seller": "seller.example",
"chainId": 8453,
"payTo": ["0xSellerPayout..."],
"issuedAt": "2026-10-24T00:00:00Z",
"expiresAt": "2026-11-24T00:00:00Z",
"signer": "0xSellerSigner...",
"signature": "0x..."
} The signature is EIP-191 personal_sign over canonicalStable of the
manifest without signature (keys sorted recursively, no whitespace). The
URL must be HTTPS and public; credentials, localhost, private hosts, private
DNS resolutions, and redirects are rejected. Pin expectedSigner when the
seller key is known. The gate cannot tell whether the caller copied the
manifest URL from the 402; the URL must come from the caller's own seller
configuration, and that remaining trust assumption is named in notChecked.
Not checked, on purpose: recipient reputation, sanctions, contract audits, full balance-delta simulation, MEV, the payment signature/signed payload, gas price/limit chosen by the signer, raw signed-transaction binding, facilitator settlement, counterparty identity/tool metadata, and prompt injection or hidden Unicode in tool output. They are named in every response instead of being implied. A PASS is not a counterparty audit.
Free is rate-limited (60/min per IP, 8 concurrent) so it cannot become a public RPC proxy.
GET /usageis an anonymous, process-local call counter. The MCP bridge sendsX-Verify402-Client: verify402-mcp/<version>so its calls can be separated from generic HTTP probes. The label is self-declared, spoofable, and never an identity;/usagealso reports whether each label came from the header or the User-Agent. The number of distinct labels is capped so a caller cannot grow the counter without bound. It resets on restart or deploy.
The MCP server exposes it as preflight_gate. It runs with no wallet (free gate
only); add EVM_PRIVATE_KEY for the five paid tools. If sign_allowed is
false, do not sign; if requires_ack is true, surface the reasons to the
operator. Client configs and the install command are at
/mcp.
Reference executor
examples/preflight-guard.mjs is a small repo-local, deny-by-default wrapper
(its only import is verify402's own canonical request-hash helper):
it calls the gate, refuses BLOCK, requires an explicit acknowledgement for
CAUTION, verifies the receipt, and only then calls your signer. It holds
no key and sends nothing itself. The intent is cloned and deep-frozen before the
gate, and that same object is passed to the signer. If you do not want to trust
the public instance, run your own and pin baseUrl.
For x402 clients, createX402PreflightHook() is the automatic version. Add one
line to onBeforePaymentCreation; it maps the selected requirement to a payment
intent, blocks on BLOCK, refuses unacknowledged CAUTION, verifies the
receipt, and attaches it to context.verify402 before the SDK can sign. See
examples/README.md.
POST /verify request:
{ "chainId": 1, "target": "WETH", "base": "USDC", "size": 100000, "minNet": 0 }Response (abridged):
{
"verdict": "no_edge",
"blockNumber": 26214368,
"buy": { "venue": "uni-v3-0.05%", "amountOutHuman": 41.366 },
"sell": { "venue": "uni-v3-0.05%", "amountOutHuman": 99707.9 },
"gross": { "human": -292.08, "bps": -29.23 },
"net": { "human": -342.14, "bps": -34.23 },
"costs": { "flashLoanPremiumBps": 5, "gasUsd": 0.0561 },
"venues": { "quoted": 12, "buySide": [...], "sellSide": [...], "unavailable": [...] },
"caveats": ["..."]
}verdict is one of edge (net cleared minNet), no_edge_costs_ate_it (gross
positive, costs larger), or no_edge (unprofitable before costs).
POST /safety request (accepts any ERC-20 address, not just registry symbols):
{ "chainId": 1, "tokenAddress": "0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE", "intendedUsd": 500 }Response (abridged):
{
"verdict": "TRUST",
"quote_status": "ok",
"checked": ["contract", "market", "quote-based sellability", "liquidity"],
"notChecked": ["actual token transfer or sell execution", "transfer tax / fee-on-transfer",
"blacklist / pause / max-tx restrictions", "venues not in config"],
"notes": ["quoteable sell via uni-v3-0.3% via hop, uniswap-v2 via hop, ...",
"round trip in and out of $500 costs 1.42%"],
"evidence": { "contract": {...}, "market": {...},
"sellability": { "canSell": true, "probedInUsd": 25,
"caveat": "quote-based only: a quote does not prove a transfer will succeed" },
"liquidity": { "roundTripCostPct": 1.42 } },
"elapsedMs": 1434
}verdict is one of:
AVOID— positively dangerous: no configured venue can quote an exit at a realistic size, or the contract is not a token at allCAUTION— tradable, but with a stated cost or problem (thin pool, manufactured price, expensive round trip)TRUST— every check that ran passed; still not a promise of profit, only a record that nothing measured was wrong
This is a quote-based sellability check. It asks the configured venues'
own routers for a price; it does not execute or simulate a transfer, so
transfer tax, blacklist, pause, and max-transaction restrictions are listed in
notChecked, not silently assumed away. Venues that are not configured on the
chain are also named in notChecked.
Probes are USD-sized on purpose: an earlier version probed "half a token" and flagged SHIB — a $1B token — as unexitable, because half a SHIB is dust no pool can quote. The test suite and live calls caught that before it launched; every future verdict change must be explainable, not silent.
A request without payment gets 402Payment Required with instructions in the
PAYMENT-REQUIRED header; the client pays and retries with proof — that is the
entire x402 flow, and it is what makes the caller a program rather than a person.
Deploying
See DEPLOY.md — free-tier container deployment, ERC-8004 identity mint
(npm run register, owner's key, on-chain data URI), mainnet facilitator
wiring, and the distribution order.
MCP: how AI agents consume this
mcp-bridge/ is packaged as verify402-mcp (stdio). It exposes the free
pre-signing gate (preflight_gate) and the five paid endpoints
(verify_arbitrage, check_token_safety, get_best_route,
verify_quote_fairness, get_trade_depth) to Claude Desktop, Cursor, OpenCode,
or any MCP client.
Free gate only: omit
EVM_PRIVATE_KEY; the server registers onlypreflight_gateand nothing can be charged.Paid tools: add a dedicated hot wallet with testnet USDC for the public demo (mainnet only in an explicit deployment). The bridge signs the x402 payment only — never a trade — and enforces
MAX_PAYMENT_USD(default $0.25) in the pre-signature guard before any signature exists. It also requires an allowlisted EVM network, the verify402 payee, and a known paid resource on the configured server, and refuses redirects.MAX_DAILY_USDis optional.New in
1.0.9: the bridge no longer depends on the@x402/axiosor@x402/evmSDK. The x402 v2 payer is implemented locally onviem(mcp-bridge/x402-client.mjs), so a clean install reports 0 audit vulnerabilities instead of the nested-zod advisory carried by the SDK. The wire protocol, spend guard, and paid-tool behavior are unchanged.1.0.8added: payout provenance is no longer inferred from a caller-supplied wallet list.policy.sellerPayoutWalletsis an allowlist; only a verified, seller-signedpolicy.sellerPayoutManifestaddspayee_payout_binding. It also addscreateX402PreflightHook()/guardX402Payment()to the example executor, and sendsX-Verify402-Client: verify402-mcp/<version>so/usagecan separate MCP bridge calls from generic HTTP. Labels are self-declared and spoofable, not identities.Install:
npx -y verify402-mcp@latestfrom the public npm registry (currently1.0.9). For a pinned install of the published package, usenpx -y verify402-mcp@1.0.9; its npm tarball ishttps://registry.npmjs.org/verify402-mcp/-/verify402-mcp-1.0.9.tgz. No npm account is required to run the published package. Client configs, the exact OpenCode V2 shape, and the release command are at/mcp. The published1.0.9defaults paid calls to Base Sepolia testnet (eip155:84532) for the public demo; Base mainnet (eip155:8453) requires an explicitVERIFY402_NETWORKSopt-in in a separate deployment and is not enabled by this release. The immutable1.0.8and1.0.7packages remain published; the older1.0.6package remains installable, and1.0.4predates the testnet default and used Base mainnet. The official MCP Registry lists1.0.9as active (isLatest: true).
Settlement, response delivery, and recovery (honest contract)
Recovery state (read this before relying on it): with
RECOVERY_DB_PATHset, the recovery store is a write-ahead SQLite file — a staged payment is journaled to disk before the facilitator settlement call — so a settled verdict and its replay counter survive a restart, a redeploy, or a SIGKILL, and a caller retrying after a lost response is not charged again. Without the variable the service keeps the historical memory-only behavior: a restart loses the records, and a previously settled key may be charged again./usagecounters are process-local in both modes. The store is single-instance by construction: one server process per database file (seeDEPLOY.md§3.1).
Delivery: the verdict is returned inside the settlement response — the paid answer arrives together with the payment confirmation (testnet receipt: tx
0x11121007bafec47f132763f42e2f58af6ebba7fba9c7ed1b5980d3f595a9324a).Recovery — implemented (
x-idempotency-key): send the header on the FIRST call of an operation, minting ONE key per operation. If the response is lost after settlement, retrying with the same key returns the SAME verdict without requiring payment again: 10 minutes (max 3 replays). Caveats stated plainly: the key is the caller's secret (whoever presents it replays for free), the window is short by design so the 402 paywall stays intact, and replayed responses carryrecovery: { replayed: true, responseHash, replays }.Key strength (required): use
crypto.randomUUID()or at least 32 URL-safe characters (A-Z,a-z,0-9,_,-) with at least 16 distinct characters. Short, repeated, sequential, or low-entropy keys are rejected with400 weak_idempotency_keyand guidance to generate a key. Requests without a key keep working exactly as before; they simply do not get recovery.The key is a secret. A leaked key allows anyone to replay the settled verdict until the 10-minute window closes or the 3-replay cap is reached. Do not put it in logs, URLs, screenshots, or public issue reports. If it leaks, use a new key for the next operation; the old key cannot be revoked except by the short TTL/cap.
What recovery does not guarantee: it does not prove payment, refund a failed call, replay after the TTL or cap, or recover a response if the caller lost the key. Durability (with
RECOVERY_DB_PATH) covers the verdict and its replay budget only — not/usagecounters — and it is not a cross-instance ledger: one database file, one server process. Without the variable, recovery does not survive a restart at all.Concurrent retries: the first same-key request reserves the key before the paywall. A second request gets a typed
409 recovery_conflictwithcode: operation_in_flight, orsettlement_pendingwhile a staged settlement is unresolved. Without a durable store the reservation is in-process and lost on restart; withRECOVERY_DB_PATHthe staged row survives the restart and stays unreplayable until reconciliation resolves it (on-chainauthorizationState(payer, nonce)check — seeDEPLOY.md§3.1). If the store cannot journal a payment, the paid route answers503 recovery_unavailableinstead of settling unrecorded;/healthreports the store asok,unavailable, ormemory-only.Settlement-gated (fail-closed): a verdict record only becomes replayable after the facilitator confirms the settlement (official x402
onAfterSettlehook). Promotion is keyed on a server-generatedoperationIdcarried in the response (recovery.operationId, excluded from the projection) and requires a confirmed settle result with a non-empty transaction reference; the projection hash of the served bytes is an additional check. Equal output does not identify an operation — two requests that both resolve to{"verdict":"no_match"}are still distinct, and settling one never promotes the other. A failed, aborted or pending settlement promotes nothing. The replay cap (3) is per operation, bound to the key.The key is the only credential. The service does not derive a payer identity from
x-payment,payment-signature, orpayload.authorization.from; those headers are unverified at the recovery layer. The facilitator-confirmed payer is stored asverified_payerfor audit only. A forgedauthorization.fromcannot replay anything.No companion alias. An earlier design also recorded a verdict under a hash of the canonical request content. That made a settled verdict readable by anyone who could reproduce the body, so it was removed. A secret token returned only in the original response cannot help a lost-response retry; the original key is the only secret that survives, so it is the only one accepted. Reusing a key with changed request content returns
409 recovery_conflict(conflict: "request_mismatch") before the paywall — never the other operation's verdict, never a charge.After the window (typed, not silent): expired verdicts leave a tombstone carrying the previous
responseHashfor 6 hours. A retry within the tombstone window re-measures with the paywall re-armed (there is no free measure after the window — anything else would be a loophole), but the response carriesrecovery: { remeasured: true, previousResponseHash, expiredAt }, so the calling agent can branch on "this operation was charged before and could not be replayed" instead of guessing.Regression controls:
test/recovery.test.mjscovers forgedauthorization.from, a different key with a known body, the victim's replay budget under forged traffic, and legitimate replay. Two staged operations with byte-identical output do not cross-promote; promotion is refused on failed/pending settle, a missing/empty transaction, or a missingoperationId.test/recovery-durable.test.mjsdoes the same across fresh processes and real SQLite files: SIGKILL after staging, a corrupt database, a locked database, restart reconciliation of a staged record, and the mainnet start guard that refuses to boot withoutRECOVERY_DB_PATH.
Evidence bundle — implemented (
verify402/evidence-bundle@1): every paid response carriesevidence_bundle: { requestHash, responseHash, pinnedBlock, mode, createdAt, payment.in }. Both hashes are sha256 over canonical JSON (keys sorted recursively, no whitespace).requestHashcovers{ route, body };responseHashcovers the response body excludingevidence_bundle,recovery,cache,elapsedMs, andoperationId. Reference implementation of the canonical form:src/recovery.mjs(canonicalStable), tests intest/recovery.test.mjs.Payment caps (bridge/client):
MAX_PAYMENT_USDcaps one payment at signature time;MAX_DAILY_USD(optional,mcp-bridge/budget.mjs) caps the whole UTC day across restarts via a local ledger. Attempted charges are counted before the payment is built — a spending guard should over-count, never under-count.
Errors, honesty, chainId, and readiness
Honesty contract: every
200and every typed failure on/verify,/safety,/route,/quotecheck, and/depthincludes non-emptychecked[]andnotChecked[].notCheckednames the real limits (unconfigured venues, transfer restrictions, execution, MEV) rather than implying coverage that doesn't exist.Typed quote outcomes:
quote_statusisok | partial | no_pool | failed.transportanddecodefailures returnok:falsewith HTTP503and never becomeAVOID,no_edge,unknown, orilliquid. Only a genuineno_poolor venuerevertmay make a leg unavailable.No settlement on failure: the installed
@x402/expressmiddleware cancels settlement for any response>= 400. A503(transport/decode/RPC) is not charged.Errors never leak: responses carry a stable public code (
bad_request,rpc_unavailable,rpc_rate_limited,internal_error, …) plus arequestId. Provider messages, RPC URLs, and API keys stay in the server log under the same id.chainId is required: every paid route and every transaction preflight requires an integer
chainId. Missing =>400 missing_chain; wrong type =>400 invalid_chain; unsupported =>400 unsupported_chain. There is no silent Ethereum default.Readiness:
/healthis liveness;/readyis a deep check that reads the latest block with a 3-second timeout.Security headers: every response sets
X-Content-Type-Options: nosniff,Referrer-Policy: strict-origin-when-cross-origin,X-Frame-Options: DENY, and a Content-Security-Policy written for the static site (self-hosted scripts, Google Fonts, no framing).x-powered-byis disabled.CORS: free read-only discovery (
GET/HEAD/OPTIONSon/info,/health,/ready,/openapi.json,/llms.txt, and/.well-known/*) allows any origin.POST /preflightand paid routes send noAccess-Control-Allow-Origin; browser-based agents must use a server-side client, the MCP bridge, or an SDK.Shutdown:
SIGTERM/SIGINTstop new connections immediately, drain in-flight requests for up toSHUTDOWN_TIMEOUT_MS(default 10000), then exit0. A drain timeout force-closes connections and exits1so a stuck deploy is visible.
First paid call, in ten lines
GET https://verify402.onrender.com/info— prices, endpoints, recovery contract.GET https://verify402.onrender.com/findings— free verdicts on majors; see the honestno_edgebaseline.POST /verifywith your question ({"chainId":1,"target":"WETH","size":100000}) and no payment → HTTP 402.Read the payment instructions from the
PAYMENT-REQUIREDresponse header.Build an x402 payment for ≤ the stated price (Node example:
mcp-bridge/first-payment.mjs; MCP users:mcp-bridge/). On the public demo, pay the testnet challenge (eip155:84532); do not substitute a mainnet payment.Re-send the same request with the proof in the
PAYMENT-SIGNATUREheader (x402 v2; the legacyx-paymentheader is also accepted). The verdict arrives with the settlement response.On a retry/restart, add
x-idempotency-key: <crypto.randomUUID()>to step 3 so lost responses replay free instead of paying twice. The key is a secret; a leaked key allows replay until the TTL/cap.Verify the receipt:
evidence_bundle.responseHashmust equal sha256(canonical JSON of the body minusevidence_bundle,recovery,cache,elapsedMs).Cap your own wallet:
MAX_PAYMENT_USD(per call) andMAX_DAILY_USD(per day) in the bridge.env.Repeat the call in the same block and compare
evidence_bundle.responseHash— determinism of the measurement. Anedgeverdict should be raced, not trusted.
Honesty
No profit is promised. The measured answer for mainstream pairs is almost always
no_edge— that is the market's fee boundary, not a failure of the tool.The service reports what it measured, not what it guarantees.
Not financial advice.
Hosting status
Demo instance runs on a free host: first request after idle can take about a minute; testnet only.
License
Apache License 2.0. See LICENSE and NOTICE.
You may use, modify, and distribute this software, including in commercial agent
systems and bots. If you redistribute it or a product containing it, keep the
copyright notice, the license text, and the NOTICE file. The verify402 name
and logo are not licensed for endorsement.
This server cannot be deployed
Maintenance
Related MCP Connectors
Pre-trade token safety checks for AI agents on Solana and Base. x402 USDC per call, no key.
Free token-safety scans + paid x402 verdicts, signals, radar & EVM swap quotes for AI agents.
Free token-safety scans + paid x402 verdicts, signals, radar & EVM swap quotes for AI agents.
Live uptime and source-URL verification for AI agents, with per-call x402/USDC paid endpoints.
Related MCP Servers
- AlicenseAqualityCmaintenanceTrust layer for AI agents: 22 pay-per-call x402 tools for scam and fraud checks, payment pre-flight, wallet screening, policy approvals, contact extraction and company research. Free preview with no wallet; paid calls settle in USDC on Base.22104 npmMIT
- AlicenseNot gradedqualityBmaintenancePay-per-call checks an AI agent runs before it moves money: token safety verdicts and wallet risk profiles on Base, on-chain payment verification, IBAN/VAT/BIC/LEI/ISIN validation, and live TLS and email-spoofing posture for a domain. Paid in USDC over x402 with no API key or account; the free payment_info tool explains the pricing.MIT
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to obtain an independent review of their drafts, returning a pass/fail verdict with specific issues and suggested fixes, plus a signed receipt. Payments are made per check over x402, with no account or API key needed.MIT
- AlicenseNot gradedqualityCmaintenanceEnables AI assistants to call pay-per-call x402 endpoints on Base mainnet using the caller's own USDC wallet, covering single and batch token risk checks, the latest BTC radar signal, project info, and a free health probe. Payments are EIP-3009 signed with a configurable per-call spending cap, and any quote with an unexpected payee, network, or asset is refused before payment.MIT