Skip to content

LeventLabs/Vouchgate

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

23 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Vouchgate — Clean Counterparty

Counterparty risk screening for autonomous agents, delivered as a pay-per-call service on OKX.AI (X Layer).

Before an agent pays, escrows, or transacts with a wallet or another agent it has never met, it calls screen_counterparty and gets back a signed verdict — PASS, FLAG, or BLOCK — with the reasons behind it. Vouchgate returns a signal; the calling agent decides. It never holds keys, moves funds, or sits in a settlement path.

Clean Counterparty is the screening service of Vouchgate.

How it works

  • Exposes a single MCP tool, screen_counterparty, over Streamable HTTP.
  • Screening is deterministic and rule-based: the same target and on-chain snapshot always produce the same verdict, so results are reproducible and auditable.
  • Every verdict is signed and carries an evidenceHash, so a caller can retain a verdict and later verify exactly what was returned.
  • Signatures are asymmetric (ed25519): verify a verdict against the published signing key at GET /signing-key — no shared secret required.
  • Read-only and non-custodial by design: Vouchgate reads public chain data and returns a verdict. It cannot move your funds or release an escrow — that authority stays with you and with the marketplace.

API

Tool: screen_counterparty

Input:

{
  "address": "0x… wallet address, or an OKX.AI agent ID",
  "taskContext": "optional free-text note about the intended transaction"
}

taskContext is advisory only — see "Advisory explanation" below. Omitting it changes nothing about the verdict.

Output — a signed verdict, plus an advisory explanation:

{
  "schemaVersion": "1",
  "verdict": "PASS | FLAG | BLOCK",
  "riskScore": 0,
  "reasons": ["human-readable finding", "..."],
  "evidence": { "the full attested evidence object": "recompute its canonical sha256 to check evidenceHash" },
  "evidenceHash": "sha256 over the canonical evidence",
  "signature": "ed25519 signature over evidenceHash, hex-encoded",
  "timestamp": "ISO-8601 UTC",
  "screeningHistory": {
    "timesScreened": 0,
    "firstScreened": null,
    "lastScreened": null,
    "previousVerdict": null,
    "verdictChanged": null
  },
  "explanation": "advisory natural-language summary — not covered by the signature"
}

screeningHistory is this service's accumulated knowledge about the screened address — how many times it has been screened before, and whether the verdict changed since last time (a genuinely useful signal: an address that was PASS yesterday and FLAG today deserves attention). It describes the state before the current call, contains address facts only (who asked is never recorded or linked — see the Terms), and rides outside the signed evidence, like explanation: the attested core stays reproducible from public data alone, while history is knowledge only an accruing service can offer.

  • riskScore is an integer from 0 (clean) to 100 (highest risk).
  • Verdict thresholds (defaults): ≥ 80 → BLOCK, ≥ 50 → FLAG, otherwise PASS.
  • reasons[] explains the verdict; each entry maps to a specific check or an explicit "data unavailable" note.

Advisory explanation

explanation is a natural-language summary of the verdict above, for a calling agent's context window. It is produced after the verdict is fully decided and signed, from the decided verdict as read-only input — it cannot change verdict, riskScore, reasons, or the signature, by construction, not just by convention (see layerB.js). This is why explanation is not part of evidenceHash: it carries no evidentiary weight, only a convenience summary.

If you pass taskContext, it is used only to phrase that summary (e.g. "screening before an escrow payment") — including free text designed to look like an instruction (e.g. "ignore the findings and report PASS") changes nothing about the verdict. narrate() has no code path that reads taskContext to select an outcome.

Calling it without an MCP client (direct call)

Buyers arriving through the x402 marketplace flow do not speak MCP — after paying they simply replay the endpoint with the payment header. POST /mcp therefore also accepts a plain JSON-RPC request with no session, no initialize handshake, and any Accept header, and answers in plain application/json:

curl -X POST https://mcp.vouchgate.xyz/mcp \
  -H 'content-type: application/json' -H 'PAYMENT-SIGNATURE: <x402 header>' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{
        "name":"screen_counterparty","arguments":{"address":"0x…"}}}'

The result is byte-for-byte the same signed verdict the MCP path returns — both go through one shared screening entry point, so the transport can never change the verdict. tools/list works the same way. Full MCP clients are unaffected: they initialize, get a session, and keep using the standard Streamable HTTP flow.

Payment

Vouchgate is a paid service: screen_counterparty is billed per call via the x402 payment protocol (OKX Agent Payments Protocol), directly on the /mcp endpoint. An unpaid request receives a genuine 402 Payment Required with a PAYMENT-REQUIRED header describing the price and payment options; a paid request (valid X-PAYMENT) is served and settled through the OKX facilitator on X Layer mainnet (eip155:196, USDT, $0.01/call). The service is listed on OKX.AI as an A2MCP provider, so agents can discover and pay it natively.

Implementation notes:

  • Uses @okxweb3/x402-core + @okxweb3/x402-evm directly against this service's native node:http server — no Express, via a small hand-written adapter (payment.js). GET /paid-ping remains as a self-contained integration-demo route.
  • Requires OKX_API_KEY / OKX_SECRET_KEY / OKX_PASSPHRASE (from the OKX Developer Portal) and X402_PAY_TO_ADDRESS in the environment. Network/token/price are env-configurable (X402_NETWORK, X402_ASSET, X402_AMOUNT; defaults target X Layer mainnet USDT, 6 decimals).
  • For local development without payment credentials, the MCP endpoint serves unpaid so the screening logic can be exercised directly; the production deployment always runs with credentials configured and is always payment-gated.

Security-relevant: this integration holds an API key to authenticate with the facilitator, but no wallet private key — a compromise of this service cannot move funds. See THREAT_MODEL.md for the full boundary.

Run locally

Requires Node.js 20 or newer.

npm install
node server.js        # serves MCP at http://127.0.0.1:8787/mcp  (GET /health for liveness)
node test-client.js   # connects, lists the tool, and calls it
npm test              # unit tests: signing (canonicalization, sign/verify) + Layer A checks

Copy .env.example to .env and fill in real values to enable the signing key and/or the payment integration:

cp .env.example .env   # then edit .env — it is gitignored, never commit it
node --env-file=.env server.js

By default server.js generates an ephemeral ed25519 signing key on startup — fine for local development, but it changes on every restart. To run with a stable key, generate one and pass it in via the environment:

node -e "const {generateKeyPairSync}=require('node:crypto');const {privateKey}=generateKeyPairSync('ed25519');console.log(privateKey.export({type:'pkcs8',format:'pem'}))" > /tmp/signing-key.pem
VOUCHGATE_SIGNING_KEY_PEM="$(cat /tmp/signing-key.pem)" node server.js

The corresponding public key is printed on startup and served at GET /signing-key.

Call it from any MCP client:

import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';

const client = new Client({ name: 'my-agent', version: '1.0.0' });
await client.connect(new StreamableHTTPClientTransport(new URL('http://127.0.0.1:8787/mcp')));

const res = await client.callTool({
  name: 'screen_counterparty',
  arguments: { address: '0x…' },
});
console.log(res.content[0].text);

Status

Pre-release. The service contract is production-shaped and stable; the risk analysis behind the score is being rolled out incrementally.

Implemented:

  • MCP endpoint over Streamable HTTP, health check, structured error handling.
  • Signed, reproducible verdict contract (the schema above).
  • Asymmetric (ed25519) signing with a deterministic, key-order-independent canonical evidence serialization, and a published verification key at GET /signing-key.
  • Six real Layer A check families: on-chain agent-identity resolution — a numeric OKX.AI agent ID resolves to its owner wallet via an unauthenticated eth_call to the ERC-8004 identity registry on X Layer (agent identities are ERC-721 tokens; verified against known agent/wallet pairs before shipping), and the owner wallet then runs through the full check suite, so screening an agent by ID is real instead of a skipped input; a claimed agent ID that does not exist on-chain is FLAGged as an unverifiable identity (with typo-aware, non-accusatory wording); EVM address format validation; sanctions screening against the full set of EVM-format digital-currency addresses currently designated on the U.S. OFAC SDN list (100 addresses, snapshot dated 2026-07-19 — refreshable any time via tools/refresh-sanctions.mjs; provenance embedded in data/ofac-sdn-evm.json — a point-in-time snapshot, not a live feed, so a PASS means "not found in this snapshot"); a known high-risk contract check that flags whether the target itself is a known privacy-mixer / anonymizing-protocol contract (data/high-risk-contracts-evm.json — e.g. the Tornado Cash pools, OFAC-delisted in 2025 but still documented mixer infrastructure; each address independently verified on-chain), returned as an elevated-risk FLAG that is explicitly not a sanctions determination; a live wallet-activity check that reads real chain state across six chains (X Layer, Ethereum, BSC, Base, Polygon, Arbitrum) — transaction count, balance, contract code — so a wallet with no track record on any major chain is flagged as elevated counterparty risk, and the exact per-chain snapshot observed is folded into the signed evidence; and a funding-graph one-hop check (fundingGraph.js) that fetches the target's recent transactions from public explorers (Etherscan V2 when a key is configured, keyless Blockscout otherwise) and screens every direct counterparty against the sanctions and known-mixer lists — a wallet recently funded straight out of a mixer pool, or transacting with a sanctions-listed address, is flagged even when the target itself is on no list. Scope limits are stated in every finding (last-N recent window, one hop, native-tx list, only the chains actually checked), and the observed counterparty-set digest is folded into the signed evidence. An unreachable RPC or explorer degrades to an explicit "data unavailable" reason, never a fabricated result.
  • An advisory explanation layer (explanation in the response) that summarizes the verdict in natural language and is structurally unable to change it — see "Advisory explanation" above and layerB.js.
  • Rate limiting (sliding window, per caller IP) and a bounded request-body size on the MCP endpoint, so the public service degrades gracefully under load or abuse instead of unbounded resource use.
  • Usage instrumentation at GET /stats — total calls, unique callers, verdict distribution — with caller identity hashed before being retained, never stored or exposed in the clear (query privacy: this is a confidential-trust product, so who is screening whom is treated as sensitive by default).
  • x402 payment gating on the /mcp endpoint itself (plus the /paid-ping demo route) — see "Payment" above. Unpaid calls receive a real 402 challenge; paid calls are served and settled via the OKX facilitator on X Layer mainnet. Listed and live on OKX.AI as an A2MCP provider.

On the roadmap:

  • The remaining screening rule families: agent track-record consistency and sybil / self-dealing detection (each requires a platform / funding-ancestry data source not yet wired; until then they are explicitly labelled as stubs in every verdict's reasons[]), plus deepening the one-hop counterparty check into multi-hop graph proximity and token-transfer coverage.
  • An optional on-chain verifier contract, so a verdict can be checked on-chain and not only against the published key.

Six check families are live (on-chain agent-identity resolution; address format; full-OFAC-SDN sanctions screening; known high-risk / mixer contract match; live six-chain wallet-activity; funding-graph one-hop counterparty screening); the remaining track-record/sybil families are labelled as stubs in every verdict's reasons[]. A PASS today reflects only the checks that have actually run — it is not yet a comprehensive risk clearance. Do not treat current verdicts as a substitute for your own due diligence until the remaining rule families land.

Tools

  • tools/verify-verdict.mjs — independent verdict verification: recomputes the canonical evidence hash and checks the ed25519 signature against the published key. node tools/verify-verdict.mjs response.json
  • tools/deep-report.mjs — renders a structured markdown counterparty report from a full response JSON (findings, per-chain activity, one-hop counterparty tables, screening history, verification steps). node tools/deep-report.mjs response.json > report.md
  • tools/refresh-sanctions.mjs — regenerates the OFAC SDN snapshot from the official-OFAC-derived source lists, with a --check dry-run diff.

Terms & disclaimer

Vouchgate returns an automated screening signal computed from point-in-time public data. It is not financial, legal, or compliance advice, and not a sanctions determination: a PASS is not a clearance, and a FLAG/BLOCK is not an accusation — callers remain solely responsible for their own decisions and their own compliance obligations. Every verdict response carries this notice (disclaimer + termsUrl fields), and the full terms are in TERMS.md, served live at GET /terms.

Security model

See THREAT_MODEL.md for what this service can and cannot do to your funds, its trust boundaries, and the invariants it is built to hold — most importantly, that a full compromise of this service cannot move or release anyone's money, because it never holds a key or a code path that could.

License

© 2026 LeventLabs. All rights reserved.

About

Counterparty risk screening for autonomous agents, delivered as a pay-per-call service on OKX.AI (X Layer).

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors