Skip to main content
Glama

verify402

Machine-payable on-chain truth & pre-signing safety 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.

OpenAPI 3.1 MCP Server x402 v2 License: Apache-2.0


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:8453",
      "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "amount": "10000",
      "payTo": "0xAeEF086DD4e436c69d6d0F684b4c9F43392df9cb",
      "resource": "https://verify402.onrender.com/verify"
    },
    "policy": { "maxAmountUsd": 0.25 }
  }'

Returns PASS / CAUTION / BLOCK, typed reasons, exact checked and notChecked lists, pinned block number, and a verifiable 90-second HMAC receipt.

2. Connect via MCP (Claude Desktop, Cursor, AI Agents)

Run the MCP server directly via npx (stdio transport):

npx -y --package=https://github.com/a32116150-ctrl/verify402/releases/download/mcp-v1.0.2/verify402-mcp-1.0.2.tgz verify402-mcp

Add to Cursor or Claude Desktop (mcp.json):

{
  "mcpServers": {
    "verify402": {
      "command": "npx",
      "args": [
        "-y",
        "--package=https://github.com/a32116150-ctrl/verify402/releases/download/mcp-v1.0.2/verify402-mcp-1.0.2.tgz",
        "verify402-mcp"
      ],
      "env": { "VERIFY402_URL": "https://verify402.onrender.com" }
    }
  }
}

Note: With no EVM_PRIVATE_KEY configured, only the free preflight_gate is registered. Configure EVM_PRIVATE_KEY with a dedicated hot wallet holding USDC to unlock the 5 paid checks.


Related MCP server: SENTINEL

Machine-Readable Discovery

Human Pages: Home · Start · 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:

  1. Invented profit (the big one). decodeFunctionResult on getAmountsOut — a single declared output, which viem unwraps — was indexed with decoded[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 the uint256[] structurally and refuses a result equal to the input.

  2. 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_000n instead of 1_000_000n when keeping two decimals of precision.

  3. Venue that doesn't exist still gets reported. Started as an unasserted assumption; all configured venues exist on-chain is now a test.

node test/measure.test.mjs   # 5 checks, needs a mainnet RPC

One 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                              # engine checks (network required)
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=... node src/server.mjs

Endpoints

Route

Price

What it does

POST /preflight

free

pre-signing gate: validate a payment request or transaction, return PASS / CAUTION / BLOCK + a short-lived receipt

POST /preflight/verify

free

verify a preflight receipt (HMAC, intent hash, expiry)

POST /verify

$0.05 (x402)

the measurement, with verdict + evidence

POST /safety

$0.10 (x402)

pre-trade gate: TRUST / CAUTION / AVOID + evidence

POST /route

$0.03 (x402)

what would the chain ACTUALLY pay for this swap? best route + every quoted venue

POST /quotecheck

$0.05 (x402)

audit a quote someone else gave you: fair / overpaying / too_good_to_be_true

POST /depth

$0.05 (x402)

position sizing: how big can I trade before the round trip eats me?

GET /findings

free

live no_edge verdicts on Ethereum majors

GET /info

free

machine-readable service description

GET /health

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, optional from (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", "payer_balance", "block_freshness"],
  "notChecked": ["recipient reputation", "sanctions screening", "payment signature / signed payload", "facilitator settlement", "off-chain promises"],
  "receipt": {
    "spec": "verify402/preflight-receipt@1",
    "intentHash": "…",
    "blockNumber": 48277717,
    "blockHash": "0x…",
    "expiresAt": "2026-10-19T…Z",
    "signature": "…"
  }
}
  • The receipt is HMAC-SHA256 signed, bound to the canonical normalized intent, a fresh pinned block where the chain is readable, and an expiry (default 90s, max 300s). POST /preflight/verify recomputes it.

  • Fresh block, not just any block: on EVM chains, a latest block older than 30s is CAUTION; older than 120s is BLOCK. The response carries block.timestamp and block.ageSeconds. A block with no timestamp is a 503, not a PASS.

  • Fail-closed: if the chain cannot be read, the endpoint returns 503 and no receipt. An executor must refuse to sign without a verifiable receipt.

  • 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, and facilitator settlement. They are named in every response instead of being implied.

  • Free is rate-limited (60/min per IP, 8 concurrent) so it cannot become a public RPC proxy.

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 dependency-free, deny-by-default wrapper: 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.

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",
  "checked": ["contract", "market", "sellability", "liquidity"],
  "notes": ["sellable 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 },
                "liquidity": { "roundTripCostPct": 1.42 } },
  "elapsedMs": 1434
}

verdict is one of:

  • AVOID — positively dangerous: cannot be exited at a realistic size, or the contract is not a token at all

  • CAUTION — tradable, but with a stated cost or problem (thin pool, manufactured price, expensive round trip)

  • TRUST — every check passed; still not a promise of profit, only a record that nothing measured was wrong

Probes are USD-sized on purpose: an earlier version probed "half a token" and flagged SHIB — a $1B token — as a honeypot, 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 only preflight_gate and nothing can be charged.

  • Paid tools: add a dedicated hot wallet with USDC. The bridge signs the x402 payment only — never a trade — and enforces MAX_PAYMENT_USD (default $0.25) inside the x402 client before a 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_USD is optional.

  • Install: npx -y --package=<release-tarball> verify402-mcp. The versioned GitHub release tarball is the install; no npm account is required. Client configs, the exact OpenCode V2 shape, and the release command are at /mcp. See also mcp-bridge/README.md.

Settlement, response delivery, and recovery (honest contract)

  • 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 (not per retry — though with double-keying even fresh ids per retry are protected, see below). If the response is lost after settlement, retrying returns the SAME verdict without requiring payment again, under two locks: 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 carry recovery: { replayed: true, responseHash, replays }.

    • Settlement-gated (fail-closed): a verdict record only becomes replayable after the facilitator confirms the settlement (official x402 onAfterSettle hook). Promotion is keyed on a server-generated operationId carried 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, shared between the caller key and the content-hash companion key — two aliases are ONE budget, not two.

    • Key/scope conflicts (rejected before payment or replay): a key is bound to one operation — its canonical request content plus, when the rail exposes it (payload.authorization.from), the payer's scope. Presenting the same key with changed request content or from a different payer returns 409 recovery_conflict (conflict: "request_mismatch" | "scope_mismatch") before the paywall — never the other operation's verdict, never a charge.

    • Double-keying: the verdict is recorded under the caller's key and an implicit key derived from the canonical request content. A caller that mints a fresh id on each retry still replays free under the content hash. Two distinct asks that canonicalise identically are the same question — sharing one verdict inside the window is determinism, not a bug. The companion is a same-caller recovery alias, not a public cache: a companion hit is only returned to a caller whose declared scope matches the scope recorded at staging. Equal content alone is never permission to see another account's result.

    • After the window (typed, not silent): expired verdicts leave a tombstone carrying the previous responseHash for 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 carries recovery: { remeasured: true, previousResponseHash, expiredAt }, so the calling agent can branch on "this operation was charged before and could not be replayed" instead of guessing.

    • Conformance controls: test/recovery.test.mjs includes the review-requested controls — two staged operations with byte-identical output do not cross-promote; promotion is refused on failed/pending settle, a missing/empty transaction, or a missing operationId; same key + changed content or scope is a conflict; a companion requires a matching scope.

  • Evidence bundle — implemented (verify402/evidence-bundle@1): every paid response carries evidence_bundle: { requestHash, responseHash, pinnedBlock, mode, createdAt, payment.in }. Both hashes are sha256 over canonical JSON (keys sorted recursively, no whitespace). requestHash covers { route, body }; responseHash covers the response body excluding evidence_bundle, recovery, cache, elapsedMs. Reference implementation of the canonical form: src/recovery.mjs (canonicalStable), tests in test/recovery.test.mjs.

  • Payment caps (bridge/client): MAX_PAYMENT_USD caps 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.

First paid call, in ten lines

  1. GET https://verify402.onrender.com/info — prices, endpoints, recovery contract.

  2. GET https://verify402.onrender.com/findings — free verdicts on majors; see the honest no_edge baseline.

  3. POST /verify with your question ({"chainId":1,"target":"WETH","size":100000}) and no payment → HTTP 402.

  4. Read the payment instructions from the payment-required response header.

  5. Build an x402 payment for ≤ the stated price (Node example: mcp-bridge/first-payment.mjs; MCP users: mcp-bridge/).

  6. Re-send the same request with the payment header. The verdict arrives with the settlement response.

  7. On a retry/restart, add x-idempotency-key: <random-32-hex> to step 3 so lost responses replay free instead of paying twice.

  8. Verify the receipt: evidence_bundle.responseHash must equal sha256(canonical JSON of the body minus evidence_bundle, recovery, cache, elapsedMs).

  9. Cap your own wallet: MAX_PAYMENT_USD (per call) and MAX_DAILY_USD (per day) in the bridge .env.

  10. Repeat the call in the same block and compare evidence_bundle.responseHash — determinism of the measurement. An edge verdict 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.

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.

Related MCP Connectors

Related MCP Servers

  • F
    license
    A
    quality
    C
    maintenance
    Agent-Level Transaction Safety Oracle. Before an AI agent signs a blockchain transaction, it returns a SAFE/UNSAFE verdict with a SENTINEL Score (AAA-D) and risk flags. Pay-per-call $0.005 USDC via x402 on Base.
    1
    -
  • A
    license
    Not graded
    quality
    B
    maintenance
    Pay-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
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables 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