vet402
Enables x402 payments on the Arc chain (eip155:5042) using USDC, with on-chain settlement verification and publishing of payment outcomes.
Enables x402 payments on BNB Chain (eip155:56) using USD1 via EIP-3009, binding settled payments to the signed nonce and supporting export of settled rows.
Rebuilds a Robinhood Chain wallet's Stock Token track record from public chain data, including look-alike tokens received and corporate actions such as share multiplier changes.
Enables x402 payments on Solana using USDC, with on-chain settlement verification and published payment results.
Enables x402 payments on Stellar pubnet using USDC, with settlement verification and result recording.
Enables x402 payments on the XRP Ledger (xrpl:0) using RLUSD, with settlement verification and result recording.
vet402
Independent Verification of the x402 Agent-Payment Economy
vet402 buys. It settles. It publishes the measurements.
Chains vet402 buys on
vet402 pays x402 sellers on ten chains with its own funds and publishes every attempt. Counts below were read from https://vet402.com/api/v1/observatory/state at 2026-10-23 09:10 UTC (whole ledger). Avalanche C-Chain started on 2026-10-26, after that read, so it has no row here; its count is in l1.byChain of the same URL. They grow daily, so read the live page for current numbers.
Chain | Network id | Asset paid | Paid attempts | Settled | Settled and answered 2xx |
Base | eip155:8453 | USDC | 15,208 | 9,616 | 8,912 |
Solana | solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp | USDC | 983 | 724 | 683 |
Tempo | eip155:4217 | USDC.e | 312 | 282 | 79 |
XRPL | xrpl:0 | RLUSD | 195 | 81 | 61 |
Celo | eip155:42220 | USDC | 168 | 140 | 135 |
Arc | eip155:5042 | USDC | 138 | 68 | 68 |
Stellar | stellar:pubnet | USDC | 77 | 70 | 70 |
BNB Chain | eip155:56 | USD1 | 59 | 41 | 38 |
Arbitrum One | eip155:42161 | USDC | 30 | 14 | 14 |
BNB Chain. vet402 pays sellers that accept USD1 (0x8d0D000Ee44948FC98c9B98A4FA4921476f08B0d) by EIP-3009 on eip155:56, and every settled BNB Chain row is bound to the nonce vet402 signed. To list them:
curl -sL "https://vet402.com/api/v1/observatory/export.csv?days=90" | grep "eip155:56"Related MCP server: trust-mcp
Arbitrum Open House Singapore (vet402 /rwa)
Paste a Robinhood Chain wallet and see its Stock Token track record rebuilt from public chain data, including look-alike tokens it was sent and the corporate actions (share multiplier changes) it held through. Live at https://vet402.com/rwa . Start with docs/rwa/README.md (30-second path, verify-it-yourself, what is not trustless).
ETHGlobal Tokyo 2026 (ENS and Intercepta partner prizes)
For Tokyo, an agent pays an x402 seller only while vet402's draft ENSIP-29 attestation in the seller's ENSv2 name still matches the seller's offer. Check it with one line (Node 22.18 or later, no key):
git clone --depth 1 --branch tokyo-2026-submission https://github.com/kzmttkc/vet402 && cd vet402/examples/tokyo-2026-demo && npm ci && npm run verify -- seller-a.ethPage from the event, where a judge could change one character of a seller's offer on-chain and watch the agent refuse: https://vet402.com/tokyo?name=seller-a.eth
Status on 2026-11-02: since 2026-10-01 the page and the verify command end at step 1 with
ens_evidence_unavailablefor any valid name, because ENS redeployed on Sepolia and the two contracts the SDK reads (UniversalResolver and UniversalHelper) now report different root registries. The demo's attestations, valid for seven days, also expired on 2026-10-03. No seven-step result is produced. The recorded demo and the transactions in for-reviewers are from before that date.Demo, commands and the Intercepta screening notes:
examples/tokyo-2026-demo/README.mdEvery requirement, transaction and one-line check:
docs/tokyo-2026/for-reviewers.mdWhat existed before the event: vet402 and
@vet402/sdk, up to the tagpre-tokyo-2026. Everything ingit log pre-tokyo-2026..tokyo-2026-submissionwas made during it. Who did what, human and AI:AI_USAGE.mdanddocs/tokyo-2026/human-log.md
Judges: 60 seconds (ETHOnline 2026, September)
# Node 24.x (package.json engines; Node 22 cancels the timeout tests)
git clone https://github.com/kzmttkc/vet402 && cd vet402
npm run judge-check # sdk → mcp-server → demo → root → ab: builds, tests, mutation runs. No key, nothing live. About 25–30 s on a clean clone (24–27 s across three measurements on 2026-09-07, warm npm cache)
GRAPH_API_KEY=… node examples/ethonline-2026-demo/src/run.ts judge https://kronossignals.com/api/v1/price/btcLine 2 prints one table, one exit code per step, and
unsets every key before it starts — a green run is proof that none is needed.Line 3 reads the seller's real 402 challenge, vet402's
/decisionand The Graph's x402 Base subgraph (_meta.block.number, live) side by side, then printsverdict: ALLOW|REFUSE,reason_codes[],signed: false. Dry run — the signing module is never loaded. About 4 s.Keys: one free Graph key from https://thegraph.com/studio → API Keys.
/decisionanswers key-less (10/min per IP); nothing else is required.Everything else a judge can run, in dependency order, with the recorded output:
SKILL.md.Continuity Track boundary — what predates the hackathon window, what does not, and the git commands that separate them:
docs/ethonline-2026/README_CONTINUITY_SECTION.md.Language. Source comments are written in Japanese, my working language; commit subjects are Japanese, and the rule from 20:00 JST on 2026-09-08 is to write new ones in English (
docs/ethonline-2026/GIT_RULES.md) — a rule going forward, not a property of the log behind it. It took effect mid-evening, not at the start of that day, and work already in flight kept landing in Japanese for a few commits after. Nothing is rewritten to tidy this up, so count it rather than take my word:TZ=Asia/Tokyo git log origin/main --since='2026-09-08 20:00:00 +0900' --pretty=%s. The English route through the work isSKILL.md,AI_USAGE.mdanddocs/ethonline-2026/CHANGED_FILES.md. Seven source files carry an English header above the Japanese one — the SDK's decision and payment path, the MCP bridge, and the two example front-ends:packages/sdk/src/pay-or-refuse.ts,packages/sdk/src/subgraph-evidence.ts,packages/sdk/src/x402-pay.ts,packages/mcp-server/src/pay-if-trusted.ts,examples/ethonline-2026-demo/src/run.ts,examples/ethonline-2026-demo/src/render.ts,examples/ethonline-2026-ab/src/mcp.mjs. Count them yourself:grep -rln "English header for judges" packages/ examples/ | grep -v dist.
vet402 buys from x402 endpoints, re-reads each payment on-chain, records what came back, checks it against the seller's output declaration when there is one, and publishes the results.
Formerly Vouch. The repository is https://github.com/kzmttkc/vet402 (renamed 2026-08-18). The npm scope is
@vet402/*; the API key prefix (vouch_) and some internal identifiers retain the old name for backward compatibility — renaming them would break every key already issued.
Site: https://vet402.com · Live demo: https://vet402.com/playground · API reference: https://vet402.com/docs/api · Accuracy ledger: https://vet402.com/accuracy
日本語の概観は docs/ja/README.md。
This repository is the source of the vet402 service and of the three npm packages published from packages/.
ETHOnline 2026 (Continuity) — what I claim from this window
The boundary tag pre-ethonline-2026 is commit c42daca, cut 2026-09-04 00:05:36 UTC, and it is
pushed, so anyone can verify the split themselves. The window opened at 2026-09-04 16:00 UTC
(ETHGlobal's published schedule, hacking-begins), so the tag sits 15 h 54 min before the start, not
five minutes after it as this file said until 2026-09-08. Three commits in the range I claim were made
before the window opened — their SHAs, times and contents, and the command that lists them, are in
docs/ethonline-2026/DISCLOSURE_2026-09-05.md, which
also carries the message I sent ETHGlobal on 2026-09-05 disclosing the volume of work that predates
the tag, and why its commit count reads 207 where this file says 214.
One caveat. main is also this product's production branch, so the range
pre-ethonline-2026..main contains work that has nothing to do with this submission — grant
applications for other programmes, SEO and marketing, a secret rotation, operational fixes. I am not
claiming all of it. What I submit is the payment gate and what it needed:
git log pre-ethonline-2026..main -- packages/sdk packages/mcp-server \
examples/ethonline-2026-demo examples/ethonline-2026-ab \
SKILL.md AI_USAGE.md docs/ethonline-2026 # the submission
git log pre-ethonline-2026..main # everything on main in the same daysBoth commands are worth running. The first is my claim; the second is the full picture, including the parts I am not claiming.
Who wrote the code, by area and representative file — and what I did: AI_USAGE.md. An excerpt of the instructions I gave, by day (Japanese; the folder's README opens in English):
docs/ethonline-2026/PROMPTS/. The payment gate itself:
SKILL.md.
Start here — four doors, all reachable from this page:
https://vet402.com/ethonline — the judge's landing page: one command to run, what to read in which order, the continuity disclosure, The Graph.
docs/ethonline-2026/COMMITS_EN.md— every commit in the window in English, derived fromgit log pre-ethonline-2026..mainbyscripts/ethonline-commits-en.mjs(✔ marks what I claim;--checkfails if the file is not what the script produces).— one click: Node 24 with SKILL.md's build order already run (
.devcontainer/devcontainer.json), sonpm run judge-checkworks as written.skills/pay-or-refuse/SKILL.md(Agent Skill) and.claude-plugin/plugin.json(Claude Code plugin) — the same gate, packaged the way The Graph's own skills repositories are.
Try it (clean clone; the first line needs no API key):
npm run judge-check— one command from the repo root: sdk → mcp-server → demo → rootnpm ci→ A/B harness, every test, exit codes recorded per step (scripts/judge-check.sh).examples/ethonline-2026-demo—refuse(two sources side by side, no signature),pay(dry run: fetches the real 402, signs nothing),judge <url>(bring your own x402 URL).examples/ethonline-2026-ab— the Bazantic A/B harness: Recipe vs no Recipe, mock run without keys, live run with a model.
Install
npm i @vet402/sdk # TypeScript API client
npm i @vet402/middleware # x402 transaction gate (Express / Next.js / Hono)
npm i @vet402/mcp-server # MCP tools for Cursor / Claude DesktopUse the scoped names exactly as written above. The unscoped npm package
vouch-sdkis an unrelated project published by a different vendor and has nothing to do with vet402. Only@vet402/*packages are vet402's.
On npm since 2026-09-12 (06:41 UTC, the day before the 2026-09-13 16:00 UTC deadline).
@vet402/sdkis 0.6.0 (published 06:41:38 UTC) and exportspayOrRefuse;@vet402/mcp-serveris 0.3.0 (06:45:12 UTC) and answerstools/listwith 7 tools,pay_if_trustedamong them. The releases that stood through the window contained neither: the@vet402/sdk@0.5.0tarball (2026-08-25) shipsindexandspend-guardonly, and@vet402/mcp-server@0.2.0(2026-08-24) answerstools/listwith 5 tools. Publishing is not part of the claimed work; what is judged is the clone, not the tarball. To read or run this window's code from source, clone this repository and build frompackages/(SKILL.md→ Install).
import { createVouchClient } from "@vet402/sdk";Get a free API key at https://vet402.com/signup (1,000 lookups/month, no card required).
Verification levels
A result never moves up a level: an L0 probe cannot report settlement, and an L3 opinion is never folded into an L0–L2 fact.
Level | Question | How | Output |
L0 | Liveness — does the endpoint answer correctly? | Probe, no purchase | pass / fail / unverified |
L1 | Settle-through — does the payment settle, and does a non-empty 2xx come back? | Real purchase | n of m settled, latency |
L2 | Conformance — does the response match the seller's own declaration? | Purchase + machine diff | conform / mismatch / undeclared / not_checked |
L3 | Quality — is the content any good? | Not built | none |
The 0–100 trust score this API returns today (banded ALLOW / WARN / BLOCK) predates these levels. It stays available to API and SDK callers during the transition, and is never reported as an L0–L2 result. Methodology: https://vet402.com/#methodology.
Since 2026-09-02 (product spec v1.0), the canonical integration is GET /api/v1/resources/{resource_id}/decision: the L0–L2 facts and the ALLOW / WARN / BLOCK recommendation arrive in the same document, with reason_codes, freshness, evidence and the rules_version that produced it. GET /api/v1/resolve?q=<url|domain|address|tx> turns whatever you hold into the §5 object ids. The score endpoints remain as a thin compatibility layer.
Docs
Start here, in this order:
PRODUCT.md — two products, verb glossary, plans
DESIGN.md — public RFC paper vs dashboard operate app
docs/ARCHITECTURE.md — system map: data flow, money safety, module boundaries (日本語)
CONTRIBUTING.md — full-stack setup (Docker or local), PR checklist, first issues
docs/README.md — which remaining file to open (do not load the rest by default)
Grant / hackathon reviewers: docs/applications/ has the one-page impact summary and per-ecosystem materials.
Customer-facing: Quickstart · OpenAPI · Deployment
Self-hosting and development
The rest of this file covers running the service yourself. Using vet402 as a customer needs none of it — see Install above.
Stack
Next.js 16 (App Router) + TypeScript
viem (Base mainnet)
Drizzle ORM + PostgreSQL (Neon)
ERC-8004 Identity & Reputation Registry
Quick start
cp .env.example .env.local
# Set DATABASE_URL, secrets, DEV_API_KEY, BASE_RPC_URL
npm install
./scripts/dev-setup.sh # local Postgres + db:push (Docker or Homebrew)
npm run devHealth check
curl http://localhost:3000/api/healthScore an agent (dev)
curl -H "Authorization: Bearer $DEV_API_KEY" \
http://localhost:3000/api/v1/agents/1/scoreScore by wallet (x402 integration path)
curl -H "Authorization: Bearer $DEV_API_KEY" \
http://localhost:3000/api/v1/wallets/0x1234567890123456789012345678901234567890/scoreAttest settlement
curl -X POST -H "Authorization: Bearer $DEV_API_KEY" \
-H "Content-Type: application/json" \
-d '{"wallet":"0x...","txHash":"0x..."}' \
http://localhost:3000/api/v1/payments/x402Resolve, then decide (product spec §7.3 / §9.1)
# what is this URL? → resource_id, endpoint_id, payee_id (key-less)
curl -L "http://localhost:3000/api/v1/resolve?q=https://api.example.com/v1/quote"
# should my agent pay it? facts + recommendation in one document
curl -L -H "Authorization: Bearer $DEV_API_KEY" \
"http://localhost:3000/api/v1/resources/<resource_id>/decision?role=payer"
# should this seller serve this payer? (seller mode, post-settlement)
curl -L -H "Authorization: Bearer $DEV_API_KEY" \
"http://localhost:3000/api/v1/resources/<resource_id>/decision?role=payee&payer=0x..."
# The rest of the §7.3 / §9.1 family — all key-less:
# GET /api/v1/resources/<resource_id> the Resource, payees, links
# GET /api/v1/endpoints/<endpoint_id> the Endpoint (sha256 id or observatory uuid)
# GET /api/v1/endpoints/<endpoint_id>/payees endpoint → payees[]
# GET /api/v1/payees/<address>/endpoints payee → endpoints[]
# GET /api/v1/observatory/endpoints/<id>/facts L0–L2 seller facts, no score
# GET /api/v1/census/summary?chain=&window=7d|30d settlements raw and real, never merged
# GET /api/v1/observatory/corrections the correction log as JSONDatabase setup (M2)
# Set DATABASE_URL in .env.local, then push schema
npm run db:push
# Create a production API key
npm run api-key:create -- --plan free --name "my app"Production: set APP_ENV=production explicitly (do not rely on NODE_ENV alone).
Production required env vars: DATABASE_URL, API_KEY_PEPPER, DASHBOARD_SESSION_SECRET, ADMIN_SECRET (min 32 chars, no placeholders).
Set PROXY_HEADER_SOURCE to name the proxy in front of the app (vercel | generic | none); per-IP rate limits depend on it, and the default none believes no forwarded header.
Never set in production: DEV_API_KEY, SKIP_CHAIN_READS.
Deep health check: GET /api/health?deep=1 with Authorization: Bearer $ADMIN_SECRET (development allows unauthenticated deep checks).
Dashboard uses httpOnly session cookies only (Bearer API keys are not accepted on dashboard routes).
Whitelist override: responses include manualOverride: true when customer WL/BL changed the outcome. Global operator blacklist is applied opaquely (blockReason: operator_policy, signals.manual.list: none). WL is not applied when sybil risk is high.
Customer WL/BL lists are shared across all API keys under the same owner account.
Score cache invalidation uses DB-backed epochs (cache_epochs table) so list changes propagate across serverless replicas.
After enabling API_KEY_PEPPER, recreate API keys (hashes change).
API keys are stored as SHA-256 hashes. Monthly quotas are enforced per owner (all API keys under the same account share one quota). Up to 10 active API keys per owner.
Plan | Monthly limit |
free | 1,000 |
pro | 50,000 |
scale | 500,000 |
Responses include X-RateLimit-Limit, X-RateLimit-Used, and X-RateLimit-Remaining headers.
Dashboard (M3)
npm run dev
# Open http://localhost:3000/dashboard
# Sign in with a database-backed API key (not DEV_API_KEY)Dashboard features:
Overview — monthly usage and plan quota
Lookup — agent / wallet score search (counts against API quota)
WL/BL — customer whitelist/blacklist + CSV import (max 500 rows)
Logs — recent query history
API Keys — create keys (inherits current plan only) and revoke
Sign-in uses httpOnly session cookies (API key is not stored in the browser).
Signup: /signup creates a free account + API key (invite code required when BETA_INVITE_CODE is set).
Billing: Dashboard → Billing (Stripe Checkout for Pro/Scale).
Funder indexer (F-03)
Background job populates funder_wallets for sybil cluster detection (read-only from scoring path):
npm run indexer:funders
# or Vercel cron: GET /api/cron/index-funders (daily 04:00 UTC)Owner agent indexer (sybil F-03)
Indexes ERC-8004 Registered / Transfer events into owner_agents for multi_agent_owner sybil checks:
npm run indexer:owners
# or Vercel cron: GET /api/cron/index-owners (daily 05:00 UTC)Partial sync is supported — scores ship while the indexer catches up. Responses include dataCoverage.ownerIndexer (synced only at tip; otherwise partial + staleRisk). Sybil multi_agent_owner uses ERC-721 balanceOf (authoritative) cross-checked with max(index, balanceOf). Full catch-up can take weeks; do not gate product launch on it.
Monitor critical outages: GET /api/cron/monitor-health (503 = env/DB/RPC only). Indexer lag is reported in the payload without forcing 503.
Log retention
trust_events are purged by plan: 90 days (free) / 1 year (pro, scale). Expired dashboard sessions and stale IP rate-limit buckets are also cleaned.
npm run cron:purge-logs
# or Vercel cron: GET /api/cron/purge-logs (daily 03:00 UTC)MCP Server (M4 / M7)
cd packages/mcp-server
npm install && npm run buildTools: check_agent_trust, check_wallet_trust, check_payee_trust, explain_trust_score, attest_x402_payment, check_resource_decision, pay_if_trusted, diagnose_x402_url
See MCP setup for Cursor / Claude Desktop configuration.
TypeScript SDK (M7)
cd packages/sdk && npm install && npm run buildimport { createVouchClient } from "@vet402/sdk";x402 sample middleware (M4)
cd examples/x402-trust-gate
npm install
export VOUCH_API_KEY=your_key
npm run demoExpress middleware that blocks BLOCK recommendations before serving paid routes. See x402 integration guide.
Project structure
src/
app/api/v1/ # REST API routes
app/docs/api/ # Hosted API reference page
lib/
chain/ # viem client, ERC-8004 reads, wallet metrics, agent resolver
scoring/ # Score engine, sybil detection
db/ # Drizzle schema
api/ # Auth, rate limits (M2)
docs/
README.md # which remaining file to open
quickstart.md
deployment.md
openapi.yaml
packages/
mcp-server/ # @vet402/mcp-server — MCP tools for Cursor / Claude
sdk/ # @vet402/sdk — thin TypeScript API client
middleware/ # @vet402/middleware — x402 gate (Express / Next.js / Hono)
examples/
x402-trust-gate/ # Express middleware sampleMilestones
Phase | Status | Scope |
M0 | ✅ Done | Base RPC, ERC-8004 reads, API skeleton |
M1 | ✅ Done | Score engine v1, sybil standard, cache |
M2 | ✅ Done | API keys, rate limits, DB persistence |
M3 | ✅ Done | Dashboard, WL/BL |
M4 | ✅ Done | MCP server, x402 sample, docs |
M5 | ✅ Done | Closed β deploy, funder indexer, signup + Stripe, log retention |
M6 | ✅ Done | x402 payment attestations + 10% score weight |
M7 | ✅ Done | Parallel channels: SDK, MCP attest, settlements UI, API docs |
License
MIT © KIZUNA Creation. The published @vet402/* packages carry the same license.
The measurements are licensed separately: CC BY 4.0 — the observatory
snapshot (/api/v1/observatory/state), the daily series (/api/v1/observatory/history), the
L1 purchase ledger (/api/v1/observatory/export.csv), the L0 export
(/api/v1/observatory/l0/export.csv) and the sellers export (/api/v1/sellers/export.csv) may be redistributed and built on,
commercially or not, provided the source is named:
KIZUNA Creation. vet402 observatory. Dataset, retrieved YYYY-MM-DD. https://vet402.com/api/v1/observatory/state
The retrieval date is part of the citation — every one of these numbers moves. The JSON carries
license / retrievedAt / cite in the body; the CSV carries them in x-vet402-* and Link
response headers.
Available Tools
7 toolsattest_x402_paymentB
Record an x402 payment attestation after settlement verification (idempotent on txHash).
Inputs: txHash (required), wallet (required), amount, network, +1 more.
check_agent_trustA
ERC-8004 agent trust check on Base. Decide on decision (ALLOW_PAY | REFUSE) and safe_to_pay (boolean); pay only on ALLOW_PAY. An error result is REFUSE too — no answer is not an ALLOW. measurement carries the full score body as evidence, not as the decision.
Inputs: agentId (required), wallet.
check_payee_trustA
Buyer-side check before paying a wallet.
Read these two fields and nothing else to decide:
decision: ALLOW_PAY | REFUSE
safe_to_pay: boolean (always equals decision === ALLOW_PAY)
Pay only on ALLOW_PAY. REFUSE already accounts for a degraded read, a partial measurement, a stale score, and a non-ALLOW recommendation; refuse_reasons names which. An error result is also REFUSE with safe_to_pay false — no answer is not an ALLOW.
measurement carries the full score body (score, dataDepth, signals) for explanation and logging. It is evidence, not the decision.
Inputs: payee (required).
check_resource_decisionA
Pre-payment decision for an x402 resource (role=payer) or pre-service decision for a payer (role=payee). Decide on decision (ALLOW_PAY | REFUSE) and safe_to_pay (boolean); pay only on ALLOW_PAY. WARN and BLOCK are both REFUSE under the default allow-only policy. An error is REFUSE too. measurement carries the full decision body: facts (L0 liveness, L1 settle-through, L2 conformance), reason_codes, freshness, evidence, and the rules_version that produced the recommendation. The L1 reason codes name whose gap it is: l1_not_attempted (vet402 signed no paid attempt), l1_inconclusive (settled purchases exist, but each paid response was a 4xx vet402 attributes to its own request shape - a gap in vet402's measurement, not evidence against the seller; facts.l1.n_inconclusive carries the count and those rows do not count toward a BLOCK), l1_never_delivered (a conclusive paid response existed and nothing was delivered), l1_delivered.
Each row of evidence[] names its own source: vet402 for a row from vet402's own L0-L2 record, subgraph for a row read from The Graph's x402 subgraph with the caller's own Graph key (that row carries subgraphId, block.number, deployment and queriedAt, which is what shows live index data rather than a static snapshot). This route returns vet402 rows. Do not add counts across sources: they count different things, and the two can disagree about the same wallet.
Your own policy (role=payer only): pass amountUsd (what the 402 asks), maxPerTxUsd (your ceiling, default 1), minL1Deliveries (floor on vet402's delivered L1 purchases) and/or requireVet402Allow (default true: a WARN refuses with payee_recommendation_not_allow, as in the SDK; false waives the WARN and needs minL1Deliveries >= 1 or the server answers 400 invalid_policy). The server then adds measurement.caller_policy with verdict ALLOW | REFUSE and reason_codes in the SDK's own words: price_above_ceiling, insufficient_delivery_evidence, payee_recommendation_block, payee_recommendation_not_allow, evidence_unavailable. A caller_policy REFUSE makes this tool REFUSE and those words are in refuse_reasons. caller_policy never rewrites recommendation; not_evaluated lists what the server did not check (min_subgraph_receipts is always there - The Graph is read only with your own key, through pay_if_trusted).
Inputs: resourceId (required), role, payer, amountUsd, +4 more.
check_wallet_trustA
Trust check for a wallet address. Resolves ERC-8004 agents when registered. Decide on decision (ALLOW_PAY | REFUSE) and safe_to_pay (boolean); pay only on ALLOW_PAY. An error result is REFUSE too — no answer is not an ALLOW. measurement carries the full score body as evidence, not as the decision.
Inputs: wallet (required).
explain_trust_scoreB
Explain a trust score breakdown in plain language for an agent or wallet.
Inputs: wallet, agentId.
pay_if_trustedA
Pay an x402 resource ONLY if vet402's decision allows it. Unlike check_resource_decision, which returns a verdict for you to act on, this tool holds the signer: on anything other than ALLOW the payment module is never even loaded, so no signature can exist.
Read these two fields and nothing else to know what happened:
decision: PAID | REFUSE | FAILED
safe_to_pay: boolean (always equals decision === PAID)
REFUSE means it stopped BEFORE a signature existed; refuse_reasons carries the server's own reason_codes unchanged, plus one of evidence_unavailable, payee_recommendation_not_allow, payment_target_unknown, payer_not_configured, payee_mismatch, chain_or_asset_mismatch, price_above_ceiling (the 402 asks more than maxPerTxUsd), price_above_declared (the 402 asks more than the amountUsd you named). FAILED means it signed and the seller did not settle — signed and nonce are returned, not hidden, because the authorization stays live until validBefore.
settlement is at most settle_claimed: the seller's PAYMENT-RESPONSE header is a claim, not an on-chain confirmation. measurement carries the /decision body verbatim, including evidence[] with each row's own source (vet402 or subgraph) so you can check which ledger answered. A subgraph row also carries subgraphId, block.number, deployment and queriedAt - the proof it was read from a live index. Counts on rows from different sources are not added together.
A seller outside vet402's catalogue (/decision answers 404) is not a dead end: when resource is given, the 404 is handed to the SDK, which judges from the 402's payTo, the payee score for that address and your evidence floors (decision_record.verdict_source payee_score or caller_policy, reason_codes resource_uncatalogued). The same boundary holds there: payTo must equal payee, a BLOCK payee score refuses even with requireVet402Allow:false. Without resource a 404 refuses with evidence_unavailable - there is nothing to judge from.
Omit resource/payee/amountUsd to get the pre-payment checks alone (Graph-key presence, degraded, the server's caller_policy, ALLOW): it refuses with payment_target_unknown, and the evidence floors and The Graph read are not evaluated (they live inside the SDK's payOrRefuse, which is only reached with a payment target). Paying also requires VOUCH_PAYER_PRIVATE_KEY in this server's env and viem installed; without them resource/payee/amountUsd are dropped before the SDK, the same checks run, and the tool refuses with payer_not_configured (floors not evaluated on this path either).
policy is passed to the SDK's payOrRefuse unchanged. policy.evidence.source "subgraph" or "both" reads The Graph's x402 Base subgraph through the Graph Gateway and puts that read on decision_record.evidence[] as its own row (source subgraph, receipts, block.number). The Gateway key is read from GRAPH_API_KEY in this server's env, never from tool input; without it the tool refuses with graph_key_not_configured before reading anything. policy.requireVet402Allow:false waives only a WARN, and only if every declared floor (minSubgraphReceipts / minL1Deliveries) is met - BLOCK and degraded still refuse. decision_record then carries verdict_source caller_policy and policy_override (what was waived, which floors were met with which numbers).
Inputs: resourceId (required), payee, method, policy, +3 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 7 tools
Three tools — check_agent_trust, check_wallet_trust, and check_payee_trust — all perform essentially the same address/agent trust lookup, and their descriptions repeat the identical "decide on decision and safe_to_pay" boilerplate rather than drawing a clear boundary. check_resource_decision also overlaps with all three while adding policy, leaving an agent unsure which entry point to use. Only explain_trust_score, attest_x402_payment, and pay_if_trusted are clearly distinct.
All names are snake_case with a leading verb (check_*, explain_*, attest_*, pay_if_*), which reads predictably. Minor deviations like pay_if_trusted (conditional phrasing) and attest_x402_payment (embedded protocol token) are still legible and do not break the pattern.
Seven tools is well within a comfortable range for a trust-gating server, and each remaining tool (explain, attest, decide, pay) maps to a distinct stage of the x402 flow. The only count concern is redundancy among the three check_* tools, not raw volume.
The surface covers check → explain → attest → decide → pay, which is the full payment-gating lifecycle. Gaps exist around reading back attestations or querying score history, but an agent can work around them via the check tools.
Maintenance
Related MCP Connectors
Trust verification for MCP servers. Check scores, scan for security issues, search 4,200+ servers.
Scores any MCP server before you trust it: free quick check, full paid report, 2-5 way compare.
Independent trust scores, tool surfaces and change history for MCP servers.
Signed, offline-verifiable safety scores for the MCP servers, packages & tools an agent connects to
Related MCP Servers
- AlicenseAqualityDmaintenanceMCP server for AI agent trust verification, enabling agents to verify identities, check trust scores, and build reputation across multiple blockchain and web platforms.1220 npm1MIT
- AlicenseAqualityDmaintenanceMCP server that enables AI agents to verify each other's trust scores, register, submit reviews, and find trusted agents before transacting.420 npmMIT
- AlicenseNot gradedqualityCmaintenanceMCP server for verifying agent payment integrity on x402, providing tools to check payTo mismatches, market stats, and lookup endpoints.1MIT
- AlicenseNot gradedqualityDmaintenanceMCP server for a pre-interaction risk check on any EVM contract/token, enabling agents to verify contracts before approving, swapping, or trusting an address.MIT