Build against proof.
Record and policy-check a declared agent payment, independently observe what settled onchain, reconcile the two against what the executor reported, and get a signed record either side can verify — across HTTP, MCP, and a TypeScript SDK. The core HTTP and MCP surfaces need no account or API key; workspace features and Auto-Evidence use an existing OCD workspace API key.
preflightMode: 'free': 100 completed evaluations per workspace per UTC month, no OCD fee signer. Merchant payments and gas are separate. The same checklist retains the optional offline/mock demo and paid alternative; the pilot checklist uses this path.1 · Agent payments
Overview
OnChainDiligence is a pay-per-call HTTP API. A client calls an endpoint, receives a 402 Payment Required challenge, pays a few cents of stablecoin, and retries the request with proof of payment attached. The server verifies payment, runs the check against live data or an on-chain observation, and returns a signed result.
Each response carries an Ed25519 signature over the data and the signer’s timestamp assertion. Store the response with independently trusted key records to verify it later without contacting this service. A separately verified anchor is required for an external time bound.
- Sanctions, name, and company data come from public sources documented under Evidence Providers below — they are inputs this infrastructure signs and delivers, not the product itself.
- Action and provider evidence (allowance, swap, bridge, staking, and commerce-executor settlement) independently observe what an agent's payment actually did on-chain, separate from any provider's own claim.
- The fee covers infrastructure only — not the underlying data, which is free and public where sourced externally.
Base URL
All API requests go to the api subdomain over HTTPS:
https://api.onchaindiligence.com
The bare domain onchaindiligence.com serves this site, not the API — point integrations at api.onchaindiligence.com.
Evidence Provider API quickstart
This separate paid Evidence Provider API example is not the offline demo or a native Tempo MPP/Stripe MPP commerce integration. For agent-payment onboarding, use SDK 0.10.1. Commerce lifecycle requests go to https://mcp.onchaindiligence.com, not the Evidence Provider API base above.
See a live 402 challenge right now — no wallet, no key, just curl:
# returns 402 Payment Required with the MPP challenge curl -i https://api.onchaindiligence.com/screen/0x7f268357A8c2552623316e2562D90e642bB538E5
To run the check and get a signed result, the mppx CLI handles the entire challenge-pay-retry flow for you. Set the private key of a Tempo wallet holding pathUSD, then call any endpoint:
# key of a wallet holding a little pathUSD on Tempo export MPPX_PRIVATE_KEY=0xYOUR_KEY npx mppx https://api.onchaindiligence.com/screen/0x7f268357A8c2552623316e2562D90e642bB538E5
The CLI receives the 402, builds and submits the payment on Tempo, and prints the signed JSON result. To call the API from your own code instead, see how payment works below.
Command line
The @onchaindiligence/cli package is the fastest way to try the API. Some commands are free and need no key — run them straight from npx. --trust/--fetch-keys and offline-by-default verification require @onchaindiligence/cli@0.4.0 or later — check with npx @onchaindiligence/cli --version if a command below reports an unknown flag.
# check the API + upstream data sources npx @onchaindiligence/cli health # genuinely offline against caller-trusted key records npx @onchaindiligence/cli verify result.json --trust keys.json # is an attestation anchored on Tempo? npx @onchaindiligence/cli anchored <signature>
verify exits 0 for VALID, 3 for INVALID, 4 for UNVERIFIABLE, and 2 for usage errors. Online discovery is available only with explicit --fetch-keys.
The paid checks settle a real per-call payment, so they read a funded payer key from the PAYER_KEY environment variable:
# a viem private key funded on the payment rail export PAYER_KEY=0xYOUR_KEY npx @onchaindiligence/cli screen 0x7f26…38E5 npx @onchaindiligence/cli screen-name "Vladimir Putin" npx @onchaindiligence/cli company 00000006 npx @onchaindiligence/cli us-company AAPL npx @onchaindiligence/cli diligence 0x7f26… 00000006
Add --json for raw output you can pipe into jq. Install globally with npm i -g @onchaindiligence/cli to get the onchaindiligence and ocd commands. Source: github.com/Qazza1/onchaindiligence-cli.
Sandbox / test mode
Build and CI-test your full integration — including a positive sanctions hit — without paying or touching production. Sandbox screening is free, takes no payment, and never calls the live oracle.
It lives on its own path, /sandbox/screen/:address, and only accepts the documented test-vector addresses below. A real address is deliberately refused, so you can never screen a real counterparty against test data by mistake.
# always SANCTIONED — test your block/deny path GET /sandbox/screen/0x00000000000000000000000000000000000000ba # always CLEAN — test your allow path GET /sandbox/screen/0x00000000000000000000000000000000c1ea0000 GET /sandbox/screen/0x0000000000000000000000000000000000000001 # list the vectors any time GET /sandbox
A sandbox response mirrors the shape of a real one so you can code against the same structure — but it is always flagged and never signed. The attestation block carries signed: false and sandbox: true with an explicit note, and the body carries "source": "sandbox". A sandbox result can never be mistaken for a real determination or pass verification.
{
"data": {
"address": "0x…ba",
"sanctioned": true,
"source": "sandbox"
},
"attestation": { "signed": false, "sandbox": true },
"sandbox": true
}
Because it's free and deterministic, it drops straight into CI — assert that your code blocks the sanctioned vector and allows the clean one, on every build:
# the sanctioned vector must be blocked by your code curl -s .../sandbox/screen/0x…ba | jq -e '.data.sanctioned == true' # the clean vector must pass curl -s .../sandbox/screen/0x…c1ea0000 | jq -e '.data.sanctioned == false'
When you're ready for real screening, switch the path from /sandbox/screen/:address to the production /screen/:address — same response shape, now paid and signed.
How payment works
Payment rides on the HTTP 402 standard over the Machine Payments Protocol (MPP). The exchange is three steps:
1 — Call with no payment
The server responds 402 Payment Required with a WWW-Authenticate: Payment header. The challenge encodes the amount, the stablecoin contract, the Tempo chain id, and the recipient.
HTTP/1.1 402 Payment Required WWW-Authenticate: Payment id="...", method="tempo", intent="charge", request="<base64 challenge>" Content-Type: application/problem+json
2 — Pay and retry
The client pays the requested stablecoin amount to the recipient on Tempo, then retries the same request with the payment proof attached in the Authorization header. The mppx CLI and SDK do this automatically.
3 — Get a signed result
The server verifies the payment, runs the check, and returns 200 OK with the result and an attestation. If the upstream data source is unreachable, the server returns 503 before requesting payment — you are never charged for a check that cannot complete.
| Field | Value |
|---|---|
| Network | Tempo mainnet · chain id 4217 |
| Currency | pathUSD · 0x20c0…0000 (predeployed) |
| Settlement | On-chain, per call |
2 · Action evidence
Overview
Beyond a simple payment, an agent's on-chain action can be a token approval, a swap, a cross-chain bridge, or a stake. Each of these has a dedicated preflight route on the MCP server (Base mainnet, x402): it validates the proposed action against your policy and returns a signed PREFLIGHT artifact before anything executes. A matching evidence check independently observes the on-chain settlement afterward and reports whether it matches what was preflighted — the same before/after distinction as payments.
These are MCP-surface tools, not routes on the api.onchaindiligence.com HTTP API documented above. See MCP server for the connection details.
ERC-20 allowance
Validates a proposed ERC20_ALLOWANCE action (network, token, spender, amount) against a caller policy before the agent submits an on-chain approve. Schema: onchaindiligence.erc20-allowance-action.v1.
Swap
Validates a proposed swap (input/output asset, amounts, router, recipient) on Base against policy before execution. Schema: onchaindiligence.swap-action.v1.
Bridge (Circle CCTP)
Validates a proposed Circle CCTP V2 bridge from Base to Ethereum mainnet (eip155:8453 → eip155:1) — source/destination network, asset, and amount bounds. Schema: onchaindiligence.bridge-action.v1.
Lido staking
Validates a proposed Lido stETH submit (stake) on Ethereum mainnet against policy before execution. Schema: onchaindiligence.staking-action.v1.
3 · Provider evidence
Executors & providers
The @onchaindiligence/sdk/commerce export includes provider-specific executor adapters, not six interchangeable payment rails. OCD evaluates policy and independently observes settlement; each executor's own authorization stays fully independent from OCD's decision — an OCD ALLOW never overrides a provider's own denial, and a provider approval never implies OCD ALLOW.
| Provider | What it is |
|---|---|
| x402 | The base production executor for a local signer: Base mainnet, USDC, x402 v2 exact settlement. |
| PayBox | An existing SDK adapter for Base USDC x402 with caller-supplied PayBox credentials and client. A historical September 2026 gateway-mode payment with PayBox SDK 0.8.5 and OneSource was exercised; current PayBox 2.x v2 header-mode compatibility is not yet validated. This is not an official PayBox partnership. Provider reports remain claims, not independent chain settlement evidence. |
| Turnkey | Turnkey Server Wallets. Turnkey's own signed transaction:status webhook, verified server-side by OCD, authenticates a provider claim, not chain settlement. |
| Crossmint | Crossmint Agent Wallets. Crossmint's signed wallet-transfer webhook, verified server-side, authenticates Crossmint's claim; settlement comes from OCD's independent chain observation. |
| CDP | Coinbase Developer Platform Server Wallet v2 EOA accounts. No wallet webhook exists, so this executor itself submits the caller-reported provider claim, same as PayBox. |
| Circle | Circle Developer-Controlled Wallets. Circle's signed transactions.outbound webhook, verified server-side, authenticates Circle's claim; settlement comes from OCD's independent chain observation. |
Provider claim vs. independent settlement. Turnkey, Crossmint, and Circle each deliver a provider-signed webhook that OCD verifies server-side — that authenticates who authored the claim, but it is still the provider’s claim, not settlement evidence. PayBox and CDP have no such webhook, so their executor instead submits a caller-reported claim to POST /operations/:operationId/provider-evidence. Every provider claim is recorded separately from OCD’s own independent chain observation, which is the only thing that confirms settlement. The distinction always travels with the record. See Provider evidence.
See the SDK coverage summary and the full executor documentation for integration details.
FLOP, Tempo MPP and Stripe MPP are not native commerce integrations here. Network settlement observation and the separate Evidence Provider billing protocol must not be read as executor support.
4 · Receipts & verification
Signed receipts
A receipt records what was decided (decision), what executed (execution), and what independently settled on-chain (settlement) — each reported separately, never merged into one summary judgment. Every check on this record resolves to one of three states:
| State | Meaning |
|---|---|
| VALID | The signature verifies against a known, correctly-scoped key and the content is unmodified. |
| INVALID | The signature does not verify, or the content has been tampered with. |
| UNVERIFIABLE | Verification could not be completed — e.g. the key is unknown or the trust registry is unreachable. Never treated as a pass or a failure. |
Explore a real signed receipt at /r/OCD-RCP-NB51-QG4S-VCAN-Y57F or in the Explorer.
Reconciliation Ledger
The Reconciliation Ledger in your authenticated workspace lists payment activity and gives each item one operational status, with a reason. Every item links to its existing operation or transaction evidence. Declared intent and preflight, provider or executor claims, independent settlement and receipt proof stay separate in that evidence.
| Status | Meaning |
|---|---|
| Accounted for | Current evidence is consistent enough that no known contradiction or unresolved required step needs follow-up. |
| Needs attention | Evidence disagrees, a proof failed, an outcome needs manual recovery, or a settled payment has no linked OCD preflight. |
| Pending | More evidence is still expected and nothing observed so far contradicts. |
| Reason label | Status | What it reflects |
|---|---|---|
| Settled and reconciled | Accounted for | Declared payment and independently observed settlement agree, and the commerce receipt verifies. |
| Matched | Accounted for | The same agreement, before final receipt bookkeeping is complete. |
| No settled payment recorded | Accounted for | Blocked, not executed or failed, with no unresolved reference. It means no settled payment is recorded for this operation, not that no payment happened anywhere. |
| Invalid receipt proof | Needs attention | A receipt for this item did not verify. |
| Intent and settlement disagree | Needs attention | What settled onchain differs from what was declared or preflighted. |
| Report conflicts with settlement | Needs attention | A provider or executor report conflicts with independent observation. This doesn't assign blame to the provider. |
| Outcome unknown | Needs attention | A payment may have been sent, and the outcome needs manual recovery. Do not pay again. |
| No linked OCD preflight | Needs attention | A payment settled onchain with no linked OCD preflight. This is not a judgement on authorization, and it does not establish which agent or person initiated it. |
| Settlement reconciliation pending | Pending | A transaction reference is known; independent confirmation or matching is still in progress. |
| Preflight without settlement | Pending | A preflight exists and no payment has been observed yet. |
| Receipt evidence pending | Pending | Settlement is confirmed; the receipt is not yet issued or its proof cannot yet be checked. |
| Refund evidence pending | Pending | A refund was requested or reported. Refund claims are not independently reconciled in v1. |
| Reported fulfilment pending | Pending | The merchant acknowledged an order and has not yet reported fulfilment or cancellation. Payments without fulfilment claims are unaffected. |
Scope. Counts and filters cover the current source page of activity, not workspace totals; older activity is on later pages. Wallet rows appear only for a wallet address you explicitly add, on a supported Wallet Evidence network, and only within the loaded, bounded range. They are not complete wallet history, and they don't establish wallet ownership or agent causation. Merchant fulfilment and refund entries are caller-reported claims, not proof of delivery or of money returned. Receipt proof VALID only means the signed receipt verifies. The Ledger is private to your workspace and creates no receipts or payments. Public and private evidence boundaries are unchanged: the Ledger adds nothing to public receipt pages, and the public MCP and A2A surfaces do not expose it.
Wallet Evidence
Wallet Evidence is a read-only view in the OnChainDiligence dashboard. For a wallet you choose, it shows recent activity in the supported payment asset and which of that activity is exactly linked to evidence already recorded in your authenticated workspace. It is not a portfolio: no balances, pricing, other tokens or complete wallet history.
- Connect an EVM wallet, or paste an address.
- Optionally verify wallet control with a read-only signature.
- Choose Base, Tempo or Arc.
- Select Check wallet activity. Nothing is scanned until you do: connecting, signing and choosing a network never start a read.
- OCD correlates exact network + transaction hash pairs with evidence in your workspace.
- Review each transfer as Agent-linked, OCD-linked, Saved receipt or Unlinked.
- On Arc, you can explicitly search earlier bounded activity when the initial range does not contain the transaction you need. Nothing older is searched unless you ask.
- For a supported transaction you have reviewed, you may choose Create signed receipt. Nothing is created until you do.
Compatible injected EVM wallets are discovered using standard browser-wallet interfaces (EIP-6963). When several are installed, OnChainDiligence asks which one to connect, and Disconnect wallet clears the connection locally. Wallet Evidence does not imply an endorsement of, or partnership with, any wallet vendor. Manual address lookup works without connecting or signing.
| Network | Asset read | Head | Scope |
|---|---|---|---|
| Base | USDC | safe head | One bounded recent scan. |
| Tempo | pathUSD | finalized head | One bounded recent read. |
| Arc | USDC: the native system Transfer stream, interpreted at 18-decimal native precision | finalized head | A default bounded page of 10,000 blocks, with explicit user-initiated search into older bounded history, up to a fixed history limit. |
Block time differs by network, so the same block count covers different amounts of time. The dashboard always shows the block range it scanned. Loaded history is complete only within the stated loaded block range, not complete wallet history. A read that cannot finish returns an error, never a partial result presented as complete.
| Classification | Meaning |
|---|---|
| Agent-linked | An exact transaction-linked OCD operation, plus supported signed agent provenance on that operation. |
| OCD-linked | An exact transaction-linked OCD operation, but no supported signed agent provenance. |
| Saved receipt | A receipt reference saved in your workspace for that exact transaction. Saving does not prove operation linkage or ownership. |
| Unlinked | Observed chain activity with no exact OCD workspace association. It stays Unlinked: OCD does not infer purpose, provider, executor or agent from an address, amount, recipient or timing. |
Connect wallet gives OCD the selected address. It is not proof of ownership or agent identity.
Verify wallet control is a read-only EIP-191 signature showing that your authenticated workspace controlled that address at that time. It is not legal ownership, real-world identity, authorization of any historical transaction, or agent identity, and it does not upgrade any chain observation. It verifies ordinary EVM accounts (externally owned accounts) only.
Wallet activity is a live chain read · not signed, whether or not wallet control was verified. Linked operations, provider or executor claims and signed agent provenance remain separate evidence from that observation.
Create signed receipt. Viewing wallet activity remains read-only. When you select Create signed receipt on a supported transaction, OCD independently re-observes that exact network + transaction on-chain and creates a signed observation receipt, or returns the existing one for that transaction. It uses the same past-payment receipt system as Create a payment receipt. The receipt's policy decision is UNKNOWN unless a policy check was separately established. Creating a receipt does not prove wallet ownership, authorization of the payment, agent identity, provider identity or merchant delivery, and it does not require wallet-control verification.
Unlinked and a signed receipt can both be true. A transfer can correctly show Unlinked and Signed OCD receipt at the same time. Unlinked means there is no exact OCD operation or signed agent provenance associated with it. The signed receipt means OCD independently observed the completed transaction and signed that evidence. Creating a receipt never changes Unlinked into OCD-linked or Agent-linked.
Receipt for a payment that already happened
When a payment already happened without an OCD preflight, give OCD the network and transaction reference. OCD independently reads the chain and returns a signed COMMERCE receipt of what it could verify. Free, no account, no API key. Served by the MCP host, not the api subdomain:
| Field | Description |
|---|---|
| network | Required. CAIP-2 network. eip155:8453 (Base), eip155:1 (Ethereum), eip155:4217 (Tempo), eip155:5042 (Arc), solana:mainnet |
| transaction_hash | Required. EVM transaction hash, or Solana transaction signature. |
| expected_recipient, expected_asset, expected_amount | Optional. Your own claims, compared against what OCD observes. Never treated as evidence. |
curl -s -X POST https://mcp.onchaindiligence.com/observe-payment -H "Content-Type: application/json" -d '{"network":"eip155:8453","transaction_hash":"0x…"}'
Observation covers the supported canonical stablecoin on each network (USDC on Base, Ethereum and Solana; pathUSD on Tempo; USDC on Arc). On Arc (eip155:5042), OCD reads Arc's native USDC system Transfer stream at 18-decimal precision and requires the transaction's block to be at or below the finalized (committed) head; that stream is not an ordinary ERC-20 token contract.
| Response | Meaning |
|---|---|
| 200 | A signed receipt. A later request for the same network and transaction returns the same receipt. |
| 425 | The transaction was not found yet, or has not reached the required confirmation depth. No receipt was issued; retry later. |
| 503 | The chain RPC or signing service was unavailable. No receipt was issued; retry later. |
| 400 | Invalid input, or a network/asset OCD does not observe. |
| 429 | Too many requests from one client; slow down. |
decision is always UNKNOWN with authorized: null, and it has no preflight link, because no OCD policy check happened. It does not establish that the payment was authorized, intended, expected, approved by a provider, delivered, safe or compliant. When a transaction contains several transfers and nothing identifies the intended one, sender, recipient and amount are left empty rather than guessed.Receipts are unlisted: they never appear in any public list or activity feed, but anyone with the exact receipt ID can retrieve and verify it. That is not private access. For a policy-bound receipt, run a preflight before your next payment.
Attestation
Every paid response includes a versioned attestation object. Version 2 signs RFC 8785 canonical JSON containing the exact data, timestamp, key ID, issuer, purpose, and schema version.
| Field | Meaning |
|---|---|
| signed | Whether the response is cryptographically signed. |
| schema_version | onchaindiligence.attestation.v2. |
| issuer / purpose | Domain separation for the signer and claim type. |
| key_id | Identifier of the signing key. e.g. ed25519-D8wfc7civVNG05Ds |
| algorithm | Always ed25519. |
| signature | base64url signature over the signing input. |
| issued_at | ISO-8601 timestamp the response was signed. |
The exact key and its active, retired, revoked, or compromised status are published by key ID:
curl https://api.onchaindiligence.com/.well-known/attestation-keys
Offline verification
Verification has two modes, and the choice is always explicit — never a silent default.
| Mode | What happens |
|---|---|
| Online | OCD retrieves current key material for you, as a convenience — verifyAttestationOnline(envelope) in the SDK, or ocd verify --fetch-keys in the CLI. A network call is made at verification time. |
| Offline | You supply key material you already obtained and trust, and no network call is made — verifyAttestationOffline(envelope, trustMaterial) in the SDK, or ocd verify --trust keys.json in the CLI. The result does not depend on OCD's server being reachable, or honest, at the moment you check. |
The trust snapshot is the live key registry, unmodified
There is no separate offline key format to learn or convert. The key registry response at /.well-known/attestation-keys is already shaped exactly like the SDK's TrustedAttestationKeySet input ({ keys: [...], issuer, registry_version }) — save that JSON once while you have a connection, and pass the file straight to --trust later with zero transformation.
--trust/verifyAttestationOffline is a deliberate statement that you trust those exact records. A key embedded inside the artifact you're verifying, or fetched from an arbitrary URL, must never be copied into your trust file automatically — that would make verification trust whatever the artifact claims about itself.A real copy-paste flow
# obtain/save trusted public keys while online curl -s https://api.onchaindiligence.com/.well-known/attestation-keys -o keys.json # disconnect / disable network here if you want to prove it to yourself # verify a saved result completely offline npx @onchaindiligence/cli verify artifact.json --trust keys.json
Run against a free, real, signed fixture (GET /sandbox/attestation-sample — no counterparty screened, no payment) with the real published @onchaindiligence/cli@0.4.0:
✓ VALID Attestation is valid under the supplied trust policy.
key: ed25519-P2jIwhCn-Af6pTz4 code: valid
verify exits 0 for VALID, 3 for INVALID, 4 for UNVERIFIABLE, and 2 if you omit both --trust and --fetch-keys — there is no silent fallback path.
What VALID, INVALID, and UNVERIFIABLE establish
| State | Meaning |
|---|---|
| VALID | The exact bytes were signed by a key present in your trust material, with a status and validity window that covers the signing time. |
| INVALID | The signature does not match, the content was altered after signing, or the signing key's own lifecycle rules (revoked, compromised, outside its valid window) were violated. |
| UNVERIFIABLE | No basis to decide either way — the key is unknown to your trust material, the online registry was unreachable (--fetch-keys only), or the input is malformed. Never treated as a pass or a failure. |
Signed evidence bundles
A bundle is a portable, signed package: a manifest, a set of existing evidence artifacts — receipts, attestations, allowance/swap/bridge/staking evidence — and a reconciliation summary, sealed together and verifiable completely offline. A bundle does not replace those artifacts or re-issue them; it packages ones that already exist so they travel as one file instead of several.
Bundle integrity and artifact verification are reported separately
Verifying a bundle answers two different questions, and the CLI and SDK keep them visibly distinct rather than collapsing them into one pass/fail:
| Result | What it answers |
|---|---|
bundle_integrity | Was the manifest sealed intact — nothing added, removed, or altered since signing? |
artifact_verifications[] | Does each individual artifact inside the bundle independently verify, on its own terms? |
Both use the same VALID / INVALID / UNVERIFIABLE states defined above. A bundle can be genuinely VALID at the manifest level while one artifact inside it is INVALID or UNVERIFIABLE — that distinction is the point, not an edge case to collapse away. An artifact of a family the verifier doesn't recognize is reported UNVERIFIABLE, never silently accepted as valid.
Reconciliation: agreements, contradictions, insufficient evidence
Where a bundle contains more than one artifact bearing on the same fact, its reconciliation summary separates three outcomes: agreements, contradictions, and insufficient_evidence. Evidence that is simply missing or unresolved is insufficient_evidence, not a contradiction — the two are never conflated.
SDK and CLI
import { verifyBundleOffline } from '@onchaindiligence/sdk' const result = await verifyBundleOffline(bundle, trustMaterial) // result.bundle_integrity -> 'VALID' | 'INVALID' | 'UNVERIFIABLE' // result.artifact_verifications -> per-artifact results, same tri-state
ocd verify bundle.json --trust keys.json
Bundle verification is offline-only by design — there is no --fetch-keys mode for a bundle, only --trust. Example output, produced by the published @onchaindiligence/cli@0.4.0 against a locally-assembled bundle:
✓ VALID bundle integrity: VALID
artifact sha256:PE__9LR3…: VALID
artifact sha256:TUYpIKRQ…: VALID
artifact sha256:cq76zr7N…: VALID
artifact sha256:qWvhdY8M…: VALID
reconciliation: none
limitation: Bundle validity proves cryptographic integrity under the verifier contract only.
verify uses the same exit codes for a bundle as for a single attestation: 0 for VALID, 3 for INVALID, 4 for UNVERIFIABLE, 2 for a usage error.
VALID bundle proves the manifest and its artifacts were sealed exactly as verified, under the verifier contract. It does not prove execution success, settlement, delivery, regulatory compliance, safety, proper authorization, or objective truth — those remain separate, independently-reported facts, exactly as for a single attestation above. A provider-authenticated artifact inside a bundle proves a provider claim, the same checked claim described under Executors & providers, never independent settlement evidence.artifact_verifications result.Legacy key limitation
The key registry publishes an honest, machine-readable trust_warnings list and a strict_offline_verification_ready flag rather than asserting stronger guarantees than the historical record supports. As of this writing, one retired key has no recorded valid_from activation boundary — it was active before this registry began tracking exact activation times, and that boundary has not been invented to make it look known:
{
"strict_offline_verification_ready": false,
"trust_warnings": [
"attestation key ed25519-D8wfc7civVNG05Ds has no valid_from activation boundary"
]
}
Anything signed with that key still verifies correctly — the signature, content, and its known valid_until upper bound all still apply. What's missing is a hard lower bound on when it started being trustworthy, so strict time-windowed verification of very old records signed with it cannot be fully bounded. Every key issued since — including the current active key — records a real, exact valid_from, so this specific gap does not recur going forward. Check trust_warnings on your own saved snapshot rather than assuming it is empty.
Legacy version 1 records
The manual snippet below is retained specifically for legacy version 1 attestations, which predate the domain-separated v2 signing input:
import crypto from 'node:crypto' const input = JSON.stringify({ data: envelope.data, issued_at: envelope.attestation.issued_at, key_id: envelope.attestation.key_id, }) const ok = crypto.verify( null, Buffer.from(input), publicKeyPem, // from /.well-known/attestation-key Buffer.from(envelope.attestation.signature, 'base64url'), ) // ok === true -> signature matches; issued_at is the signer's assertion
5 · MCP
MCP server
The checks and evidence types on this page are also exposed as a paid Model Context Protocol server, so an AI agent can discover them as tools and pay for them autonomously — no API key, no account. It is a live remote server over Streamable HTTP, listed in the official MCP Registry as com.onchaindiligence/compliance.
Connecting the server surfaces the checks and action-evidence preflights documented above as tools, each priced and payment-gated — the live server is the source of truth for the exact current tool list, since it changes as new evidence types ship.
How payment works
The MCP server settles with x402 — the open agent-payment standard built on HTTP 402. A tool call carries no payment on first attempt; the server returns the payment requirements, the agent signs a USDC authorization, and the call is retried with the payment attached. The server verifies and settles before running the check. The flow is non-custodial: payment goes directly from the agent's wallet to the recipient, and the server never holds funds.
| Field | Value |
|---|---|
| Protocol | x402 over MCP · payment in tools/call _meta |
| Currency | USDC |
| Network | Base mainnet |
| Settlement | On-chain, per call, non-custodial |
Discovery
The server is published in the official MCP Registry, the canonical source that downstream MCP clients and marketplaces draw from. An agent that speaks x402 can find it, read its tool schemas, pay, and call — with no manual configuration.
6 · SDK
TypeScript SDK
@onchaindiligence/sdk wraps the HTTP API for typed calls, and its separate /commerce export is the client-side orchestration layer for the provider executors documented above: createCommerceClient opens an operation, preflight() evaluates policy, your executor's execute() authorizes and submits, and observeAndFinalize() independently observes what settled and reconciles it against what was declared and reported. Execute only when preflight is ready (an ALLOW decision), and treat pending and terminal-error as results to handle, not receipts.
import { createCommerceClient, apiPurchasePolicy, BASE_USDC } from '@onchaindiligence/sdk/commerce' import { NodeFileRecoveryStore } from '@onchaindiligence/sdk/commerce/node' // paidFetch: an x402-paying fetch (wrapFetchWithPayment) around your dedicated fee wallet -- the preflight costs $0.01 if (!process.env.OCD_API_KEY) throw new Error('Set OCD_API_KEY first') const ocd = createCommerceClient({ preflightMode: 'paid', accountApiKey: process.env.OCD_API_KEY, recovery: new NodeFileRecoveryStore('./ocd-recovery'), fetch: paidFetch }) const { policy } = apiPurchasePolicy({ maxAmount: '1.00', allowedNetwork: 'eip155:8453', allowedAsset: BASE_USDC }) const op = await ocd.open({ action: proposedPayment, policy }) const preflight = await op.preflight() if (preflight.kind !== 'ready') return handle(preflight) // not ALLOW, or not settled yet: call no executor const execution = await op.execute({ executor: myExecutor }) if (execution.kind !== 'execution-recorded') return resumeLater(execution) // never pay again on ambiguity const finalized = await op.observeAndFinalize() if (finalized.kind !== 'receipt-produced') return retryLater(finalized) // pending or terminal-error
npm install @onchaindiligence/sdk@0.10.1 @x402/core @x402/evm @x402/fetch · Package on npm →. This excerpt remains the paid alternative: 0.01 USDC on Base, including BLOCK evaluations. The canonical onboarding checklist shows free mode first: authenticated Base/Solana USDC preflight, 100 completed evaluations per workspace per UTC month, no fee signer. On exhaustion, 429 free_allowance_exhausted never automatically switches to paid mode. The offline fixture is separate from both production modes.
SDK 0.10.1 forwards accountApiKey and supports explicit preflightMode: 'free'. Only documented payment flows are covered; installing a plugin does not intercept arbitrary payments. Use durable recovery for restarts; the plain withOcd wrapper does not have the proxy’s full stable purchase-attempt journal. Receipt proof VALID verifies integrity/authenticity, not settlement, payment authorization or merchant fulfilment. Auto-Evidence @onchaindiligence/auto-evidence-proxy@0.2.0-rc.0 is a published MIT prerelease under next; latest remains older. No successful production free-receipt or funded live proxy canary is claimed.
7 · Evidence Providers
The checks below are OnChainDiligence's Evidence Providers — sources of external, verifiable fact that the infrastructure signs and delivers. They are inputs to the product, documented here for completeness; they are not the product's identity.
Sanctions screen
Checks a single EVM wallet address for an exact match against the digital currency address identifiers on the U.S. Treasury OFAC SDN List, using a validated snapshot confirmed against OFAC within the last 6 hours. Returns match_status (listed / not_listed) with the dataset snapshot and limitations, signed and timestamped. SDN List only — not OFAC's non-SDN lists, EU, UN, UK or other regimes — and a non-match is not legal clearance. If the snapshot is not current the call answers 503 sanctions_screening_unavailable before payment, never a negative.
| Parameter | Description |
|---|---|
| address | The wallet address to screen, or an ENS name. 0x + 40 hex, or e.g. vitalik.eth |
const r = await od.screen('0x7f26…38E5') // or pass an ENS name: od.screen('vitalik.eth')
npx mppx https://api.onchaindiligence.com/screen/0x7f26…38E5
# MCP tool (Base / x402) Tool: screen_wallet Args: { "address": "0x7f26…38E5" }
{
"data": {
"address": "0x7f268357A8c2552623316e2562D90e642bB538E5",
"match_status": "not_listed",
"sanctioned": false,
"matches": [],
"screening": { "scope": "ofac_sdn_digital_currency_address", "dataset": { /* publication date, SHA-256, retrieved/revalidated */ }, "limitations": [ /* … */ ] },
"source": "U.S. Treasury OFAC Specially Designated Nationals (SDN) List, SDN.XML (U.S. government data, public domain)",
"checked_at": "2026-10-17T06:44:15.164Z"
},
"attestation": {
"signed": true,
"key_id": "ed25519-D8wfc7civVNG05Ds",
"algorithm": "ed25519",
"signature": "wFRfDRSiU5pMOG9y…yqHYfDQ"
}
}
A listed address returns "match_status": "listed" with each matching SDN entry (sdn_uid, name, programs, listed value and entry URL). sanctioned is kept for compatibility and is true only when listed.
Counterparty verdict
Returns a single, signed decision — PASS or BLOCK — with human-readable reasons, rather than raw screening data for you to interpret. Built for agents that need to act, not analyse. Every response is signed with the same key as every other route, so the decision itself is verifiable.
| Parameter | Description |
|---|---|
| address | The wallet address to evaluate, or an ENS name. 0x + 40 hex, or e.g. vitalik.eth |
| Verdict | Meaning |
|---|---|
| BLOCK | The address is listed as a digital currency address on the OFAC SDN List (exact match). Do not transact. |
| PASS | No exact OFAC SDN digital currency address match. This is not legal clearance and does not cover other lists or regimes. |
This endpoint never returns a false PASS. If an upstream check fails, it returns an error rather than passing the address through.
npx mppx https://api.onchaindiligence.com/verdict/0x7f26…38E5
# unpaid requests return 402 with a payment challenge curl https://api.onchaindiligence.com/verdict/0x7f26…38E5
{
"data": {
"verdict": "BLOCK",
"reasons": [
"Address is listed as a digital currency address on the OFAC SDN List (exact match; see signals.sanctions.matches)."
],
"address": "0x8576acc5c05d6ce88f4e49bf65bdf0c62f91353c",
"signals": {
"sanctions": { "checked": true, "sanctioned": true },
"direct_counterparty_exposure": {
"checked": true,
"transfers_scanned": 200,
"counterparties_found": 112,
"counterparties_screened": 25,
"sanctioned_counterparties": [],
"scope": "Direct (one-hop) counterparties observed on Tempo mainnet only…"
}
},
"verdict_basis": {
"live_signals": ["sanctions", "direct_counterparty_exposure"],
"not_yet_evaluated": ["risk_score", "mixer_exposure", "wallet_age", "sanctions_proximity"],
"note": "BLOCK = this address is sanctioned. WARN = it is not, but a direct counterparty is. PASS = neither, within the stated scope — not a full risk clearance."
},
"checked_at": "2026-07-07T14:22:08.441Z"
},
"attestation": {
"signed": true,
"key_id": "ed25519-D8wfc7civVNG05Ds",
"algorithm": "ed25519",
"signature": "UN4TzBvkRsf0eGm4…ZFyElhq1Cg"
}
}
Three verdicts, and they mean different things. BLOCK means the address itself is sanctioned — a hard legal line. WARN means it is not sanctioned but transacted directly with an address that is: a counterparty risk signal, never a designation of the address you asked about. We do not designate by association. PASS means neither was found within the stated scope.
Read verdict_basis before you trust a PASS. live_signals lists the signals that actually ran for that request. If a signal could not be evaluated it is reported as not evaluated — never silently assumed clean — and the reasons say so explicitly. Direct counterparty exposure is deliberately narrow: one hop only (not a multi-hop proximity score), Tempo mainnet only (an address active elsewhere shows no counterparties here), and a bounded recent window. Those limits ride in signals.direct_counterparty_exposure.scope on every response. No sanctioned counterparty found does not mean none exists.
OFAC name screen
Screens a person or company name against the official U.S. Treasury OFAC Specially Designated Nationals (SDN) list — public-domain government data. Uses transparent fuzzy matching (token overlap + edit distance) against primary names and strong aliases, returning confidence-scored candidates. Weak AKAs are not screened, per OFAC guidance.
| Parameter | Description |
|---|---|
| name | Person or company name to screen. min 2 characters |
| threshold | Optional match cutoff, 0.5–1.0. default 0.85 |
const r = await od.screenName('Vladimir Putin') // optional: { threshold: 0.9 }
npx mppx 'https://api.onchaindiligence.com/screen-name?name=Vladimir Putin'{
"data": {
"query": "Vladimir Putin",
"hit": true,
"matches": [
{
"ent_num": 306,
"matched_name": "PUTIN, Vladimir Vladimirovich",
"matched_on": "primary",
"program": "RUSSIA-EO14024",
"score": 0.9
}
],
"threshold": 0.85,
"source": "U.S. Treasury OFAC SDN list (public domain)"
},
"attestation": { "signed": true, … }
}
Company check
Looks up a UK company by its registration number: status, type, incorporation date, registered address, and the people with significant control (PSC) behind it.
| Parameter | Description |
|---|---|
| companyNumber | UK Companies House registration number. e.g. 00000006 |
const r = await od.verifyCompany('00000006')
npx mppx https://api.onchaindiligence.com/company/00000006
# MCP tool (Base / x402) Tool: verify_uk_company Args: { "companyNumber": "00000006" }
{
"data": {
"profile": {
"companyNumber": "00000006",
"companyName": "MARINE AND GENERAL MUTUAL LIFE ASSURANCE SOCIETY",
"status": "dissolved",
"incorporatedOn": "1862-10-25",
"registeredAddress": "Cms Cameron Mckenna Llp Cannon Place, London, EC4N 6AF"
},
"pscList": [],
"source": "UK Companies House (open government data)"
},
"attestation": { "signed": true, "key_id": "ed25519-D8wfc7civVNG05Ds", … }
}
US company check
Looks up an SEC-registered US public company via EDGAR by ticker, CIK, or name: registered entity, CIK, industry (SIC), state of incorporation, exchanges, business address, and most recent filing. Covers public companies and funds only — private US companies register at the state level and are not in EDGAR, so the result carries an explicit coverage note.
| Parameter | Description |
|---|---|
| q | Ticker, SEC CIK, or company name. e.g. AAPL, 0000320193, Apple Inc |
const r = await od.verifyUSCompany('AAPL')
npx mppx https://api.onchaindiligence.com/us-company?q=AAPL
# MCP tool (Base / x402) Tool: verify_us_company Args: { "query": "AAPL" }
{
"data": {
"source": "SEC EDGAR",
"cik": "0000320193",
"name": "Apple Inc.",
"sic_description": "Electronic Computers",
"state_of_incorporation": "CA",
"exchanges": ["Nasdaq"],
"latest_filing": { "form": "4", "filing_date": "2026-06-17" },
"coverage_note": "EDGAR covers SEC-registered (public) companies only…"
},
"attestation": { "signed": true, "key_id": "ed25519-D8wfc7civVNG05Ds", … }
}
Combined diligence
Runs both checks in one call, in parallel. Returns independent results plus an explicit disclaimer that no link between the wallet and the company is established by the data.
| Query | Description |
|---|---|
| wallet | Wallet address to screen. 0x + 40 hex |
| company | UK company registration number. |
const r = await od.diligence({ wallet: '0x7f26…38E5', company: '00000006', })
npx mppx 'https://api.onchaindiligence.com/diligence?wallet=0x7f26…38E5&company=00000006'# MCP tool (Base / x402) Tool: diligence Args: { "wallet": "0x7f26…38E5", "company": "00000006" }
{
"data": {
"wallet_check": { "sanctioned": false, … },
"company_check": { "profile": { … } },
"link_disclaimer": "These are independent checks against separate data sources. No verified link between the wallet and the company is established by this data."
},
"attestation": { "signed": true, … }
}
8 · Anchoring
On-chain anchoring
Any signed attestation can be anchored on Tempo mainnet for an immutable, timestamped, tamper-evident record that a check happened. Only the keccak256 hash of the attestation signature is written on-chain — never the wallet, name, company, or result — so records stay private while remaining provable by anyone. Anchoring is decoupled from checks: it never blocks or delays a paid response.
Cryptographically verifies the complete v2 attestation envelope before anchoring its signature hash. Pass the original { data, attestation } response. Idempotent — re-anchoring sends no new transaction.
await od.anchor(r)
POST /anchor
{ "data": { … }, "attestation": { "signed": true, "schema_version": "onchaindiligence.attestation.v2", … } }Checks whether an attestation has been anchored on-chain, and when — so anyone can verify a record independently.
{
"anchor_hash": "0xadb12c9f…401d",
"anchored": true,
"anchored_at": "2026-06-24T14:44:21.000Z",
"chain": "Tempo",
"network": "mainnet",
"contract": "0xDe47…ea14"
}
ANCHOR_RPC_URL and ANCHOR_CHAIN_ID to be explicitly configured on the deployment; network and contract above always reflect the deployment's real, currently-configured values rather than an assumed default.9 · Reference infrastructure
Discovery & health
These endpoints are free and require no payment — they describe the service and report its status for humans and machines.
Reports whether each upstream data source is reachable and whether response signing is configured. Returns 200 when healthy and 503 when any upstream is degraded, so automated monitors can key off the status code.
{
"status": "ok",
"upstreams": {
"sanctions_oracle": "reachable",
"companies_house": "reachable",
"sec_edgar": "reachable"
},
"attestation": "configured"
}
Service information: the routes, prices, and attestation key URL.
A complete, machine-readable OpenAPI 3.1 document describing every route, parameter, response schema, and the per-call payment terms — for automatic agent and tooling discovery.
Errors
Errors use standard HTTP status codes. Bad input is rejected before any payment is requested.
WWW-Authenticate header carries the payment challenge.Rate limits
Requests are rate limited per client. When you exceed the limit the API returns 429 with a Retry-After header and an X-RateLimit-Remaining count on every response. Back off and retry after the indicated interval.
The full source for the HTTP API, MCP server, SDK, and anchoring contract lives on GitHub.