# x402check — pre-payment risk checks for x402 agents and wallets **LIVE**: [https://x402check.xyz](https://x402check.xyz) · `did:web:x402check.xyz` · $0.001 per evaluation with prepaid credits, or per call via x402 ($0.0035 on Base; $0.005 with transaction simulation) · [discovery](https://x402check.xyz/.well-known/risk-check.json) · [DID document](https://x402check.xyz/.well-known/did.json) · [JWKS](https://x402check.xyz/.well-known/jwks.json) x402check is an x402 `risk-check` provider (wire format of [x402 PR #2422](https://github.com/x402-foundation/x402/pull/2422)). You call it before an agent or a wallet pays or signs, and it checks the counterparty. It combines provider-verified evidence with a typed model: - the **OFAC SDN** list, refreshed daily; - curated **phishing and drainer feeds**; - **transaction simulation**: where the assets actually go, and which approvals are granted; - **drainer-kit code fingerprints**, which recognize redeployed drainer contracts before their address is listed; - **our own kit watch**: every Ethereum and Base block is read as it is produced. It records look-alike wallets that delegate (EIP-7702) to an address-poisoning executor, wallets whose delegate forwards whatever they receive (sweepers), and new contracts running drainer-kit code; - **look-alike domain** analysis; - **on-chain facts** about the counterparty, such as whether an approval is being granted to a plain wallet; - a typed model (TypeSafe **Jev**) that reads the content the agent acted on for **injected instructions**. Every verdict is an **ES256 attestation**. It states which checks the provider actually ran and which fields the caller merely asserted. > **Scope, stated plainly.** x402check catches what the chain, the lists, its own kit watch, and the content in front of it reveal. It does **not** see laundering patterns or other transaction-graph behaviour. It cannot flag an unknown drainer address that is simply sent funds, unless the address is one of the look-alikes or compromised wallets the kit watch has seen. A clean verdict means "none of these checks fired", not "safe". Measured limits are in [docs/EVIDENCE.md](docs/EVIDENCE.md). [![x402check demo](https://i.ytimg.com/vi/MCOWk7nh5r8/hqdefault.jpg)](https://youtu.be/MCOWk7nh5r8) The demo video predates v0.2.0. Its evidence slide shows v5 corpus numbers that [EVIDENCE.md](docs/EVIDENCE.md) supersedes, and it predates the v0.3 layers (simulation, code fingerprints) and pricing (every evaluation is paid; there is no free tier). ## What it checks | Check | Source | Effect on the verdict | |---|---|---| | Sanctioned address | Official OFAC SDN XML: 1,063 digital-currency addresses (2026-10-14 release), refreshed daily, dated snapshot | score **0 / critical**, deterministic, no model call | | Known phishing domain | MetaMask eth-phishing-detect (~106k hosts, refreshed daily; an embedded copy as fallback) | capped at **20** (critical) | | Known drainer / scam address | ScamSniffer (EVM, runtime KV, 7-day publication lag) | capped at **20** | | Community-flagged domain | ScamSniffer domain list | capped at **40** only when our own domain analysis corroborates it | | Look-alike domain | public-suffix aware: leet, IDN homoglyphs, typosquats, brand + lure word, official domain reused as a subdomain | "strong" impersonation → capped at **40** | | Approval granted to a plain wallet | on-chain `eth_getCode` / activity (EVM), account data (Solana) | permits and approvals to an EOA → capped at **55**; **40** if the address has no activity | | Hidden recipient | simulation of `transaction` (`eth_simulateV1` + `traceTransfers`) | assets leave, nothing comes back, and a wallet the user never named ends up with them → **40** (**75**, review, when a source-verified contract such as a bridge forwarded them) | | Payee gets more than declared | simulation + `payment` / the explicit transfer in the calldata | the named payee receives a different asset, or more, than declared → **40** | | Assets parked in an unverified contract | simulation + Blockscout source verification | nothing in return, contract source not verified → **55** | | Drainer-kit code | logic-code fingerprints of contracts listed by Forta (embedded) and ScamSniffer (runtime) | the subject, or a contract in the simulated transaction, runs a listed drainer's code → **30** | | Address-poisoning look-alike | **kit watch** (our own scan of every Ethereum and Base block) | the wallet delegates (EIP-7702) to an address-poisoning executor → **20** (`address_poisoning`) | | Compromised wallet | kit watch | the wallet delegates to a labelled sweeper family → **20** (`compromised_wallet`); to code that forwards what it receives, with no label → **40** (`auto_forwarding_wallet`) | | Drainer operator | kit watch | the address deployed drainer-kit code → **30** (`drainer_operator`). A forwarder's destinations are not flagged (v0.6.0): whoever deploys a forwarder chooses them | | Unverified spender | Blockscout source verification | approval or permit to an unverified contract → **75** and at least `medium` (review) | | Injected / manipulated intent | Jev typed questions over `context` (what the agent acted on) | model penalties and caps | | New address | on-chain activity | informational `new_address` category | Caller-supplied `screening` and `authorization` fields are recorded as `asserted` in the attestation and **can never lower the score**. Prose claims such as "already screened" are unverified by construction. ## Quickstart Every evaluation is paid; there is no free tier. There are two ways to pay: - **Prepaid credits:** one x402 payment buys a balance, and each check then costs $0.001 with no payment round trip. This is the cheapest and fastest option. - **Per call:** an x402 client pays when it gets the 402 and retries. 1. **See the price.** An unpaid call returns `402` with the accepted mainnet options in the `PAYMENT-REQUIRED` header: ```bash curl -si -X POST https://x402check.xyz/v1/risk-check -H "Content-Type: application/json" -d '{"wallet":"0x7a3e8f0c2b1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f","chain":"base"}' | head -1 # HTTP/2 402 ``` 2. **Pay and check.** Use the TypeScript SDK with an x402-paying fetch (see [Payments](#payments)), the [MCP server](#for-agents-sdk-and-mcp-server), or any x402 client: ```ts const verdict = await x402check.check({ wallet: "0x7a3e8f0c2b1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f", chain: "eip155:1", domain: "https://app.example-dapp.org", context: "Permit2 signature: unlimited USDC allowance to this spender", interaction: { type: "permit_signature", unlimited: true }, payment: { network: "eip155:1", asset: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", pay_to: "0x7a3e8f0c2b1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f" }, }); ``` The same request as the raw body the client sends: ```bash curl -X POST https://x402check.xyz/v1/risk-check \ -H "Content-Type: application/json" \ -H "PAYMENT-SIGNATURE: " \ -d '{ "wallet": "0x7a3e8f0c2b1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f", "chain": "eip155:1", "domain": "https://app.example-dapp.org", "context": "Permit2 signature: unlimited USDC allowance to this spender", "interaction": { "type": "permit_signature", "unlimited": true }, "payment": { "network": "eip155:1", "asset": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "pay_to": "0x7a3e8f0c2b1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f" } }' ``` ```json { "checked": true, "score": 40, "tier": "high", "categories": ["intent_risk", "behavioral", "approval_to_eoa", "new_address"], "provider": "did:web:x402check.xyz", "evidence": { "sanctions": { "list": "ofac-sdn", "as_of": "2026-09-29", "status": "not_listed" }, "domain": { "host": "app.example-dapp.org", "registrable": "example-dapp.org", "official": false, "impersonation": "none", "signals": [] }, "onchain": { "status": "ok", "network": "eip155:1", "is_contract": false, "activity": "none", "tx_count": 0 }, "feeds": [{ "source": "metamask-phishing-detect", "kind": "domain", "as_of": "2026-09-29", "status": "clear" }, "…"], "model": "jev-wallet-risk/v6" }, "jws": "eyJhbGciOiJFUzI1NiIsInR5cCI6InJpc2stY2hlY2srand0Ii…", "jwks_url": "https://x402check.xyz/.well-known/jwks.json", "checked_at": "…", "expires_at": "…" } ``` To also check **what a transaction will do**, send it as `transaction` ($0.005 per simulated evaluation). The provider simulates it against the latest block and reports the net asset movements, the approvals granted, and any findings: ```bash curl -X POST https://x402check.xyz/v1/risk-check -H "Content-Type: application/json" -d '{ "wallet": "0x…called contract or decoded counterparty…", "chain": "eip155:1", "transaction": { "from": "0x…user…", "to": "0x…contract…", "value": "0x2386f26fc10000", "data": "0x…" } }' # evidence.simulation → { "status": "ok", "outflows": [{ "standard": "native", "amount": "10000000000000000", # "counterparty": "0x…", "counterparty_is_contract": false }], "inflows": [], "approvals": [], # "findings": ["outflow_to_undisclosed_eoa"] } → score capped at 40 ``` ## Request fields | Field | Required | Rules | |---|---|---| | `wallet` | yes | the subject address: EVM `0x…`, base58 (Solana/Tron/BTC…), bech32, cashaddr, or CAIP-10. Anything else → `422 {error, field:"wallet"}` | | `chain` | no | alias (`ethereum`, `base`, `solana`, …) or a known CAIP-2 id (`eip155:8453`, the Solana mainnet or devnet id, `bip122:…`, `tron:…`); enables on-chain facts on supported mainnets. An unknown id (`solana:mainnet`) or a chain that cannot hold the wallet (an EVM address with `solana`) → `422` | | `domain` | no | hostname or http(s) URL (normalized server-side); the site the payment or signature is for | | `context` | no | ≤ 4096 chars: what the agent acted on (tool output, page text, instruction). Untrusted by design. Leave out secrets and personal data: keys, seed phrases and credit tokens are redacted before the model sees it, but nothing else is (see [Data handling](#data-handling)) | | `interaction` | no | `{type, unlimited?}`; `type` ∈ `native_transfer`, `token_transfer`, `token_approval`, `nft_approval`, `permit_signature`, `order_signature`, `message_signature`, `contract_call` | | `payment` | no | binds the attestation to a payment to `wallet`: `{network, pay_to, amount (base units), asset, resource}`. `pay_to` must be `wallet` itself and `network` must be able to hold it, or the request is refused (422) | | `aud` | no | ≤ 256 chars; copied into the attestation, never shown to the model | | `transaction` | no | EVM `{from, to?, value?, data?}` to simulate; needs an `eip155` chain. `value` is decimal or 0x-hex; `data` is 0x-hex, ≤ 49,152 chars. Simulated on Ethereum, Base, Polygon, Arbitrum, Optimism and BSC | | `screening`, `authorization` | no | caller assertions, recorded as `asserted` (can only raise risk) | A field not in this table is refused (`422` naming it), never ignored, and a mixed-case EVM address must carry a valid EIP-55 checksum. Batch: `POST /v1/risk-check/batch` with `{"requests": [...]}` (≤ 25). It is all-or-nothing: an invalid item returns `422` with its `index`. ## Attestation A compact JWS (`alg: ES256`, `typ: risk-check+jwt`, `kid` as published in the DID document, today `jev-attest-v1`), TTL 1 h. Claims: | Claim | Meaning | |---|---| | `iss`, `sub`, `iat`, `exp`, `jti` | issuer `did:web:x402check.xyz`, the subject wallet, times, unique id | | `score`, `tier`, `categories` | the verdict and the findings behind it | | `checks` | **what the provider verified**, each item with its freshness anchor (since 0.6.1; [METHODOLOGY §5](docs/METHODOLOGY.md)): `sanctions` (list, date, `digest` of OFAC's SDN.XML, status), `domain` (impersonation), `onchain` (status, network, activity), `feeds` (`source@date:status`, including code-fingerprint sets), `simulation` (status, network, findings, `at_block`), `kit_watch` (scan clock, status, `complete_through` per chain), `model` (question set, or `skipped`), `model_id` (the model id the backend reported; through the AI Gateway this is the alias `typesafe-ai/jev`) | | `asserted` | what the caller **claimed** (screening / pre-authorization): not verified | | `payment`, `interaction`, `aud` | what the verdict was issued for | | `input_hash` | SHA-256 over the canonical normalized inputs, sources and question set | | `request_hash` | SHA-256 over the request fields exactly as sent (RFC 8785): recompute it to prove nothing was dropped or altered in transit | Verify it by **pinning the issuer**, and bind it to the request it answers. Never trust a key URL carried by a response or an intermediary: ```bash npx tsx scripts/verify-attest.ts --request '' --max-age 300 [--aud ] [--pay-to --amount ] ``` The reference verifier is the SDK's `verifyAttestation` behind a CLI. It resolves the key from the issuer's `did:web` document and pins x402check's key by default. It checks `alg`, `typ`, `iss`, `exp` and `iat`, and with `--request` it recomputes `request_hash`, so a verdict issued for another wallet, payment or transaction is rejected (`request_mismatch`). `--max-age` refuses an older verdict (plus a 300 s clock-skew allowance). Without `--request`, a valid verdict for something else still verifies. ## Wallet integration Check the **real counterparty**. For `approve`, a Permit2 signature or a Seaport order, that is the spender, operator or recipient decoded from the calldata or typed data, not the token contract. Send the interaction type too, and the transaction when you want it simulated. The MetaMask Snap in `snap/` is a complete reference decoder. The wallet (or its backend) pays each check with x402, so `payingFetch` below is a fetch wrapped with an x402 client, as in [Payments](#payments): ```js const res = await payingFetch("https://x402check.xyz/v1/risk-check", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ wallet: spenderOrRecipient, // decoded counterparty chain: `eip155:${chainId}`, domain: location.origin, // the requesting site context: "Permit2: unlimited USDC allowance to spender 0x…", interaction: { type: "permit_signature", unlimited: true }, }), }); if (res.status !== 200) return showNotVerified(res.status); // 402 = payment not settled: never an all-clear const v = await res.json(); if (!v.checked) return showNotVerified(); // fail-closed, never an all-clear if (v.tier === "high" || v.tier === "critical") warnOrBlock(v); ``` | Tier | UX | |---|---| | `low` | no warning; optional "checked" badge | | `medium` | amber: "Some signals suggest caution" | | `high` | red: "We recommend you do not proceed" | | `critical` | hard block with override; show the categories and evidence | ## Payments Every evaluation is paid; there is no free tier. **Prepaid credits (recommended):** - `POST /v1/credits {"amount_usd": 1}` returns a 402 for $1.00 on any network. Pay it with any x402 client and the response carries a **token** (`x402c_…`, shown once) and its balance. Packs run from $0.10 to $100. - Send `Authorization: Bearer x402c_…` with `/v1/risk-check`. Each check costs **$0.001** ($0.005 when a transaction is simulated), debited atomically. - Such a check has no 402 round trip and no on-chain settlement, so it is also several times faster. - `GET /v1/credits` with the token returns the balance. `POST /v1/credits` with the token tops it up. - **To hold the token before you pay,** generate it yourself and buy the pack as a top-up of it: `x402c_` and 32 random bytes in base64url (`node -e 'console.log("x402c_"+require("crypto").randomBytes(32).toString("base64url"))'`), sent as `Authorization: Bearer` with the purchase. A token returned in the response is lost if the connection drops while the payment settles. - If a verdict is not produced, the check is refunded, and a client that leaves before its check starts is not charged. Once the verdict is produced, the check is charged even if the client left. **Per call:** - **Price by payment network** (the 402 lists every network it offers): Base **$0.0035**, Solana $0.002, Sei $0.002, Avalanche $0.001, Monad $0.001, Polygon $0.007, Arbitrum $0.009. - A request whose `transaction` is simulated costs **$0.005**, or the network's price if that is higher. The simulated price covers the simulation, the classification of every recipient and spender, and code fingerprints through delegations and proxies. It is charged only on chains where simulation runs. - A batch is billed per item. - **Why the prices differ:** every x402 payment is an on-chain settlement, and its cost is ours. PayAI settles EVM payments with EIP-3009, which any wallet can pay gaslessly, and bills us the network's gas + 30% per settlement (about $0.002 on Base on 2026-10-15). Dexter bills nothing, but on EVM networks it settles only through Permit2, which most payers' wallets cannot use without an on-chain approval. Coinbase CDP settles with EIP-3009 too, at $0.001 per settlement after 1,000 free a month, so it takes the EVM networks where it is cheapest. - **Routing:** each network settles through a facilitator that does not cost more than the price; among those, one any payer can pay through, then the cheapest to us: - Coinbase CDP for Base, Polygon and Arbitrum ($0.001 per settlement after 1,000 free a month); - PayAI for Sei, and as the fallback for every EVM network; - Dexter for Solana and Monad (on Monad through Permit2, since CDP's fee there equals the price). A network whose every route would lose money is not offered until one clears its cost again: Avalanche since 2026-10-15, when PayAI's fee there rose to $0.0057 against a $0.001 price. [`/status`](https://x402check.xyz/status) shows each network's facilitator, transfer method, fee and margin live. It also lists each facilitator's health and published signers, so anyone can check on-chain who settled a payment. - **Settlement:** USDC via x402 v2 (`PAYMENT-SIGNATURE`), **mainnet only**: Base, Polygon, Arbitrum, Avalanche, Monad, Sei and Solana. The x402 "exact" scheme is gasless for the payer, so USDC alone is enough. - **An unpaid request** gets `402` with the accepted options in `PAYMENT-REQUIRED`, whatever its body. Any x402 client pays and retries. - **Invalid input** is never charged: with a payment or a token it is rejected (`422`/`413`) before any payment work; unpaid, the `402` says why in `request_error`. - **Release after settlement:** the attestation is returned only once the payment settles. If the evaluation cannot be produced, nothing is settled (`503`, no charge). - **One payment, one evaluation:** a payment is claimed once, when it verifies, by what its payer signed: a copy of the same payment, sent at the same time or later and however it is re-encoded, gets `409 payment_already_used`. An authorization must stay valid for 60 s more (and at most 24 h), a payer may have 8 payments in flight, and repeated settlement failures hold a payer back. A payment carries exactly one scheme payload, the one its route takes, and a Solana payment comes from the payer's own token account. - **The payer is screened:** a paying wallet on the OFAC SDN list gets `403 payer_sanctioned`, and nothing is charged. - **HTTPS only:** a plain-HTTP API call gets `403`, before its body or token is read. ```ts import { createClient } from "@x402check/client"; import { x402Client, wrapFetchWithPayment } from "@x402/fetch"; import { ExactEvmScheme } from "@x402/evm/exact/client"; import { privateKeyToAccount } from "viem/accounts"; const payer = new x402Client(); payer.register("eip155:*", new ExactEvmScheme(privateKeyToAccount(process.env.PAYER_KEY as `0x${string}`))); payer.setSpendControls({ maxAmountPerPayment: "$1" }); // Once: buy credits (one x402 payment). Store the token like a password. const { token } = await createClient({ fetch: wrapFetchWithPayment(fetch, payer) }).buyCredits(1); // Every check after that: $0.001 from the balance, no payment round trip. const x402check = createClient({ creditToken: token }); const verdict = await x402check.check({ wallet: "0x…", chain: "base" }); // Or pay per call instead: createClient({ fetch: wrapFetchWithPayment(fetch, payer) }). ``` ## Architecture ``` request ─► validate (422 names the field) ─► credits debit, or x402 payment (per item) ─► Provider │ deterministic, provider-side ──────────────────────────────────────┤ OFAC SDN screen ── listed? ──► score 0 · critical (no model call)│ domain analysis (PSL, homoglyph, typosquat, lure) │ threat feeds (MetaMask + OFAC refreshed daily, ScamSniffer KV) │ on-chain facts + code fingerprints (JSON-RPC) │ transaction simulation (eth_simulateV1) + contract verification │ model ─ Jev typed questions over provider checks + context ────────┤ code ─ weights, deterministic caps, tiers (src/scoring.ts) ───────┤ ▼ settle payment (per call) ─► release ES256 attestation: checks · asserted · payment · jti ``` Source layout: - `src/`: provider, validation, enrichment, simulation, code fingerprints, scoring and JWS. - `deploy/`: the Cloudflare Worker: the x402 paywall and pricing, facilitator routing, feed refresh and `/status`. See [deploy/README.md](deploy/README.md). - `packages/`: the TypeScript SDK (`client`) and the MCP server (`mcp`). - `snap/`: the MetaMask Snap. - `eval/`: evaluation layers and production probes. - `scripts/`: data refresh, the feeds publisher and the verifier. - `docs/`: [METHODOLOGY](docs/METHODOLOGY.md), [EVIDENCE](docs/EVIDENCE.md), [STRATEGY](docs/STRATEGY.md). ## Evidence (v0.4–v0.6) | What | Result | |---|---| | **Single-use payments (v0.6.0, production):** three copies of one payment sent at once | one evaluated and settled, two refused (`409`); a later copy refused too | | **Kit watch, Ethereum, a 24 h backfill:** addresses flagged (6,232 poisoning look-alikes, 575 wallets delegated to sweepers or forwarders, 24 destinations) | **6,831**, none of them on ScamSniffer's list, which publishes with a 7-day delay: lead time not yet measured | | **Kit watch:** sampled poisoning look-alikes confirmed by a victim's history (lower bound) | **27/37** (73%) | | **Kit watch:** new contracts matching an old drainer kit (Ethereum 24 h + Base 6 h, from the backfill's logs, not a tracked report) | **0/2,966**: drainer infrastructure moved to EIP-7702 | | **Code sets on held-out legitimate code** (verified contracts + callees) | Before the v0.4 collision gate, the v0.4 kit families matched 16/2,500 held-out contracts on Ethereum; the v0.3 production Forta set matched exchange deposit fleets (Luno, Poloniex, BitGo) that Forta labels as phishing. Found and fixed. After the gate, on a second held-out set: **1 match in 1,737 + 0 in 2,935** (Ethereum + Base), and that match was a real, unlisted drainer on our own review | | **PayAI shadow:** 7 days of PayAI-settled payments on Base, deterministic layers | 3,132 payments, 207 payees, **0 flagged**; $3.13 to check them all | | OFAC SDN addresses (external labels) | 24/24 critical | | MetaMask-listed phishing domains · ScamSniffer drainer addresses | 40/40 · 30/30 | | Drainer **permits**, drainer feed switched off (approval-to-EOA rule) | **27/30** | | **Simulation:** real drainer transactions that still move assets at the latest block | **18/25 flagged** (72%), all as hidden recipients | | **Simulation:** real transactions to 19 well-known contracts | **0/84** flagged | | **Code fingerprints:** listed drainer contracts matched by earlier kits' code, at creation time | **40/82** (Ethereum and Base; 43/100 in v0.3 on Ethereum only) | | Plain **transfers** to unlisted drainers | **0/30**: not detectable from the address alone, unless the kit watch has seen the wallet | | Unlisted phishing domains without a feed | 0–4/60 across four samples: feeds do the heavy lifting | | Well-known contracts and top dApp domains | 0 false positives (0/22, 0/40) | | Tranco top 200k, deterministic rules | 22 capped (0.011%): 20 on MetaMask's own list, 2 crypto look-alikes | | Risky cases with an **attacker-written** context | 20/100 (only look-alike domains) | | Injected instructions passed as raw agent content | 40/40 | | **Signing guard (v0.6.0):** real drainer transactions that still move the victim's assets, refused before the key signs (ScamSniffer feed off) | **22/25** (88%; by contract 10/11): 18 by simulation, 4 by drainer code; the 3 misses are one contract · legitimate: **0/49** that move assets and **0/84** that execute refused; 1/228 in all (a reverting transaction too large to simulate) | | **Payments in production:** a check paid per call on Base, and one paid from prepaid credits | $0.0035, 2.7–5.7 s end to end (single samples: PayAI, Coinbase CDP, CDP on v0.6.0 and v0.6.3) · **$0.001, 0.5–1.4 s**, with no settlement per check | | **Production, every evaluation paid** (settled in USDC on Base, or from credits bought that way) | 53/53 correct and 53/53 attestations verified · `security:v2` 12/12 (re-run on v0.5) · `security:v3` 8/8 · `security:v4` 7/7 · `security:v5` 9/9, 11/11 through Coinbase CDP (v0.5.1), and 11/11 on v0.6.0 | Full methodology, confidence intervals and what each number does *not* show: [docs/EVIDENCE.md](docs/EVIDENCE.md). How each verdict is formed, with every cap: [docs/METHODOLOGY.md](docs/METHODOLOGY.md). Earlier evidence documents are kept as historical records with correction notes. ## Run locally ```bash npm install npm test # provider unit tests npm run typecheck # root + deploy + scripts + snap AI_GATEWAY_API_KEY=... npm start # :8799 local dev server, no paywall (or TYPESAFE_API_KEY=...) curl localhost:8799/.well-known/risk-check.json npm run eval:suite -- --seed 200 # full evaluation (~$0.15 of model calls) ``` Worker: `npm run dev:worker`, or `wrangler dev --local` in `deploy/`. See [deploy/README.md](deploy/README.md) for secrets, feeds, deploy and rollback. ## For agents: SDK and MCP server - **[`@x402check/client`](packages/client)** is a typed TypeScript client with zero runtime dependencies. It runs on Node ≥ 20, browsers, Cloudflare Workers, Deno and Bun. - `verifyAttestation` checks the signature against the issuer's `did:web` key and binds it to the request you made, including `request_hash`. - `interpret` applies the fail-closed policy. Its verdicts come from the **signed** claims only, never from the unsigned body. - **`guardAccount`** (`@x402check/client/guard`) makes the check **enforced, not advisory**. It wraps the account an agent signs with, and every transaction, permit, order, x402 payment or EIP-7702 delegation is signed only after a verified `allow` bound to that exact request. Otherwise it throws, and the key never signs. A transaction is signed only on its simulation (the verified attestation must say `simulation.status: "ok"`), so on a chain the provider does not simulate it is not signed. The exceptions are stated: an x402 payment of up to $0.25 to x402check's own `pay_to` (paying for a check) is not checked; a request with no counterparty and nothing to simulate (e.g. a zero-value deployment) is signed as inert; and a `warn` is signed when your `onWarn` approves it. See the [signing guard](packages/client#signing-guard-block-before-the-key-signs). - **[`@x402check/mcp`](packages/mcp)** is an MCP server for any agent (Claude Code, Claude Desktop, other MCP clients), with the tools `x402check_check`, `x402check_pay`, `x402check_verify_attestation` and `x402check_methodology`. - Every verdict is verified before the agent sees an action. - `x402check_pay` fetches an x402 resource and pays only after x402check clears the exact payee, right before signing. A `warn` goes to the user in the client, never to the agent. - It pays each check from prepaid credits (`X402CHECK_CREDIT_TOKEN`) or itself via x402 (USDC on Base, gasless for the payer), with a per-payment cap and a total budget. Both secrets are redacted from every output. ```bash # with prepaid credits ($0.001 a check, no payment round trip): claude mcp add x402check -e X402CHECK_CREDIT_TOKEN=x402c_… -- npx -y @x402check/mcp # or paying per call from a dedicated wallet with a small USDC balance on Base: claude mcp add x402check -e X402CHECK_PAYER_KEY=0x… -e X402CHECK_BUDGET_USD=1 -- npx -y @x402check/mcp ``` Both packages are on npm: - [`@x402check/client`](https://www.npmjs.com/package/@x402check/client); - [`@x402check/mcp`](https://www.npmjs.com/package/@x402check/mcp). The MCP server is also in the official [MCP Registry](https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.caiovicentino/x402check) as `io.github.caiovicentino/x402check`. **Discovery:** agents and their developers can find x402check in three catalogs. - **The x402 Bazaar**, which Coinbase CDP builds from the payments it settles. It lists [`/v1/risk-check`](https://x402check.xyz/v1/risk-check) and its batch endpoint, with the input schema, a callable example and the price on each network (`GET https://api.cdp.coinbase.com/platform/v2/x402/discovery/resources`). - **[x402scan](https://www.x402scan.com/server/14680ac3-396d-4174-b07d-9fae9bc74e96)**, which reads [`/openapi.json`](https://x402check.xyz/openapi.json). That document declares each paid operation's price range, schemas and agent guidance. - **The official MCP Registry**, where the MCP server is listed as `io.github.caiovicentino/x402check` (npm `@x402check/mcp`). Opening an endpoint in a browser returns an example request and the prices. ## MetaMask Snap (preview) `snap/` has `onTransaction` / `onSignature` insights that decode the request locally and show the real counterparty, the amounts (including UNLIMITED approvals) and local danger findings. **Every check is paid, and this Snap version cannot pay yet.** So 0.3.0 sends **nothing** to x402check.xyz and has no network permission. Every insight says "NOT verified by x402check" and never shows an all-clear. The paid mode is complete and tested behind a single flag (`src/config.ts`), ready for when wallet-side payment exists. It checks the counterparty, simulates the transaction ("You send 1.5 ETH → 0x… (wallet)"), and renders the signed verdict with its evidence and warnings for hidden recipients and known drainer code. Supported decoding: - calldata: ERC-20 approve / transfer, Permit2, EIP-2612, setApprovalForAll, NFT transfers; - typed data: v1, v3 and v4, including Permit2 and Seaport; - `personal_sign` messages, decoded to text. On install and on update it shows a disclosure that states exactly what happens. In this version nothing is sent. It stores only which disclosure you have seen. ```bash cd snap && npm install && npm test # builds, then 322 tests (285 run; paid-mode scenarios also run in Node) incl. the built bundle in SES npx mm-snap serve # then wallet_requestSnaps "local:http://localhost:8062" in MetaMask Flask ``` The Snap is **not yet published to npm nor allowlisted by MetaMask**, so there is no one-click install in regular MetaMask yet. ## Data handling What a check sends where, so you can decide what to put in a request: - **The model vendor.** TypeSafe's Jev model, reached through the Vercel AI Gateway, receives the request's fields (including `context`) and the provider's own findings. Before that, the server redacts private keys, seed phrases and x402check credit tokens from `context`. Nothing else is redacted. We have not yet confirmed the vendor's retention terms, so leave secrets and personal data out of `context`. - **Public RPC operators and Blockscout** receive the addresses and, for a simulation, the transaction. - **Cloudflare** runs the Worker. Workers Logs keep only the Worker's own log lines, which by design carry no request bodies or tokens; invocation logs are off (`invocation_logs = false`), so no request metadata or header is stored there. Cloudflare's zone analytics still count requests by path, status, user agent and country. - **x402check's KV** keeps a record of each settlement (network, transaction, route) for 400 days, for reconciliation. Credit tokens are stored only as their SHA-256. - **Attestations** carry what the verdict covers (`sub`, `payment`, `interaction`, `aud`) and hashes of the request (`input_hash`, `request_hash`), not the `context` itself. ## Data sources and licenses Code is MIT. Data sources: - OFAC SDN (U.S. Treasury); - MetaMask eth-phishing-detect (DBAD-1.2, embedded as a derived hash set, attributed); - ScamSniffer scam-database (GPL-3.0, runtime only: never committed or bundled); - Forta labelled datasets (MIT, derived code fingerprints); - Blockscout and public JSON-RPC endpoints. See [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md). OFAC and MetaMask are refreshed daily by `.github/workflows/feeds.yml` and swapped in at runtime after verification. [`/status`](https://x402check.xyz/status) shows the versions in use. Manual refresh: `npm run ofac:update`, `npm run feeds:update`. ## Roadmap The full roadmap is in [docs/STRATEGY.md](docs/STRATEGY.md). 1. Shadow facilitator traffic continuously. The first week of PayAI-settled payments is replayed (v0.4), and a weekly report and an inline, log-only integration are offered to PayAI. 2. Measure the kit watch's lead time over the public lists as their listings arrive (they lag 7 days), and extend it to factory deployments (CREATE2 inside a contract) and to more chains. 3. Fresher address intelligence: funding-source analytics for plain transfers to wallets the watch has not seen. 4. Valuation-aware simulation rules (price data), closing the "return a dust asset" evasion. 5. Batch and `upto` payment schemes, to spread settlement gas when facilitators stop sponsoring it. 6. Wallet-side payment for the Snap, then publish it and request MetaMask allowlisting. 7. KMS/HSM custody for the attestation key. (Rotation with overlapping `kid`s shipped in v0.6.0: a next key is published before it signs.) 8. Kora `decision_provider` integration (issue #682); AP2 `RiskPayload` once upstream stabilizes.