Zendiq agent API

Agent-callable trade infrastructure for Solana.

by ZendIQ·MIT license·★ 1 Stars on the repo·GitHub ↗

Files of Zendiq agent API

ZendIQ/main1 file
README.md
Show the full text396 lines

ZendIQ Agent API

ZendIQ gives agents a paid, machine-readable Solana swap triage verdict before they sign. The public surface includes the MCP client, x402 client, budget controls, response contract, and a runnable example.

What's open, and what isn't

This repository contains the complete agent-facing integration surface:

  • MCP server exposing zendiq_screen_token, zendiq_triage_swap and zendiq_optimize_swap
  • x402 payment client
  • Execution client — build, verify, and submit an optimized unsigned swap (/optimize)
  • Autonomous candidate feed and triage loop
  • Decision ledger with a hard local spend ceiling
  • Public request and response contract
  • Payment network read from the live API (GET /v1/agent), with a required spend ceiling on mainnet

The scoring model is intentionally not included. Each response surfaces the individual signals, their observed values, their status, and each factor's point contribution to the score — so an agent can act on any single signal (for example, refuse on a serial-deployer flag) rather than only the headline verdict. But how those signals are derived and combined — the data sources, thresholds, and weighting model — runs behind the hosted /analyse endpoint and remains ZendIQ's proprietary engine. The contract is open so integrators can inspect exactly what is sent, returned, paid for, and acted on.

This repository contains no extension analytics, user telemetry, production deployment configuration, database schema, facilitator wallet, or production credentials. It has fresh history independent of ZendIQ's private backend.

Requirements

  • Node.js 22.5 or newer
  • A Solana keypair holding mainnet USDC for paid calls (it needs no SOL)
  • Network access to ZendIQ's hosted API at https://api.zendiq.ai, where scoring runs — see Where your calls go before running anything.

Quickstart

Start free, with nothing installed. Screening a token needs no wallet, no payment, no key and no clone. This returns a real risk score, usually in a few seconds:

curl -s -X POST https://api.zendiq.ai/v1/agent/analyse-token \
  -H "content-type: application/json" \
  -d '{"mint":"DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263"}'

PowerShell:

Invoke-RestMethod -Method Post https://api.zendiq.ai/v1/agent/analyse-token `
  -ContentType 'application/json' `
  -Body '{"mint":"DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263"}' | Select-Object -ExpandProperty tokenRisk

Python (standard library only):

import json, urllib.request

req = urllib.request.Request(
    "https://api.zendiq.ai/v1/agent/analyse-token",
    data=json.dumps({"mint": "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263"}).encode(),
    headers={"content-type": "application/json"},
)
with urllib.request.urlopen(req, timeout=30) as resp:
    body = json.load(resp)

risk = body["tokenRisk"]
print(risk["symbol"], risk["score"], risk["level"], "signals", body["signals_resolved"])

The response holds tokenRisk (score and level), the 16 signals behind it with coverage in signals_resolved, and an analysisId that /optimize can reuse for 60 s.

Paid calls. The steps below run on your machine. The only calls to ZendIQ are the scoring and /optimize requests to the hosted API; no URL needs setting for those. The npm commands are the same in bash and PowerShell.

1. Install and set a local spend ceiling. budget:init reads the payment network from the API (GET /v1/agent, free) and writes a $1.00 ledger for it under runtime/. It spends nothing. On mainnet every payment is reserved against this ceiling, and nothing here pays without one.

npm ci
npm run budget:init

2. Provide and fund the paying key. This code never generates or writes a mainnet key. Put a Solana keypair you control at runtime/payer-mainnet.key.json: any file solana-keygen writes, for example solana-keygen new -o runtime/payer-mainnet.key.json. Fund its address with a little USDC on Solana mainnet. No SOL is needed: the x402 facilitator pays the payment's network fee. $1 covers 100 calls at $0.01. Use a dedicated wallet, because the ledger only bounds what is paid through this code.

3. Make one paid call ($0.01 in mainnet USDC):

npm run analyse

4. Run the MCP server with the same key and ledger:

npm run mcp

Or skip the clone entirely: npx -y @zendiq/mcp@latest runs the same server from npm. See Connect it to an agent.

The MCP server uses newline-delimited JSON-RPC over stdio. Diagnostics go to stderr so stdout remains a valid MCP transport.

5. Optional: try /optimize build-only. /optimize builds a mainnet swap for a taker. The taker is only a public key and nothing is signed, so any funded mainnet address shows the full plan, simulation and venue decision. For the default 0.003 SOL swap, pick one holding a little more than 0.003 SOL:

npm run optimize -- --taker <ANY_FUNDED_MAINNET_ADDRESS>

This is build-only: it pays $0.02 in USDC and signs nothing, and you cannot sign a transaction built for a wallet you do not control. A taker that cannot fund the trade gets 422 taker_insufficient_balance, uncharged. To land a trade, see Execution.

Set variables in your shell. Nothing in this repository loads a .env file; .env.example only lists the variables for reference.

The first scan of a token typically takes 1–5 s and at most about 12 s; a token scanned by anyone in the last 60 s comes back in under a second. Details: How long a call takes.

A paid call can also hold its response for up to 90 seconds while payment settlement is confirmed on chain, which happens when the facilitator cannot confirm the transfer itself. Allow at least 120 seconds on /analyse and /optimize. MCP hosts often time out a tool call sooner; raise that limit if your host allows it. Details: Settlement can hold the response.

If that ends in 402 with settlement_pending, keep the payment header and retry once with the same header: a payment that landed late is redeemed for one fresh response on the same route, within 24 h. zendiq-client.js and the MCP server do this for you. Details: Retry once after settlement_pending.

Where your calls go

Runs on your machine: the MCP server (a local stdio adapter), x402 payment signing, the budget ledger, the candidate feed, the triage loop, transaction verification and signing, and the demo visualizer.

Calls ZendIQ's hosted API: token screening, /analyse and /optimize. The scoring engine is not in this repository and has no local mode — every score comes from the hosted API.

Calls third parties directly: DexScreener (candidate feed), Jupiter /execute and a Solana RPC (only when you pass --execute), and the x402 facilitator (payment settlement).

The default endpoint is ZendIQ's live hosted API. If you clone this repo and run it without setting a URL, your calls hit our production service and are billed as real x402 payments:

const BASE_URL = process.env.ZENDIQ_API_URL ?? 'https://api.zendiq.ai';

That default is deliberate — it makes the quickstart work without infrastructure. It is not a sandbox. Two consequences worth understanding before you run a loop:

  • Payments are real settlements in mainnet USDC: $0.01 per /analyse, $0.02 per /optimize. They are on-chain transactions, not mocks.
  • npm run watch is an autonomous loop. It pays per candidate until the local budget ceiling stops it. Set budget:init deliberately; it is the only thing bounding spend.

The endpoint can be overridden — ZENDIQ_AGENT_URL for the MCP server, ZENDIQ_API_URL for the examples and the demo runner — but it must point at a ZendIQ Agent API. These are two separate variables reading two separate code paths; setting one does not affect the other. The demo runner is the exception: it passes its ZENDIQ_API_URL to the MCP server it spawns, so there ZENDIQ_AGENT_URL is ignored.

Payment rail: the examples, budget:init and the demo runner read the payment network from the API (GET /v1/agent, field network). AGENT_NETWORK is optional; if set, it must agree with the API or they refuse to run. budget:init is the exception: an explicit AGENT_NETWORK is taken as given there, so a ledger can be created before a server switches. The MCP server reads ZENDIQ_AGENT_NETWORK, which defaults to mainnet; any value other than mainnet or devnet makes its paid tools refuse. The hosted API settles in mainnet USDC, analyses mainnet, and /optimize returns a real mainnet transaction.

No ZendIQ credentials ship in this repository. The paying key is yours, signs your payments, and never leaves your machine.

Connect it to an agent (MCP)

The server speaks the Model Context Protocol over stdio (newline-delimited JSON-RPC), so any MCP-capable client — Claude Desktop, Cursor, Cline, or your own harness — can call it directly. It exposes three tools, one per workflow stage. They are independent entry points, not a required sequence: call whichever matches the question you actually have.

zendiq_screen_token — screen stage. Call it first, whenever you are considering a token and have no trade yet (an agent scanning many fresh mints has none). Free and rate-limited, cacheable across callers. Returns the token risk score, its signal breakdown, signals_resolved coverage, and a cache block (hit, ageSeconds, observedAt) so you can decide whether to force fresh.

Input Type Required Description
mint string yes Base58 mint of the token to screen

zendiq_triage_swap — decide stage. Call it before signing a swap, to decide whether and how to trade it. Paid. Returns the full token score inline (so screening first is optional, never required), plus sandwich exposure, the route and the recommended execution. Builds no transaction.

Input Type Required Description
inputMint string yes Base58 mint being sold
outputMint string yes Base58 mint being bought
amount string yes Amount to sell, in the input mint's atomic units (e.g. "1000000000" for 1 SOL)
slippageBps integer no Slippage tolerance in basis points; omit for the route default

Output (structuredContent):

  • verdict — Safe (route normally), Protect (route through a Jito bundle), or Refuse (do not execute)
  • recommendedExecution — { path, priorityFeeLamports, jitoTipLamports }
  • reasons — plain-language justification

The full risk breakdown (token-risk factors, sandwich exposure, provenance fingerprint) is returned unchanged alongside these committed fields, so the MCP result is byte-identical to the HTTP /analyse response. Each call costs $0.01 in USDC, paid automatically via x402 using the configured keypair.

zendiq_optimize_swap — execute stage. Call it once you have decided to trade and need the transaction. Paid. Returns an unsigned swap transaction (the Jupiter route, or a direct venue or Jito bundle that beats it after every cost by more than 0.1% of the trade, capped at $1), the plan to verify it against, submit instructions, a simulation, an itemised netBenefit, and the same verdict as zendiq_triage_swap. Zero custody — nothing is signed here.

Input Type Required Description
inputMint string yes Base58 mint being sold
outputMint string yes Base58 mint being bought
amount string yes Amount to sell, in the input mint's atomic units
taker string yes Base58 mainnet wallet the swap is built for; must hold the input amount and SOL for fees and rent (a gasless Jupiter Ultra fill is exempt from the SOL)
slippageBps integer no Slippage tolerance in basis points; omit for the route default
method "jito" no Force a Jito bundle venue even where risk scoring would not bundle. plan.choice is then forced; no unbundled route is substituted if none builds. Submit the signed bundle to POST /v1/agent/bundle

This tool also returns the Safe / Protect / Refuse verdict and its reasons, the same as zendiq_triage_swap, but it builds the transaction even on a Refuse, because it assumes the decision to trade has been taken. Read verdict before signing. Each call costs $0.02 in USDC; a build that fails charges nothing.

Payment settles in mainnet USDC from the paying wallet, and the swap is routed against mainnet liquidity for taker. The API accepts the paying wallet as taker; the examples here keep the two keys apart and refuse a taker key that is the payer.

Register it in your MCP client's config. The package runs straight from npm, with no clone. In Claude Code it is one command:

claude mcp add --scope user zendiq -- npx -y @zendiq/mcp@latest

For any other client, paste the JSON below into its user config (~/.claude.json for Claude Code, or the client's own config file), which connects straight away. A project .mcp.json works too, but the client asks you to approve the server first.

{
  "mcpServers": {
    "zendiq": {
      "command": "npx",
      "args": ["-y", "@zendiq/mcp@latest"]
    }
  }
}

That is enough for zendiq_screen_token, which is free and needs no wallet. The paid tools need two more things, both kept in ~/.zendiq (or AGENT_STATE_DIR):

  1. A spend ceiling: npx -y @zendiq/mcp@latest budget init 1.00 writes budget-mainnet.json. It spends nothing. npx -y @zendiq/mcp@latest budget shows what has been spent.
  2. A paying key holding mainnet USDC at ~/.zendiq/payer-mainnet.key.json: any file solana-keygen writes, for example solana-keygen new -o ~/.zendiq/payer-mainnet.key.json. It is never generated for you. At startup the server logs the paying address and the file it came from to stderr.

The server refuses to keep keys or the ledger inside node_modules or the npx cache, because npm deletes those folders without warning and a funded key there would be lost.

From a clone, point the client at the file instead (paths must be absolute):

{
  "mcpServers": {
    "zendiq": {
      "command": "node",
      "args": ["/absolute/path/to/ZendIQ-Agent-API/src/mcp-server.js"]
    }
  }
}

There the key and ledger default to runtime/ beside the clone, as for the examples.

Env Default Purpose
ZENDIQ_AGENT_URL https://api.zendiq.ai API base URL. The default is ZendIQ's live service — see Where your calls go
ZENDIQ_AGENT_NETWORK mainnet Payment rail: mainnet or devnet; must match the network the API settles on. Any other value makes the paid tools refuse
AGENT_STATE_DIR ~/.zendiq (npm), runtime/ (clone) Where the paying key and the budget ledger live
ZENDIQ_AGENT_BUDGET_FILE <state dir>/budget-<network>.json Budget ledger every payment is reserved against. On mainnet a paid call refuses without it. See What the budget ceiling guarantees
ZENDIQ_AGENT_KEYPAIR — Devnet only. Solana keypair JSON that holds USDC; signs x402 payments only. Refused on mainnet

On mainnet the paying key is only loaded together with a mainnet ledger.

Diagnostics go to stderr so stdout stays a clean JSON-RPC transport. Transport is stdio only — the standard local MCP transport every client supports; a remote/HTTP transport is not currently provided.

To sanity-check the wiring without a client, drive it by hand — initialize then tools/list need no keypair or payment:

printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18"}}' \
  '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' | npx -y @zendiq/mcp@latest

PowerShell:

'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18"}}',
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' | npx -y @zendiq/mcp@latest

Autonomous agent

The complete test agent is public under examples/. It watches DexScreener's live Solana boost feed, enriches each candidate, pays ZendIQ for a verdict, and records whether it would refuse, protect, or route the trade normally.

npm run budget:init
npm run feed
npm run watch

watch begins with USDC as a control, so a run proves that the agent discriminates rather than refusing everything. It then prints a run ledger containing triage spend, refused candidates, protected candidates, and candidates cleared for direct routing.

The autonomous agent is advisory — it reports the recommended path and fees without claiming that money moved. To build and submit a real optimized swap, see Execution below. Keys and budget ledgers live under the gitignored runtime/ directory.

What the budget ceiling guarantees

The ledger (examples/budget.js) is a hard ceiling on what the paying wallet spends on ZendIQ calls. It holds under these conditions, and only these:

  • What it counts. Every x402 payment made through ZendIQClient: the examples, the demo runner, and the MCP server, which all share that one payment path. Each payment is reserved before it is signed and resolved afterwards. An outcome that might have been charged is counted as spent, so the ceiling over-counts rather than under-counts.
  • On mainnet the paying key is fenced.
    • It is loaded only by loadAgentSigner, from its own file, runtime/payer-mainnet.key.json, and only with a mainnet ledger attached; without one it throws.
    • Advanced override for CI: AGENT_SECRET_SEED takes the paying key as a JSON array of exactly 32 bytes (a seed). If a key file is also present and holds a different key, nothing starts and both addresses are printed.
    • A ledger records the one key that pays against it, and a key is bound to one ledger.
    • The wallet a swap is built for (--taker, ZENDIQ_TAKER_KEYPAIR) is a separate key, and it is refused if it is the payer.
  • More than one process may share a ledger. Every change takes a lockfile (<ledger>.lock, holding the owner's PID) and re-reads the file, so two processes cannot both reserve the same remaining budget.
  • A crashed process does not wedge the ledger. If the lock holder is killed mid-operation, the next spender sees that its PID is gone and takes the lock over at once. If the holder is still alive, the spender waits up to 5 s and then refuses to pay. The error names the PID and the lockfile; it never hangs. A lock older than 30 s is taken over regardless. Manual recovery: stop every agent using that ledger, then delete <ledger>.lock.
  • What it cannot see:
    • Anything signed with the paying key outside this code, for example a script that reads the key file itself.
    • The funding transfer into the paying wallet, and any later top-ups.
    • Network fees and token-account rent. The facilitator pays the payment's network fee, so in normal use the paying wallet spends USDC only.
    • Swaps, which the taker signs and pays for, from a different wallet.

What "exact reconciliation" means. Compare two lists for one paying address: every USDC transfer out of that address on chain, and every ledger entry in state settled, matched by transaction signature (the entry's note, which holds the settlement signature from the server's PAYMENT-RESPONSE). For a key used only through this code they match one to one. The single expected exception is a settled entry noted unconfirmed_settlement_may_have_landed, which may have no transfer, because the ledger counts a payment it cannot rule out. Inflows (funding, top-ups) are not ledger entries and are not part of the comparison. Any outflow with no matching entry means the key was used outside the ledger.

Reconciling a fresh key

  1. Generate the key and fund it with USDC. Record the funding transaction's signature; it is the only expected inflow.
  2. Create a new ledger for it (AGENT_NETWORK=mainnet node examples/budget.js init <ceilingUsd>). Never point a new key at a ledger that already has entries: its history belongs to another key and can never reconcile. init refuses to overwrite an existing file, so move an old one aside first.
  3. Make paid calls only through this code. The first load binds the key to the ledger (payer in the ledger, runtime/payer-bindings.json).
  4. List the paying address's USDC token-account history on chain. Drop the funding transfer and any top-ups. The remaining outflows' signatures must equal the note signatures of the ledger's settled entries, and their amounts must equal each entry's atomic (USDC, 6 decimals).

Execution — build a signable swap (/optimize)

/analyse is advisory. /optimize goes one step further: it returns an unsigned swap transaction, with the venue, priority fee and MEV posture chosen from the same risk model and a net-benefit comparison across venues — plus the plan, an on-chain simulation, and the net-benefit arithmetic. You verify the bytes against the stated plan, then sign and submit with your own wallet. ZendIQ never holds a key.

npm run budget:init
# Stop at simulation — pays $0.02 USDC, prints the plan + simulation, signs nothing:
npm run optimize -- --taker <YOUR_MAINNET_PUBKEY>

# Real landing — signs the returned tx and submits it as the response's submit block directs:
export ZENDIQ_TAKER_KEYPAIR=/path/to/mainnet-keypair.json
npm run optimize -- --taker <YOUR_MAINNET_PUBKEY> --execute

In PowerShell, set the key path with $env:ZENDIQ_TAKER_KEYPAIR = 'C:\path\to\mainnet-keypair.json' instead of export.

The swap routes on mainnet, so --taker must be a wallet that holds the input amount and SOL for fees and rent; the x402 payment is a separate USDC transfer from the paying wallet. By default the example stops at simulation and spends nothing on-chain — pass --execute (with ZENDIQ_TAKER_KEYPAIR) to sign and land a real swap. On jupiter_ultra the example submits through Jupiter's /execute; on jupiter_swap it sends through SOLANA_RPC_URL, which defaults to the public mainnet RPC; on a Jito bundle venue it posts the signed transaction to ZendIQ's /v1/agent/bundle and polls until it lands. The response carries the unsigned transaction, the plan, the submit instructions for the chosen venue, the simulation result, and the netBenefit breakdown — everything needed to confirm the transaction matches the stated intent before signing.

The example also prints the verdict and the venue decision. The venue decision lists every candidate with its net value, priority fee, Jito tip, modelled sandwich cost and bundle landing risk, the margin it had to beat, and why the winner won. It is plan.venueDecision from the response, so an agent can check the choice rather than trust it. With --execute the example will not sign a trade whose verdict is Refuse, just as it will not sign one whose simulation failed; pass --sign-refused to override it.

Demo visualizer

A local spectator view that renders one real swap-triage call as a live, animated sequence across two transports side by side — the MCP agent tool and the direct x402 HTTP rail — then verifies that both returned the same token-risk evidence fingerprint. It then runs /optimize for the same swap and shows the execution sequence — Optimize → Sign → Land — ending at an on-chain simulation (or a real mainnet landing with --execute). Every value on screen is real: live risk score, real USDC settlement, real transaction. Nothing is staged.

Prerequisites
  • Node.js 22.5+ and npm ci already run.
  • The funded paying key and budget ledger from the Quickstart, steps 1–2. The runner uses the same runtime/payer-mainnet.key.json and runtime/budget-mainnet.json; the key signs USDC payment authorizations only and never leaves your machine (runtime/ is gitignored). No SOL is required. The runner never creates a mainnet ledger itself.
  • The visualizer and runner run locally; the calls they display go to the hosted API. The runner reads ZENDIQ_API_URL and hands the same URL to its MCP lane, so ZENDIQ_AGENT_URL has no effect here.
Run it

Start the visualizer in one terminal:

npm run demo

Open http://127.0.0.1:4173, then in a second terminal:

npm run demo:run -- --mint DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263 --taker <YOUR_MAINNET_PUBKEY>

To finish with a real on-chain landing, add --execute and set ZENDIQ_TAKER_KEYPAIR to the mainnet keypair for --taker. Without --execute, the execution lane stops at simulation and spends nothing on-chain.

Both lanes fill in — request → 402 → USDC authorization signed → payment settled → analysis returned — and the footer shows Verified · identical token-risk evidence with the shared fingerprint. The fingerprint covers the deterministic token screening (mint, score, level, signals, inputs); the live route economics shown per lane (sandwich exposure, price impact) are re-fetched on each call and can drift a fraction of a percent with price movement between the two sequential requests. The runner exits 0 on a fingerprint match, non-zero on mismatch. The event stream deliberately excludes payment authorizations, secrets, RPC URLs, and complete wallet addresses.

Troubleshooting
  • Preflight failed / fetch failed — the runner needs both the Agent API and the visualizer (npm run demo) up at the same time. Start the visualizer first and leave it running.
  • Payment was rejected — the paying wallet holds too little mainnet USDC. Fund it (Quickstart, step 2) and re-run; a rejected payment is never charged.
  • UI stays on "Waiting for an agent call…" — the page is passive; it only fills once demo:run emits events. Confirm the runner printed Demo complete.

Contract

The complete machine-readable contract is openapi.json (OpenAPI 3.1): every endpoint, request body, response shape, error code, and worked examples. It is the same file served at https://zendiq.ai/openapi.json. For the live prices, rate limits and field-stability tiers, GET /v1/agent on the API is authoritative. The summary below covers what most integrations need.

POST /v1/agent/analyse-token — free, rate-limited (screen stage)

{ "mint": "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263" }

Screen a token by mint, with no trade size. Returns the token risk score, its signal breakdown, signals_resolved coverage, and a cache block (hit, ageSeconds, observedAt) — a cached score reports the slot and time it was computed at, never the current one. No payment; rate-limited per IP.

POST /v1/agent/analyse — paid (decide stage)

{
  "inputMint": "So11111111111111111111111111111111111111112",
  "outputMint": "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263",
  "amount": "500000000",
  "slippageBps": 100
}

Stable response fields:

  • verdict: Safe, Protect, or Refuse
  • recommendedExecution.path
  • recommendedExecution.priorityFeeLamports
  • recommendedExecution.jitoTipLamports
  • reasons
  • disclaimer

Additional fields are experimental and may change within v1. Stable fields are additive-only within v1; breaking changes ship under a new API version.

POST /v1/agent/optimize

Same request body as /analyse plus a taker public key. Returns an unsigned swap transaction, the plan (venue, slippage, priority fee, and the venueDecision comparison behind the venue), a simulation result, the netBenefit breakdown, and the same verdict, confidence and reasons as /analyse. Zero custody — you verify, sign, and submit. Priced per call in USDC.

The venue is chosen by risk, so read plan.venue rather than assuming one. Jupiter Ultra is used for low-risk trades and for sandwich-driven risk, where its upstream MEV protection is the instrument that addresses the exposure; it sizes the priority fee itself. The Jupiter Swap API (Quote + Build) is used when risk scoring calls for a specific priority fee, which Ultra cannot honour — there plan.priorityFee reports the fee actually applied, read back out of the build, and the route carries no upstream MEV protection.

A direct venue is quoted alongside and replaces the Jupiter route only when it beats it after every cost: priority fee, Jito tip, expected sandwich loss, and for a bundle its landing risk (an assumed 5% chance of paying for a rebuild). It must win by a margin of 0.1% of the trade, capped at $1: enough that quote noise cannot flip the venue, small enough that a real saving still wins. Unprotected Raydium competes only on a Safe verdict. The Jito bundle venues (Raydium + Jito, Jupiter Swap + Jito) compete on every verdict; on Safe they step aside while ZendIQ's shared Jito submission budget is busy, so protected trades keep it. plan.venueDecision shows every candidate's arithmetic; often the answer is Jupiter.

Submission differs by venue — follow the returned submit object rather than hardcoding a path. On jupiter_ultra, sign transaction and POST { signedTransaction, requestId } to https://lite-api.jup.ag/ultra/v1/execute; submitting through your own RPC instead forfeits Ultra's MEV protection and invalidates the netBenefit figures. On jupiter_swap and raydium there is no requestId (it is null) and no /execute step — sign and send to your own RPC, with the priority fee already inside the transaction. Send a raydium transaction promptly: Raydium embeds its own blockhash and submit.lastValidBlockHeight is null. On a Jito bundle venue (submit.method: "jito_bundle"), sign and POST { "signedTransaction": "<base64>" } to /v1/agent/bundle on this API (free): ZendIQ forwards those exact bytes to Jito and reports landing. Never send a bundle transaction to an RPC yourself; it would sit in the public mempool and still pay the tip.

netBenefit.netUsd = expectedMevLossUsd − zendiqFeeUsd − jitoTipUsd − jupiterPlatformFeeUsd − priorityFeeUsd: the sandwich loss the route avoids, less every fee you pay to execute it. It is stated only on routes that claim MEV protection (Jupiter Ultra and the bundle venues). jupiterPlatformFeeUsd is Jupiter's own fee on an Ultra trade (0–50 bps by pair, 2 bps on SOL–USDC; already inside the quoted amounts) and 0 elsewhere. priorityFeeUsd is decoded from the transaction: what your taker pays, 0 on a bundle or a gasless fill. The 5,000-lamport base signature fee is the same on every route and is not included. When a cost cannot be priced, netUsd is null and netUsdBasis says why.

/optimize returns the same verdict (Safe / Protect / Refuse) as /analyse but does not refuse to build: a Refuse still comes back with a transaction, even for a token that scores CRITICAL. Read verdict and tokenRisk before signing, and do not sign a Refuse unless you mean to trade against it.

When token screening does not complete

Screening can time out or fail upstream. Neither endpoint blocks on it — both still return 200 — but the gap is always explicit, never a clean-looking score:

  • tokenRisk is { mint, available: false, error, assumedScore: 50, assumedLevel: "HIGH", note }, with no score or level.
  • Fees and overall risk are sized as if the token scored HIGH, not as if it scored 0.
  • /analyse fails closed: verdict is Protect with confidence: "low", degraded contains token_screening:<reason>, and reasons states that screening did not complete. A Protect reached this way means the token was not checked, not that it was found risky.
  • /optimize still returns a transaction. The example prints token risk UNAVAILABLE and warns before signing, but does not refuse — check tokenRisk.available yourself if your agent should.

Security

Never commit keypairs, seeds, .env, budget ledgers, or RPC URLs containing credentials. This repository configures a mandatory pre-push secret scan through .githooks/pre-push; install either gitleaks or trufflehog before pushing.

1# ZendIQ Agent API
2 
3ZendIQ gives agents a paid, machine-readable Solana swap triage verdict before they sign. The public surface includes the MCP client, x402 client, budget controls, response contract, and a runnable example.
4 
5## What's open, and what isn't
6 
7This repository contains the complete agent-facing integration surface:
8 
9- MCP server exposing `zendiq_screen_token`, `zendiq_triage_swap` and `zendiq_optimize_swap`
10- x402 payment client
11- Execution client — build, verify, and submit an optimized unsigned swap (`/optimize`)
12- Autonomous candidate feed and triage loop
13- Decision ledger with a hard local spend ceiling
14- Public request and response contract
15- Payment network read from the live API (`GET /v1/agent`), with a required spend ceiling on mainnet
16 
17The scoring model is intentionally not included. Each response surfaces the individual signals, their observed values, their status, and each factor's point contribution to the score — so an agent can act on any single signal (for example, refuse on a serial-deployer flag) rather than only the headline verdict. But **how** those signals are derived and combined — the data sources, thresholds, and weighting model — runs behind the hosted `/analyse` endpoint and remains ZendIQ's proprietary engine. The contract is open so integrators can inspect exactly what is sent, returned, paid for, and acted on.
18 
19This repository contains no extension analytics, user telemetry, production deployment configuration, database schema, facilitator wallet, or production credentials. It has fresh history independent of ZendIQ's private backend.
20 
21## Requirements
22 
23- Node.js 22.5 or newer
24- A Solana keypair holding mainnet USDC for paid calls (it needs no SOL)
25- Network access to ZendIQ's hosted API at `https://api.zendiq.ai`, where scoring runs — see [Where your calls go](#where-your-calls-go) before running anything.
26 
27## Quickstart
28 
29**Start free, with nothing installed.** Screening a token needs no wallet, no payment, no key and no clone. This returns a real risk score, usually in a few seconds:
30 
31```bash
32curl -s -X POST https://api.zendiq.ai/v1/agent/analyse-token \
33 -H "content-type: application/json" \
34 -d '{"mint":"DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263"}'
35```
36 
37PowerShell:
38 
39```powershell
40Invoke-RestMethod -Method Post https://api.zendiq.ai/v1/agent/analyse-token `
41 -ContentType 'application/json' `
42 -Body '{"mint":"DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263"}' | Select-Object -ExpandProperty tokenRisk
43```
44 
45Python (standard library only):
46 
47```python
48import json, urllib.request
49 
50req = urllib.request.Request(
51 "https://api.zendiq.ai/v1/agent/analyse-token",
52 data=json.dumps({"mint": "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263"}).encode(),
53 headers={"content-type": "application/json"},
54)
55with urllib.request.urlopen(req, timeout=30) as resp:
56 body = json.load(resp)
57 
58risk = body["tokenRisk"]
59print(risk["symbol"], risk["score"], risk["level"], "signals", body["signals_resolved"])
60```
61 
62The response holds `tokenRisk` (score and level), the 16 `signals` behind it with coverage in `signals_resolved`, and an `analysisId` that `/optimize` can reuse for 60 s.
63 
64**Paid calls.** The steps below run on your machine. The only calls to ZendIQ are the scoring and `/optimize` requests to the hosted API; no URL needs setting for those. The `npm` commands are the same in bash and PowerShell.
65 
66**1. Install and set a local spend ceiling.** `budget:init` reads the payment network from the API (`GET /v1/agent`, free) and writes a `$1.00` ledger for it under `runtime/`. It spends nothing. On mainnet every payment is reserved against this ceiling, and nothing here pays without one.
67 
68```bash
69npm ci
70npm run budget:init
71```
72 
73**2. Provide and fund the paying key.** This code never generates or writes a mainnet key. Put a Solana keypair you control at `runtime/payer-mainnet.key.json`: any file `solana-keygen` writes, for example `solana-keygen new -o runtime/payer-mainnet.key.json`. Fund its address with a little USDC on Solana mainnet. No SOL is needed: the x402 facilitator pays the payment's network fee. $1 covers 100 calls at `$0.01`. Use a dedicated wallet, because the ledger only bounds what is paid through this code.
74 
75**3. Make one paid call** (`$0.01` in mainnet USDC):
76 
77```bash
78npm run analyse
79```
80 
81**4. Run the MCP server with the same key and ledger:**
82 
83```bash
84npm run mcp
85```
86 
87Or skip the clone entirely: `npx -y @zendiq/mcp@latest` runs the same server from npm. See [Connect it to an agent](#connect-it-to-an-agent-mcp).
88 
89The MCP server uses newline-delimited JSON-RPC over stdio. Diagnostics go to stderr so stdout remains a valid MCP transport.
90 
91**5. Optional: try `/optimize` build-only.** `/optimize` builds a mainnet swap for a `taker`. The `taker` is only a public key and nothing is signed, so any funded mainnet address shows the full plan, simulation and venue decision. For the default 0.003 SOL swap, pick one holding a little more than 0.003 SOL:
92 
93```bash
94npm run optimize -- --taker <ANY_FUNDED_MAINNET_ADDRESS>
95```
96 
97This is **build-only**: it pays $0.02 in USDC and signs nothing, and you cannot sign a transaction built for a wallet you do not control. A `taker` that cannot fund the trade gets `422 taker_insufficient_balance`, uncharged. To land a trade, see [Execution](#execution--build-a-signable-swap-optimize).
98 
99Set variables in your shell. Nothing in this repository loads a `.env` file; `.env.example` only lists the variables for reference.
100 
101The first scan of a token typically takes 1–5 s and at most about 12 s; a token scanned by anyone in the last 60 s comes back in under a second. Details: [How long a call takes](https://zendiq.ai/agents/#latency).
102 
103A paid call can also hold its response for up to 90 seconds while payment settlement is confirmed on chain, which happens when the facilitator cannot confirm the transfer itself. Allow at least 120 seconds on `/analyse` and `/optimize`. MCP hosts often time out a tool call sooner; raise that limit if your host allows it. Details: [Settlement can hold the response](https://zendiq.ai/agents/#settlement).
104 
105If that ends in `402` with `settlement_pending`, keep the payment header and retry once with the same header: a payment that landed late is redeemed for one fresh response on the same route, within 24 h. `zendiq-client.js` and the MCP server do this for you. Details: [Retry once after settlement_pending](https://zendiq.ai/agents/#redeem).
106 
107## Where your calls go
108 
109**Runs on your machine:** the MCP server (a local stdio adapter), x402 payment signing, the budget ledger, the candidate feed, the triage loop, transaction verification and signing, and the demo visualizer.
110 
111**Calls ZendIQ's hosted API:** token screening, `/analyse` and `/optimize`. The scoring engine is not in this repository and has no local mode — every score comes from the hosted API.
112 
113**Calls third parties directly:** DexScreener (candidate feed), Jupiter `/execute` and a Solana RPC (only when you pass `--execute`), and the x402 facilitator (payment settlement).
114 
115**The default endpoint is ZendIQ's live hosted API.** If you clone this repo and run it without setting a URL, your calls hit our production service and are billed as real x402 payments:
116 
117```js
118const BASE_URL = process.env.ZENDIQ_API_URL ?? 'https://api.zendiq.ai';
119```
120 
121That default is deliberate — it makes the quickstart work without infrastructure. It is not a sandbox. Two consequences worth understanding before you run a loop:
122 
123- **Payments are real settlements** in mainnet USDC: `$0.01` per `/analyse`, `$0.02` per `/optimize`. They are on-chain transactions, not mocks.
124- **`npm run watch` is an autonomous loop.** It pays per candidate until the local budget ceiling stops it. Set `budget:init` deliberately; it is the only thing bounding spend.
125 
126The endpoint can be overridden — `ZENDIQ_AGENT_URL` for the MCP server, `ZENDIQ_API_URL` for the examples and the demo runner — but it must point at a ZendIQ Agent API. These are two separate variables reading two separate code paths; setting one does not affect the other. The demo runner is the exception: it passes its `ZENDIQ_API_URL` to the MCP server it spawns, so there `ZENDIQ_AGENT_URL` is ignored.
127 
128**Payment rail:** the examples, `budget:init` and the demo runner read the payment network from the API (`GET /v1/agent`, field `network`). `AGENT_NETWORK` is optional; if set, it must agree with the API or they refuse to run. `budget:init` is the exception: an explicit `AGENT_NETWORK` is taken as given there, so a ledger can be created before a server switches. The MCP server reads `ZENDIQ_AGENT_NETWORK`, which defaults to `mainnet`; any value other than `mainnet` or `devnet` makes its paid tools refuse. The hosted API settles in **mainnet USDC**, analyses mainnet, and `/optimize` returns a real mainnet transaction.
129 
130No ZendIQ credentials ship in this repository. The paying key is yours, signs your payments, and never leaves your machine.
131 
132## Connect it to an agent (MCP)
133 
134The server speaks the Model Context Protocol over stdio (newline-delimited JSON-RPC), so any MCP-capable client — Claude Desktop, Cursor, Cline, or your own harness — can call it directly. It exposes three tools, one per workflow stage. They are independent entry points, not a required sequence: call whichever matches the question you actually have.
135 
136**`zendiq_screen_token`** — **screen** stage. Call it first, whenever you are considering a token and have no trade yet (an agent scanning many fresh mints has none). **Free and rate-limited**, cacheable across callers. Returns the token risk score, its signal breakdown, `signals_resolved` coverage, and a `cache` block (`hit`, `ageSeconds`, `observedAt`) so you can decide whether to force fresh.
137 
138| Input | Type | Required | Description |
139|---|---|---|---|
140| `mint` | string | yes | Base58 mint of the token to screen |
141 
142**`zendiq_triage_swap`** — **decide** stage. Call it before signing a swap, to decide whether and how to trade it. Paid. Returns the **full token score inline** (so screening first is optional, never required), plus sandwich exposure, the route and the recommended execution. Builds no transaction.
143 
144| Input | Type | Required | Description |
145|---|---|---|---|
146| `inputMint` | string | yes | Base58 mint being sold |
147| `outputMint` | string | yes | Base58 mint being bought |
148| `amount` | string | yes | Amount to sell, in the input mint's atomic units (e.g. `"1000000000"` for 1 SOL) |
149| `slippageBps` | integer | no | Slippage tolerance in basis points; omit for the route default |
150 
151Output (`structuredContent`):
152 
153- `verdict` — `Safe` (route normally), `Protect` (route through a Jito bundle), or `Refuse` (do not execute)
154- `recommendedExecution` — `{ path, priorityFeeLamports, jitoTipLamports }`
155- `reasons` — plain-language justification
156 
157The full risk breakdown (token-risk factors, sandwich exposure, provenance fingerprint) is returned unchanged alongside these committed fields, so the MCP result is byte-identical to the HTTP `/analyse` response. Each call costs `$0.01` in USDC, paid automatically via x402 using the configured keypair.
158 
159**`zendiq_optimize_swap`** — **execute** stage. Call it once you have decided to trade and need the transaction. Paid. Returns an unsigned swap transaction (the Jupiter route, or a direct venue or Jito bundle that beats it after every cost by more than 0.1% of the trade, capped at $1), the `plan` to verify it against, `submit` instructions, a simulation, an itemised `netBenefit`, and the same verdict as `zendiq_triage_swap`. Zero custody — nothing is signed here.
160 
161| Input | Type | Required | Description |
162|---|---|---|---|
163| `inputMint` | string | yes | Base58 mint being sold |
164| `outputMint` | string | yes | Base58 mint being bought |
165| `amount` | string | yes | Amount to sell, in the input mint's atomic units |
166| `taker` | string | yes | Base58 mainnet wallet the swap is built for; must hold the input amount and SOL for fees and rent (a gasless Jupiter Ultra fill is exempt from the SOL) |
167| `slippageBps` | integer | no | Slippage tolerance in basis points; omit for the route default |
168| `method` | `"jito"` | no | Force a Jito bundle venue even where risk scoring would not bundle. `plan.choice` is then `forced`; no unbundled route is substituted if none builds. Submit the signed bundle to `POST /v1/agent/bundle` |
169 
170This tool also returns the Safe / Protect / Refuse `verdict` and its `reasons`, the same as `zendiq_triage_swap`, but it builds the transaction even on a Refuse, because it assumes the decision to trade has been taken. Read `verdict` before signing. Each call costs `$0.02` in USDC; a build that fails charges nothing.
171 
172Payment settles in mainnet USDC from the paying wallet, and the swap is routed against mainnet liquidity for `taker`. The API accepts the paying wallet as `taker`; the examples here keep the two keys apart and refuse a taker key that is the payer.
173 
174Register it in your MCP client's config. The package runs straight from npm, with no clone. In Claude Code it is one command:
175 
176```bash
177claude mcp add --scope user zendiq -- npx -y @zendiq/mcp@latest
178```
179 
180For any other client, paste the JSON below into its user config (`~/.claude.json` for Claude Code, or the client's own config file), which connects straight away. A project `.mcp.json` works too, but the client asks you to approve the server first.
181 
182```json
183{
184 "mcpServers": {
185 "zendiq": {
186 "command": "npx",
187 "args": ["-y", "@zendiq/mcp@latest"]
188 }
189 }
190}
191```
192 
193That is enough for `zendiq_screen_token`, which is free and needs no wallet. The paid tools need two more things, both kept in `~/.zendiq` (or `AGENT_STATE_DIR`):
194 
1951. A spend ceiling: `npx -y @zendiq/mcp@latest budget init 1.00` writes `budget-mainnet.json`. It spends nothing. `npx -y @zendiq/mcp@latest budget` shows what has been spent.
1962. A paying key holding mainnet USDC at `~/.zendiq/payer-mainnet.key.json`: any file `solana-keygen` writes, for example `solana-keygen new -o ~/.zendiq/payer-mainnet.key.json`. It is never generated for you. At startup the server logs the paying address and the file it came from to stderr.
197 
198The server refuses to keep keys or the ledger inside `node_modules` or the npx cache, because npm deletes those folders without warning and a funded key there would be lost.
199 
200From a clone, point the client at the file instead (paths must be absolute):
201 
202```json
203{
204 "mcpServers": {
205 "zendiq": {
206 "command": "node",
207 "args": ["/absolute/path/to/ZendIQ-Agent-API/src/mcp-server.js"]
208 }
209 }
210}
211```
212 
213There the key and ledger default to `runtime/` beside the clone, as for the examples.
214 
215| Env | Default | Purpose |
216|---|---|---|
217| `ZENDIQ_AGENT_URL` | `https://api.zendiq.ai` | API base URL. **The default is ZendIQ's live service** — see [Where your calls go](#where-your-calls-go) |
218| `ZENDIQ_AGENT_NETWORK` | `mainnet` | Payment rail: `mainnet` or `devnet`; must match the network the API settles on. Any other value makes the paid tools refuse |
219| `AGENT_STATE_DIR` | `~/.zendiq` (npm), `runtime/` (clone) | Where the paying key and the budget ledger live |
220| `ZENDIQ_AGENT_BUDGET_FILE` | `<state dir>/budget-<network>.json` | Budget ledger every payment is reserved against. On mainnet a paid call refuses without it. See [What the budget ceiling guarantees](#what-the-budget-ceiling-guarantees) |
221| `ZENDIQ_AGENT_KEYPAIR` | — | **Devnet only.** Solana keypair JSON that holds USDC; signs x402 payments only. Refused on mainnet |
222 
223On mainnet the paying key is only loaded together with a mainnet ledger.
224 
225Diagnostics go to stderr so stdout stays a clean JSON-RPC transport. Transport is stdio only — the standard local MCP transport every client supports; a remote/HTTP transport is not currently provided.
226 
227To sanity-check the wiring without a client, drive it by hand — `initialize` then `tools/list` need no keypair or payment:
228 
229```bash
230printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18"}}' \
231 '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' | npx -y @zendiq/mcp@latest
232```
233 
234PowerShell:
235 
236```powershell
237'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18"}}',
238'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' | npx -y @zendiq/mcp@latest
239```
240 
241## Autonomous agent
242 
243The complete test agent is public under `examples/`. It watches DexScreener's live Solana boost feed, enriches each candidate, pays ZendIQ for a verdict, and records whether it would refuse, protect, or route the trade normally.
244 
245```bash
246npm run budget:init
247npm run feed
248npm run watch
249```
250 
251`watch` begins with USDC as a control, so a run proves that the agent discriminates rather than refusing everything. It then prints a run ledger containing triage spend, refused candidates, protected candidates, and candidates cleared for direct routing.
252 
253The autonomous agent is advisory — it reports the recommended path and fees without claiming that money moved. To build and submit a real optimized swap, see **Execution** below. Keys and budget ledgers live under the gitignored `runtime/` directory.
254 
255## What the budget ceiling guarantees
256 
257The ledger (`examples/budget.js`) is a hard ceiling on what the paying wallet spends on ZendIQ calls. It holds under these conditions, and only these:
258 
259- **What it counts.** Every x402 payment made through `ZendIQClient`: the examples, the demo runner, and the MCP server, which all share that one payment path. Each payment is reserved before it is signed and resolved afterwards. An outcome that might have been charged is counted as spent, so the ceiling over-counts rather than under-counts.
260- **On mainnet the paying key is fenced.**
261 - It is loaded only by `loadAgentSigner`, from its own file, `runtime/payer-mainnet.key.json`, and only with a mainnet ledger attached; without one it throws.
262 - Advanced override for CI: `AGENT_SECRET_SEED` takes the paying key as a JSON array of exactly 32 bytes (a seed). If a key file is also present and holds a different key, nothing starts and both addresses are printed.
263 - A ledger records the one key that pays against it, and a key is bound to one ledger.
264 - The wallet a swap is built for (`--taker`, `ZENDIQ_TAKER_KEYPAIR`) is a separate key, and it is refused if it is the payer.
265- **More than one process may share a ledger.** Every change takes a lockfile (`<ledger>.lock`, holding the owner's PID) and re-reads the file, so two processes cannot both reserve the same remaining budget.
266- **A crashed process does not wedge the ledger.** If the lock holder is killed mid-operation, the next spender sees that its PID is gone and takes the lock over at once. If the holder is still alive, the spender waits up to 5 s and then **refuses to pay**. The error names the PID and the lockfile; it never hangs. A lock older than 30 s is taken over regardless. **Manual recovery:** stop every agent using that ledger, then delete `<ledger>.lock`.
267- **What it cannot see:**
268 - Anything signed with the paying key outside this code, for example a script that reads the key file itself.
269 - The funding transfer into the paying wallet, and any later top-ups.
270 - Network fees and token-account rent. The facilitator pays the payment's network fee, so in normal use the paying wallet spends USDC only.
271 - Swaps, which the taker signs and pays for, from a different wallet.
272 
273**What "exact reconciliation" means.** Compare two lists for one paying address: every **USDC transfer out of that address** on chain, and every ledger entry in state `settled`, **matched by transaction signature** (the entry's `note`, which holds the settlement signature from the server's `PAYMENT-RESPONSE`). For a key used only through this code they match one to one. The single expected exception is a `settled` entry noted `unconfirmed_settlement_may_have_landed`, which may have no transfer, because the ledger counts a payment it cannot rule out. Inflows (funding, top-ups) are not ledger entries and are not part of the comparison. Any outflow with no matching entry means the key was used outside the ledger.
274 
275**Reconciling a fresh key**
276 
2771. Generate the key and fund it with USDC. Record the funding transaction's signature; it is the only expected inflow.
2782. Create a **new** ledger for it (`AGENT_NETWORK=mainnet node examples/budget.js init <ceilingUsd>`). Never point a new key at a ledger that already has entries: its history belongs to another key and can never reconcile. `init` refuses to overwrite an existing file, so move an old one aside first.
2793. Make paid calls only through this code. The first load binds the key to the ledger (`payer` in the ledger, `runtime/payer-bindings.json`).
2804. List the paying address's USDC token-account history on chain. Drop the funding transfer and any top-ups. The remaining outflows' signatures must equal the `note` signatures of the ledger's `settled` entries, and their amounts must equal each entry's `atomic` (USDC, 6 decimals).
281 
282## Execution — build a signable swap (`/optimize`)
283 
284`/analyse` is advisory. `/optimize` goes one step further: it returns an **unsigned** swap transaction, with the venue, priority fee and MEV posture chosen from the same risk model and a net-benefit comparison across venues — plus the plan, an on-chain simulation, and the net-benefit arithmetic. You verify the bytes against the stated plan, then sign and submit with your own wallet. ZendIQ never holds a key.
285 
286```bash
287npm run budget:init
288# Stop at simulation — pays $0.02 USDC, prints the plan + simulation, signs nothing:
289npm run optimize -- --taker <YOUR_MAINNET_PUBKEY>
290 
291# Real landing — signs the returned tx and submits it as the response's submit block directs:
292export ZENDIQ_TAKER_KEYPAIR=/path/to/mainnet-keypair.json
293npm run optimize -- --taker <YOUR_MAINNET_PUBKEY> --execute
294```
295 
296In PowerShell, set the key path with `$env:ZENDIQ_TAKER_KEYPAIR = 'C:\path\to\mainnet-keypair.json'` instead of `export`.
297 
298The swap routes on **mainnet**, so `--taker` must be a wallet that holds the input amount and SOL for fees and rent; the x402 payment is a separate USDC transfer from the paying wallet. By default the example **stops at simulation and spends nothing on-chain** — pass `--execute` (with `ZENDIQ_TAKER_KEYPAIR`) to sign and land a real swap. On `jupiter_ultra` the example submits through Jupiter's `/execute`; on `jupiter_swap` it sends through `SOLANA_RPC_URL`, which defaults to the public mainnet RPC; on a Jito bundle venue it posts the signed transaction to ZendIQ's `/v1/agent/bundle` and polls until it lands. The response carries the unsigned `transaction`, the `plan`, the `submit` instructions for the chosen venue, the `simulation` result, and the `netBenefit` breakdown — everything needed to confirm the transaction matches the stated intent before signing.
299 
300The example also prints the verdict and the venue decision. The venue decision lists every candidate with its net value, priority fee, Jito tip, modelled sandwich cost and bundle landing risk, the margin it had to beat, and why the winner won. It is `plan.venueDecision` from the response, so an agent can check the choice rather than trust it. With `--execute` the example will not sign a trade whose `verdict` is `Refuse`, just as it will not sign one whose simulation failed; pass `--sign-refused` to override it.
301 
302## Demo visualizer
303 
304A local spectator view that renders one real swap-triage call as a live, animated sequence across two transports side by side — the MCP agent tool and the direct x402 HTTP rail — then verifies that both returned the same token-risk evidence fingerprint. It then runs `/optimize` for the same swap and shows the execution sequence — **Optimize → Sign → Land** — ending at an on-chain simulation (or a real mainnet landing with `--execute`). Every value on screen is real: live risk score, real USDC settlement, real transaction. Nothing is staged.
305 
306### Prerequisites
307 
308- Node.js 22.5+ and `npm ci` already run.
309- The funded paying key and budget ledger from the [Quickstart](#quickstart), steps 1–2. The runner uses the same `runtime/payer-mainnet.key.json` and `runtime/budget-mainnet.json`; the key signs USDC payment authorizations only and never leaves your machine (`runtime/` is gitignored). **No SOL is required.** The runner never creates a mainnet ledger itself.
310- The visualizer and runner run locally; the calls they display go to the hosted API. The runner reads `ZENDIQ_API_URL` and hands the same URL to its MCP lane, so `ZENDIQ_AGENT_URL` has no effect here.
311 
312### Run it
313 
314Start the visualizer in one terminal:
315 
316```bash
317npm run demo
318```
319 
320Open `http://127.0.0.1:4173`, then in a second terminal:
321 
322```bash
323npm run demo:run -- --mint DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263 --taker <YOUR_MAINNET_PUBKEY>
324```
325 
326To finish with a real on-chain landing, add `--execute` and set `ZENDIQ_TAKER_KEYPAIR` to the mainnet keypair for `--taker`. Without `--execute`, the execution lane stops at simulation and spends nothing on-chain.
327 
328Both lanes fill in — request → `402` → USDC authorization signed → payment settled → analysis returned — and the footer shows **Verified · identical token-risk evidence** with the shared fingerprint. The fingerprint covers the deterministic token screening (mint, score, level, signals, inputs); the live route economics shown per lane (sandwich exposure, price impact) are re-fetched on each call and can drift a fraction of a percent with price movement between the two sequential requests. The runner exits `0` on a fingerprint match, non-zero on mismatch. The event stream deliberately excludes payment authorizations, secrets, RPC URLs, and complete wallet addresses.
329 
330### Troubleshooting
331 
332- **`Preflight failed` / `fetch failed`** — the runner needs both the Agent API and the visualizer (`npm run demo`) up at the same time. Start the visualizer first and leave it running.
333- **`Payment was rejected`** — the paying wallet holds too little mainnet USDC. Fund it ([Quickstart](#quickstart), step 2) and re-run; a rejected payment is never charged.
334- **UI stays on "Waiting for an agent call…"** — the page is passive; it only fills once `demo:run` emits events. Confirm the runner printed `Demo complete`.
335 
336## Contract
337 
338The complete machine-readable contract is [`openapi.json`](openapi.json) (OpenAPI 3.1): every endpoint, request body, response shape, error code, and worked examples. It is the same file served at <https://zendiq.ai/openapi.json>. For the live prices, rate limits and field-stability tiers, `GET /v1/agent` on the API is authoritative. The summary below covers what most integrations need.
339 
340`POST /v1/agent/analyse-token` — **free**, rate-limited (screen stage)
341 
342```json
343{ "mint": "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263" }
344```
345 
346Screen a token by mint, with no trade size. Returns the token risk score, its signal breakdown, `signals_resolved` coverage, and a `cache` block (`hit`, `ageSeconds`, `observedAt`) — a cached score reports the slot and time it was computed at, never the current one. No payment; rate-limited per IP.
347 
348`POST /v1/agent/analyse` — paid (decide stage)
349 
350```json
351{
352 "inputMint": "So11111111111111111111111111111111111111112",
353 "outputMint": "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263",
354 "amount": "500000000",
355 "slippageBps": 100
356}
357```
358 
359Stable response fields:
360 
361- `verdict`: `Safe`, `Protect`, or `Refuse`
362- `recommendedExecution.path`
363- `recommendedExecution.priorityFeeLamports`
364- `recommendedExecution.jitoTipLamports`
365- `reasons`
366- `disclaimer`
367 
368Additional fields are experimental and may change within `v1`. Stable fields are additive-only within `v1`; breaking changes ship under a new API version.
369 
370`POST /v1/agent/optimize`
371 
372Same request body as `/analyse` plus a `taker` public key. Returns an **unsigned** swap `transaction`, the `plan` (venue, slippage, priority fee, and the `venueDecision` comparison behind the venue), a `simulation` result, the `netBenefit` breakdown, and the same `verdict`, `confidence` and `reasons` as `/analyse`. Zero custody — you verify, sign, and submit. Priced per call in USDC.
373 
374The venue is chosen by risk, so read `plan.venue` rather than assuming one. **Jupiter Ultra** is used for low-risk trades and for sandwich-driven risk, where its upstream MEV protection is the instrument that addresses the exposure; it sizes the priority fee itself. The **Jupiter Swap API** (Quote + Build) is used when risk scoring calls for a specific priority fee, which Ultra cannot honour — there `plan.priorityFee` reports the fee actually applied, read back out of the build, and the route carries no upstream MEV protection.
375 
376A direct venue is quoted alongside and replaces the Jupiter route only when it beats it after every cost: priority fee, Jito tip, expected sandwich loss, and for a bundle its landing risk (an assumed 5% chance of paying for a rebuild). It must win by a margin of 0.1% of the trade, capped at $1: enough that quote noise cannot flip the venue, small enough that a real saving still wins. Unprotected **Raydium** competes only on a Safe verdict. The Jito bundle venues (**Raydium + Jito**, **Jupiter Swap + Jito**) compete on every verdict; on Safe they step aside while ZendIQ's shared Jito submission budget is busy, so protected trades keep it. `plan.venueDecision` shows every candidate's arithmetic; often the answer is Jupiter.
377 
378**Submission differs by venue** — follow the returned `submit` object rather than hardcoding a path. On `jupiter_ultra`, sign `transaction` and POST `{ signedTransaction, requestId }` to `https://lite-api.jup.ag/ultra/v1/execute`; submitting through your own RPC instead forfeits Ultra's MEV protection and invalidates the `netBenefit` figures. On `jupiter_swap` and `raydium` there is no `requestId` (it is `null`) and no `/execute` step — sign and send to your own RPC, with the priority fee already inside the transaction. Send a `raydium` transaction promptly: Raydium embeds its own blockhash and `submit.lastValidBlockHeight` is `null`. On a Jito bundle venue (`submit.method: "jito_bundle"`), sign and POST `{ "signedTransaction": "<base64>" }` to `/v1/agent/bundle` on this API (free): ZendIQ forwards those exact bytes to Jito and reports landing. Never send a bundle transaction to an RPC yourself; it would sit in the public mempool and still pay the tip.
379 
380`netBenefit.netUsd = expectedMevLossUsd − zendiqFeeUsd − jitoTipUsd − jupiterPlatformFeeUsd − priorityFeeUsd`: the sandwich loss the route avoids, less every fee you pay to execute it. It is stated only on routes that claim MEV protection (Jupiter Ultra and the bundle venues). `jupiterPlatformFeeUsd` is Jupiter's own fee on an Ultra trade (0–50 bps by pair, 2 bps on SOL–USDC; already inside the quoted amounts) and `0` elsewhere. `priorityFeeUsd` is decoded from the transaction: what your taker pays, `0` on a bundle or a gasless fill. The 5,000-lamport base signature fee is the same on every route and is not included. When a cost cannot be priced, `netUsd` is `null` and `netUsdBasis` says why.
381 
382`/optimize` returns the same `verdict` (Safe / Protect / Refuse) as `/analyse` but does not refuse to build: a `Refuse` still comes back with a transaction, even for a token that scores `CRITICAL`. Read `verdict` and `tokenRisk` before signing, and do not sign a `Refuse` unless you mean to trade against it.
383 
384### When token screening does not complete
385 
386Screening can time out or fail upstream. Neither endpoint blocks on it — both still return `200` — but the gap is always explicit, never a clean-looking score:
387 
388- `tokenRisk` is `{ mint, available: false, error, assumedScore: 50, assumedLevel: "HIGH", note }`, with no `score` or `level`.
389- Fees and overall risk are sized as if the token scored `HIGH`, not as if it scored 0.
390- `/analyse` fails closed: `verdict` is `Protect` with `confidence: "low"`, `degraded` contains `token_screening:<reason>`, and `reasons` states that screening did not complete. A `Protect` reached this way means the token was **not checked**, not that it was found risky.
391- `/optimize` still returns a transaction. The example prints `token risk UNAVAILABLE` and warns before signing, but does not refuse — check `tokenRisk.available` yourself if your agent should.
392 
393## Security
394 
395Never commit keypairs, seeds, `.env`, budget ledgers, or RPC URLs containing credentials. This repository configures a mandatory pre-push secret scan through `.githooks/pre-push`; install either [gitleaks](https://github.com/gitleaks/gitleaks) or [trufflehog](https://github.com/trufflesecurity/trufflehog) before pushing.
396 

Discussion

Alternatives

API and interface designGuides stable API and interface design. Use when designing APIs, module boundaries, or any public interface. Use when creating REST or GraphQL endpoints, defining type contracts between modules, or establishing boundaries between frontend and backend.Coding · MITContext7Pulls up-to-date, version-specific library docs and code examples into the prompt so the AI stops inventing old APIs.Coding · MITContext7 Documentation LookupFetch up-to-date documentation and code examples for any library, framework, SDK, CLI tool, or cloud service. Use whenever the user asks about a specific library — even well-known ones like React, Next.js, Prisma, Express, Tailwind, Django, or Spring Boot — because training data may not reflect recent API changes or version updates. Always use for: API syntax questions, configuration options, version migration issues, "how do I" questions mentioning a library name, debugging that involves library-specific behavior, setup instructions, and CLI tool usage. Use even when you think you know the answer. Do not rely on training data for API details, signatures, or configuration options — they are frequently out of date. Prefer this over web search for library documentation.Coding · MITAdaptyv Bio Foundry APIHow to use the Adaptyv Bio Foundry API and Python SDK for protein experiment design, submission, and results retrieval. Use this skill whenever the user mentions Adaptyv, Foundry API, protein binding assays, protein screening experiments, BLI/SPR assays, thermostability assays, or wants to submit protein sequences for experimental characterization. Also trigger when code imports `adaptyv`, `adaptyv_sdk`, or `FoundryClient`, or references `foundry-api-public.adaptyvbio.com`.Science · MIT