Content
<p align="center">
<img src="https://raw.githubusercontent.com/evidiq/evidiq-assay-mcp/main/assets/evidiq-assay.png" width="200" alt="EVIDIQ Assay" />
</p>
<h1 align="center">EVIDIQ Assay</h1>
<p align="center"><strong>Decode what a transaction will actually do, before an agent signs it.</strong></p>
<p align="center">
Decode · Screen · Simulate · Attest — calldata, EIP-712 payloads, and unsigned transactions turned into plain-language intent, with a signed report anyone can re-verify.
</p>
<p align="center">
<a href="https://evidiq.dev">evidiq.dev</a> ·
<a href="https://evidiq.dev/docs/assay">Assay Docs</a> ·
<a href="https://mcp.evidiq.dev/assay/skill.md">Agent Skill</a> ·
<a href="https://github.com/evidiq/evidiq">EVIDIQ Main</a> ·
<a href="https://github.com/evidiq/evidiq-assay-mcp">Assay MCP</a>
</p>
<p align="center">
<a href="https://mcp.evidiq.dev/assay/mcp"><img src="https://img.shields.io/badge/MCP%20Server-Active-3CCF4E?style=flat-square" alt="MCP Server active" /></a> <a href="https://eips.ethereum.org/EIPS/eip-712"><img src="https://img.shields.io/badge/Decodes-calldata%20%2B%20EIP--712-0284C7?style=flat-square" alt="Decodes calldata and EIP-712" /></a> <a href="https://www.oklink.com/xlayer"><img src="https://img.shields.io/badge/X%20Layer-USDT0-3CCF4E?style=flat-square" alt="X Layer USDT0" /></a> <a href="https://mcp.evidiq.dev/assay/x402"><img src="https://img.shields.io/badge/x402-0.005%E2%80%930.03%20USDT0-2563EB?style=flat-square" alt="x402: 0.005 to 0.03 USDT0" /></a> <a href="https://web3.okx.com/onchainos/dev-docs/payments/service-seller-sdk"><img src="https://img.shields.io/badge/Payments-Official%20OKX%20SDK-121212?style=flat-square&logo=okx&logoColor=white" alt="Official OKX Payment SDK" /></a> <a href="https://www.okx.ai/agents/9727"><img src="https://img.shields.io/badge/OKX.AI-Agent%20%239727%20Listed-121212?style=flat-square&logo=okx&logoColor=white" alt="OKX.AI Agent 9727 listed" /></a> <a href="./LICENSE"><img src="https://img.shields.io/badge/License-MIT-3DA639?style=flat-square" alt="License: MIT" /></a>
</p>
---
Agents hold keys now. They swap, bridge, approve, stake, and sign typed data
without any idea what the bytes mean. An agent handed
`0x095ea7b3…ffffffffffffffff…` has no way to know it is about to give an unlimited
spending allowance to an address it has never seen. Humans get a wallet UI that
tries to explain this. Agents get nothing.
**EVIDIQ Assay is that explanation, as a service.**
Give it calldata, an EIP-712 payload, or an unsigned transaction. It decodes the
intent from a vendored selector database and real ABIs, unwraps `multicall` and
Safe-style wrappers to find the call that actually matters, screens the target's
deployed bytecode, and returns findings with a verdict and a signed report. Same
payload, same verdict, every time.
**Assay never signs and never broadcasts, and it never accepts a private key.**
## What it does
- **Plain-language intent** — "Approve 0xSpender… to spend an unlimited amount of USDT0 on your behalf", not a hex blob.
- **Approval analysis** — unlimited and excessive allowances, `setApprovalForAll`, Permit and Permit2 grants, and whether that spender already had an allowance.
- **Typed-data decoding** — an off-chain `Permit` costs no gas and never appears in transaction history, which is exactly why it is the modern drain vector. Assay spells it out in full.
- **Recursive unwrap** — `multicall`, `execute`, `aggregate`, Permit2 batches, Safe `execTransaction`. The dangerous call is usually one level down, and a decoder that stops at the wrapper is worse than none.
- **Counterparty screening** — proxy and upgradeability slots, owner privileges, mint/pause/blacklist/fee-setter surface, self-destruct, no code at the target.
- **Simulation** — net asset deltas per address, pinned to a block so the result is reproducible.
- **Signed attestation** — `attest_intent` binds the decoded intent and verdict to a content digest and anchors it, so a counterparty or an audit can confirm the check happened over exactly these bytes.
- **Honest ignorance** — an unknown selector or opaque bytecode is reported as unknown and can never produce a `PASS`.
- **Pay per check** — five paid tools at `0.005–0.03 USDT0`; five preflight and verification tools are free.
## Route to Assay when
Use Assay **between building a transaction and signing it**: before an approval,
before signing typed data, before sending value to an address the agent has not
transacted with, or when you need to prove afterwards what a transaction was
understood to do at the moment it was authorised.
Do **not** send it keys — ever. Do not route agent reputation here (that is
**EVIDIQ Core**; a reputable counterparty can still hand you a hostile approval).
Endpoint and Skill scanning is **Sentinel**, source code is **Warden**, dependencies
are **Lineage**, and durable action logging is **Vault**.
A natural chain: `decode_transaction` → `assess_approval` → sign elsewhere →
`append_record` (Vault).
## Use it from any agent
```bash
# Read the public Skill document
curl -s https://mcp.evidiq.dev/assay/skill.md
# Inspect current x402 pricing discovery
curl -s https://mcp.evidiq.dev/assay/x402
# Connect the remote MCP server (Claude Code)
claude mcp add --transport http evidiq-assay https://mcp.evidiq.dev/assay/mcp
```
Public endpoints:
| Endpoint | Purpose |
|----------|---------|
| `https://mcp.evidiq.dev/assay/mcp` | Remote MCP transport |
| `https://mcp.evidiq.dev/assay/skill.md` | Agent-readable usage and safety guide |
| `https://mcp.evidiq.dev/assay/x402` | x402 v2 pricing and payment discovery |
| `https://mcp.evidiq.dev/assay/health` | Service health |
| `https://evidiq.dev/docs/assay` | Technical documentation |
## MCP tools
### Paid analysis
| Tool | Cost | Atomic | Description |
|------|------|-------:|-------------|
| `decode_transaction` | `0.005 USDT0` | `5000` | Decode calldata, an EIP-712 payload, or an unsigned transaction into intent plus findings |
| `assess_approval` | `0.01 USDT0` | `10000` | Allowance analysis, including the spender's existing on-chain allowance |
| `simulate_transaction` | `0.015 USDT0` | `15000` | Simulate against a pinned block and report net asset deltas per address |
| `screen_counterparty` | `0.02 USDT0` | `20000` | Bytecode screening: upgradeability, owner privileges, pause/blacklist/mint surface |
| `attest_intent` | `0.03 USDT0` | `30000` | Bind intent and verdict to a digest, sign it, and anchor the digest on 0G |
### Free preflight and verification
| Tool | Cost | Description |
|------|------|-------------|
| `assay_capabilities` | Free | Rule catalog with severities and which rules are heuristic; supported chains, selector-set version, limits, pricing |
| `validate_payload` | Free | Parse-check the input and return **finding counts by severity** and the detected payload kind |
| `estimate_cost` | Free | Exact price for a paid tool; with no argument, the whole table |
| `verify_assay_report` | Free | Recompute the report digest and verify its EIP-191 signature |
| `get_artifact` | Free | Retrieve a stored report or attestation by id, within its TTL |
## Recommended workflow
Settlement happens **before** a paid tool runs, so a malformed argument is still a
paid call. Preflight for free first:
1. `assay_capabilities` — rule set and selector set versions, supported chains, prices.
2. `validate_payload` — confirms the payload kind and returns counts by severity, so you know whether a full decode is worth buying.
3. `estimate_cost` — the exact price of the operation you intend to run.
4. One paid call per request.
5. `verify_assay_report` — free, offline verification of what you received.
## Supported chains
| Chain | Id |
|-------|---:|
| X Layer | `196` |
| Ethereum | `1` |
| Base | `8453` |
| BNB Smart Chain | `56` |
| Arbitrum One | `42161` |
| Polygon | `137` |
| Optimism | `10` |
Any other chain id is reported as `chain: "unsupported"` and excluded from the
verdict. A chain Assay cannot read is never reported as clean.
## What a report proves, and what it does not
- It **does** prove that this exact payload, under this rule set and selector set
version, at this block, produced this decode and this verdict. Re-running gives an
identical digest, and `verify_assay_report` checks the signature offline.
- It **does not** prove the transaction is safe. Assay cannot see the
counterparty's intent, has no oracle for price impact, and does not know what you
meant to do. **An unknown selector means unknown, not benign.**
- Findings that follow from arithmetic (an amount at `2^256-1`) or from bytecode
presence can be `blocker` or `high`. Anything inferred from a heuristic is capped
at `medium` and flagged as heuristic in `assay_capabilities`.
- When a chain read is unavailable, the report says `onchain: "unavailable"` with the
reason, downgrades the findings that needed it, and does **not** upgrade the
verdict.
## Safety
- **Assay never signs and never broadcasts.** The only key it holds is its own report signer. `attest_intent` signs a report *about* a transaction, never the transaction.
- **Never send it a private key or a mnemonic.** Inputs are screened for key and mnemonic shapes before anything else runs; a hit refuses the whole request, logs only the rule id, and stores nothing.
- No caller payload on disk. Artifacts live in a bounded in-memory cache addressed by digest.
- Logs carry rule ids, counts, digests, and block numbers — never raw calldata, which can contain a signature pasted by mistake.
- Chain access is read-only: `eth_call`, `eth_getCode`, `eth_getStorageAt`. No write endpoints, no keys in RPC config.
- No "safe" or "audited" claim is made. Assay reports what it decoded and what its rules found.
## Pricing and x402
| Operation | Cost | Token | Network | Atomic |
|-----------|------|-------|---------|-------:|
| `decode_transaction` | `0.005` | USDT0 | X Layer (`eip155:196`) | `5000` |
| `assess_approval` | `0.01` | USDT0 | X Layer (`eip155:196`) | `10000` |
| `simulate_transaction` | `0.015` | USDT0 | X Layer (`eip155:196`) | `15000` |
| `screen_counterparty` | `0.02` | USDT0 | X Layer (`eip155:196`) | `20000` |
| `attest_intent` | `0.03` | USDT0 | X Layer (`eip155:196`) | `30000` |
| all five free tools | Free | — | — | — |
Asset: USDT0 (6 decimals) on X Layer (`eip155:196`), contract `0x779ded0c9e1022225f8e0630b35a9b54be713736`.
Payment is always settled on X Layer regardless of which chain the analysed
transaction targets. Assay reads other chains; it is only ever paid on one.
### Official OKX Payment SDK
Payment verification and settlement run through the **official OKX Onchain OS Payment SDK**:
| Package | Role |
|---------|------|
| [`@okxweb3/x402-core`](https://www.npmjs.com/package/@okxweb3/x402-core) | `OKXFacilitatorClient` (HMAC-SHA256 OKX REST auth) and `x402ResourceServer` |
| [`@okxweb3/x402-evm`](https://www.npmjs.com/package/@okxweb3/x402-evm) | `ExactEvmScheme` — the EVM `exact` scheme server implementation |
The OKX facilitator verifies each authorization and settles it on X Layer; Assay
keeps ownership of the decoder, the selector database, the rule set, and report
signing. Each immutable per-tool price reaches the SDK as an explicit USD₮0 **atomic
asset amount** rather than a USD string, so neither the fee nor its token can be
substituted by currency conversion.
The facilitator is treated as the authority on settlement: when it reports success,
the call is served, even if the response carries no transaction hash. A `pending` or
`timeout` response with a hash goes into a bounded settlement-status poll. Overriding
that verdict is what once made a working integration look broken.
Integration guide: [OKX Onchain OS — integrate via SDK](https://web3.okx.com/onchainos/dev-docs/payments/service-seller-sdk).
## Proven on-chain
Every paid tool on EVIDIQ Assay is gated by x402 v2 payments settled on-chain via the official OKX Payment SDK on X Layer mainnet (`eip155:196`).
| Tool | Amount | Settlement tx | Result |
|------|--------|---------------|--------|
| `decode_transaction` | `0.005 USD₮0` | [`0x90c7032c…84cd`](https://www.oklink.com/xlayer/tx/0x90c7032ca0a3b3b0f1c2ac790063ff88954947c16b2af927a133c5067c8d84cd) | Decoded `approve(address,uint256)` unlimited allowance; `UNLIMITED_APPROVAL` blocker finding; Verdict `BLOCK`; Relayer OKX Facilitator (`0xba848e…`), Block `66370642` |
## Architecture
```mermaid
flowchart TB
agent["<b>AI agent / wallet</b><br/>MCP client"]
request{"Tool call<br/>free or paid?"}
agent -->|POST /assay/mcp| request
free["Free preflight<br/>capabilities · validate_payload<br/>estimate · verify · get_artifact"]
gate["x402 v2 gate<br/>EIP-3009 exact · pay per check"]
xlayer[("X Layer<br/>USD₮0 · eip155:196")]
request -->|free helper| free
request -->|paid check| gate
gate -. verify and settle .-> xlayer
subgraph assay["EVIDIQ Assay trust boundary — no keys, no signing, no payload stored"]
direction TB
guard["1. Secret guard<br/>refuse keys and mnemonics"]
detect["2. Payload detect<br/>calldata · EIP-712 · unsigned tx"]
decode["3. Decode + unwrap<br/>selectors · ABI · multicall · Safe"]
rules["4. Rule engine<br/>approvals · authority · targets"]
report["5. Signed report<br/>intent · verdict · SHA-256 · EIP-191"]
guard --> detect --> decode --> rules --> report
end
chains[("Read-only RPC<br/>eth_call · getCode · getStorageAt<br/>pinned block")]
rules -. bounded reads .-> chains
zerog[("0G<br/>attestation digest anchor")]
report -. attest_intent .-> zerog
free --> guard
gate --> guard
response["<b>MCP response</b><br/>intent + findings + verdict"]
report --> response
classDef client fill:#312e81,stroke:#a78bfa,color:#ffffff,stroke-width:2px;
classDef payment fill:#052e16,stroke:#4ade80,color:#ffffff,stroke-width:2px;
classDef core fill:#0f172a,stroke:#38bdf8,color:#ffffff,stroke-width:2px;
classDef output fill:#4c1d95,stroke:#c4b5fd,color:#ffffff,stroke-width:2px;
class agent,request client;
class free,gate,xlayer payment;
class guard,detect,decode,rules,report core;
class response,zerog,chains output;
style assay fill:#0f172a,stroke:#38bdf8,color:#e0f2fe,stroke-width:2px;
```
## Self-host
Requirements: Node.js `22+` and npm.
```bash
npm install
npm run build
npm start
```
Or run the container:
```bash
docker build -t evidiq-assay .
docker run -d --name evidiq-assay -p 3000:3000 --env-file .env evidiq-assay
```
Local routes: `POST /mcp` · `GET /skill.md` · `GET /x402` · `GET /health`
No volume is required, by design. The selector and rule databases ship inside the
image under `data/`; verify them in the container rather than in the build log — a
missing selector database turns every decode into `unknown_selector`, which looks
like a working service returning nothing useful.
### Configuration
Copy `.env.example` to `.env` and set parameters:
```bash
# Server
PORT=3000
HOSTNAME=0.0.0.0
PUBLIC_BASE_URL=https://mcp.evidiq.dev/assay
# Official OKX Payment SDK — all three credentials are required together
OKX_API_KEY=...
OKX_SECRET_KEY=...
OKX_PASSPHRASE=...
OKX_BASE_URL=https://web3.okx.com
# x402 v2 — X Layer mainnet / USDT0 (prices are fixed per tool; no X402_PRICE)
X402_CHAIN=eip155:196
X402_ASSET=0x779ded0c9e1022225f8e0630b35a9b54be713736
X402_PAY_TO=0x2a8efe3093278bb4bd3b2d9c7b5ba992ca4fc9b0
X402_DOMAIN_NAME=USD₮0
X402_DOMAIN_VERSION=1
X402_RPC=https://rpc.xlayer.tech
# Report signing (EIP-191) — accepted with or without the 0x prefix
ASSAY_SIGNER_PRIVATE_KEY=...
ASSAY_SIGNER_ADDRESS=0x...
# Read-only RPC per supported chain. Never a key, never a write endpoint.
ASSAY_RPC_196=https://rpc.xlayer.tech
ASSAY_RPC_1=...
ASSAY_RPC_8453=...
ASSAY_RPC_56=...
ASSAY_RPC_42161=...
ASSAY_RPC_137=...
ASSAY_RPC_10=...
# Limits
ASSAY_MAX_INPUT_BYTES=131072
ASSAY_MAX_RPC_CALLS=12
ASSAY_RPC_BUDGET_MS=6000
ASSAY_ARTIFACT_TTL_MS=600000
# 0G anchoring for attestations — digest only, never payloads
OG_PRIVATE_KEY=...
OG_STORAGE_RPC=https://evmrpc.0g.ai
OG_STORAGE_INDEXER=https://indexer-storage-turbo.0g.ai
```
## Development
```bash
npm install # install dependencies
npm run build # compile TypeScript to dist/
npm test # detect, secrets, decode, typed, recursive, rules, engine, report, x402
npm run dev # start local watch server
```
Start with `lib/assay/secrets.ts`: refusing credentials comes before any feature,
because it is the one failure that cannot be undone. Then the decoder, with real
mainnet calldata captured as fixtures so tests stay deterministic and offline. Every
rule needs a true positive **and** a near-miss negative — a checker with false
positives gets switched off, and one with false negatives is worse than none.
## Links
- **Website** — https://evidiq.dev
- **Assay documentation** — https://evidiq.dev/docs/assay
- **Live MCP endpoint** — https://mcp.evidiq.dev/assay/mcp
- **Agent Skill** — https://mcp.evidiq.dev/assay/skill.md
- **x402 discovery** — https://mcp.evidiq.dev/assay/x402
- **Service health** — https://mcp.evidiq.dev/assay/health
- **OKX Payment SDK guide** — https://web3.okx.com/onchainos/dev-docs/payments/service-seller-sdk
- **EVIDIQ main repository** — https://github.com/evidiq/evidiq
- **x402 Protocol** — https://x402.org
## TypeScript SDK
A typed client for the live endpoint lives in [`sdk/index.ts`](sdk/index.ts) — 10 tools (5 free, 5 paid). Free tools answer a bare call; paid tools run the x402 flow automatically (402 challenge → `pay` hook → replay with the `x-payment` header). No key lives in the file.
```ts
import { AssayClient } from "./sdk/index.js";
const client = new AssayClient(); // endpoint defaults to https://mcp.evidiq.dev/assay/mcp
// free
const caps = await client.callTool("assay_capabilities", {});
// paid — settle the 402 challenge via the constructor's pay hook, or omit it
// to receive a PaymentRequiredError carrying the full x402 v2 challenge
const result = await client.callTool("some_paid_tool", { arg: "value" });
```
The `pay` hook receives the decoded x402 v2 challenge (`{ x402Version, resource, accepts[] }` — payTo, asset, amount) and returns the value for the `x-payment` header, e.g. an EIP-3009 `transferWithAuthorization` settled via the official OKX SDK. Without a hook, paid calls throw `PaymentRequiredError` so the caller can settle however it wants.
## License
MIT © 2026 EVIDIQ — see [LICENSE](./LICENSE). Part of the [EVIDIQ](https://github.com/evidiq/evidiq) trust and execution layer for the AI agent economy.
Connection Info
You Might Also Like
Train-in-Silence
The first Task-Aware MCP server and automated VRAM calculator for LLM...
stacklit
108,000 lines of code. 4,000 tokens of index. One command makes any repo...
AppClaw
AI-powered mobile automation agent — describe what you want in plain...
pdf-mcp
Production-ready MCP server for PDF processing with intelligent caching....
kotadb
Local-only code intelligence API for AI developer workflows (Bun +...
gemini-api-docs-mcp
A remote HTTP MCP server for searching Google Gemini API documentation.