# Trade Router (api.traderouter.ai) Non-custodial Solana swap transaction builder and market-cap order router for AI agents. Multi-DEX routing across Raydium, PumpSwap, Orca, Meteora. Jito MEV-protected execution. Ed25519-verified server messages. Private key never leaves the agent's process. - API version: 1.3.0 - REST: https://api.traderouter.ai - WebSocket: wss://api.traderouter.ai/ws - OpenAPI 3.1 (JSON): https://api.traderouter.ai/openapi.json - OpenAPI 3.1 (YAML): https://api.traderouter.ai/openapi.yaml - Threat model: https://api.traderouter.ai/SECURITY.md MCP server (Claude Desktop, Cursor, Cline, ElizaOS, LangChain, any MCP client): - npm: `npx -y @traderouter/trade-router-mcp` — requires TRADEROUTER_PRIVATE_KEY (base58). Latest: 1.0.10. Docs: https://api.traderouter.ai/#mcp - PyPI: `uv add traderouter-mcp` then run `traderouter-mcp` (Python equivalent, latest 1.0.10) - MCP Registry: `ai.traderouter/trade-router-mcp@1.0.10` (isLatest, active). Secondary: `io.github.TradeRouter/trade-router-mcp@1.0.9` - Glama listing: https://glama.ai/mcp/servers/TradeRouter/trade-router-mcp - Cookbook with 7 example agents (5 strategies + ElizaOS + LangChain): https://github.com/TradeRouter/cookbook - Source: https://github.com/TradeRouter/trade-router-mcp (MIT) Safe testing: set `TRADEROUTER_DRY_RUN=true` and every write tool short-circuits to `{ dry_run: true, tool, args }` instead of touching mainnet. Read-only tools still execute. Added in 1.0.9. Trust signals (verifiable): - VirusTotal scan v1.0.10: 0/62 detections on both the npm tarball and the inner mjs (https://www.virustotal.com/gui/file/cf3c2d92a7ab514ef7327197a5b4a603ed844ccac8b269b095f9f834bf2b2fd9) - CI green across Node 18/20/22 with tarball-leak guard: https://github.com/TradeRouter/trade-router-mcp/actions - Branch protection on main (no force-push, required CI checks) - DNS-auth publishing to MCP Registry (no device codes; TXT record on traderouter.ai) - npm publish signed (`dist.signatures` on every release) Other docs: - SKILL.md (OpenClaw / Anthropic Agent Skills format, full implementation guide): https://api.traderouter.ai/SKILL.md - vs-jupiter (honest side-by-side comparison for AI agent builders): https://traderouter.ai/vs-jupiter - integrations (drop-in config snippets for Claude Desktop, Cursor, Cline, Continue, Goose, Zed, ElizaOS, LangChain, LlamaIndex, Solana Agent Kit, GOAT): https://traderouter.ai/integrations - use-cases (recipes for mcap-trigger buy, TWAP DCA, trailing stop, combo orders, sniper, fill log): https://traderouter.ai/use-cases - why (the agent-native thesis, design choices, what we are NOT for): https://traderouter.ai/why - faq (common questions on security, fees, capabilities, integrations; includes JSON-LD FAQPage structured data): https://traderouter.ai/faq - X: https://x.com/trade_router ## Critical invariants - `/swap` returns `data.swap_tx` as **base58 unsigned tx**. - `/protect` expects **base64 signed tx**. - Required pipeline: `base58 decode -> VersionedTransaction deserialize -> sign -> serialize -> base64 encode -> POST /protect`. - `buy` requires `amount`. - `sell` accepts either `amount` (exact token units) or `holdings_percentage` (bps). At least one is required. ## POST /swap Build an unsigned swap transaction. ```json POST https://api.traderouter.ai/swap Content-Type: application/json { "wallet_address": "string", "token_address": "string", "action": "buy | sell", "amount": 100000000, "holdings_percentage": 10000, "slippage": 1500 } ``` Rules: - `amount`: required for buy (lamports), min 1; allowed for sell as exact token amount. - `holdings_percentage`: optional for sell (bps), min 1, max 10000. - `slippage`: bps, API default 500, allowed 100-2500. For low-liquidity/new tokens use 1500-2500. - For `sell`, send at least one of `amount` or `holdings_percentage`. Runtime may accept both. `pool_type` is an open enum (examples: `raydium`, `pumpswap`, `orca`, `meteora`). Handle unknown values gracefully. `"Error running simulation"` means route is unsellable now (dead/rugged pool, zero effective balance, or no route). Do not tight-loop retries. Put token on cooldown and retry later only if strategy requires. ## POST /holdings Scan wallet token balances. ```json POST https://api.traderouter.ai/holdings { "wallet_address": "string" } ``` Set timeout to at least 100s. Endpoint is intended to return sellable tokens, but keep defensive filtering before sell attempts: - `valueNative > 0` ## GET /mcap Return market cap data for token addresses. ```bash GET https://api.traderouter.ai/mcap?tokens=MINT1,MINT2 ``` Query param `tokens`: comma-delimited Solana mint addresses. Returns object keyed by token address with `marketCap`, `pair_address`, `pool_type`, `priceUsd`. Empty object if no tokens or none found. ## GET /flex Generate flex trade card PNG. ```bash GET https://api.traderouter.ai/flex?wallet_address=WALLET&token_address=MINT ``` Returns image/png. 400 on invalid params; 501 if deps not available; 500 on error. ## POST /protect Submit signed transaction (base64), MEV-protected lane. ```bash curl --request POST \ --url https://api.traderouter.ai/protect \ --header 'Content-Type: application/json' \ --data '{"signed_tx_base64":"YOUR_SIGNED_TX"}' ``` Behavior: - Can block until on-chain confirmation; use 30s client timeout. - If timeout occurs, check tx status before retrying (it may have landed). - `503` means protect lane unavailable. Fallback to direct RPC is possible (less safe, no MEV protection). ## Security - **GET /security** returns `server_signing_public_key` and `server_signing_public_key_next` (base58). Use to verify `order_filled.server_signature` (Ed25519 over canonical JSON payload). Key rotation: 14 days; next key published alongside current. - **order_created** includes `params_hash` (SHA-256 hex of canonical params) and optional `server_signature` over params_hash. - **order_filled**: Always verify `server_signature` before signing/submitting. If `already_dispatched` is true, skip sign/submit (idempotent ack). Responsible disclosure: security@traderouter.ai. - **GET /** returns health/status (`{"status":"ok","message":"WebSocket at /ws"}`). ## WebSocket `/ws` Connection sequence (runtime): 1. Connect to `wss://api.traderouter.ai/ws` 2. Server sends `{"type":"challenge","nonce":"..."}` (or fallback `{"type":"subscribed"}` if no nonce is available). 3. Sign the nonce with your wallet, then send `{"action":"register","wallet_address":"...","signature":""}`. 4. On valid signature, server sends `{"type":"registered","wallet_address":"...","authenticated":true}`. 5. Only authenticated sessions can place/manage orders (`sell`, `buy`, `trailing_sell`, `trailing_buy`, `twap_buy`, `twap_sell`, `limit_twap_sell`, `limit_twap_buy`, `trailing_twap_sell`, `trailing_twap_buy`, `limit_trailing_sell`, `limit_trailing_buy`, `limit_trailing_twap_sell`, `limit_trailing_twap_buy`, `check_order`, `list_orders`, `cancel_order`, `extend_order`). Authenticated sessions receive full `order_filled` (including `data.swap_tx`). Metadata-only `order_filled` payloads can still occur for idempotent already-dispatched fills. On disconnect: - reconnect - re-register - check for pending fills - apply staleness check (`triggered_mcap / filled_mcap < 0.85` -> likely stale) Order expiry is silent. Poll `list_orders` / `check_order`. ### sell (limit) `target` (aka targetMcapBps) is bps vs **current mcap at order placement time** (not wallet entry price). Any positive integer; no maximum. ```json {"action":"sell","token_address":"MINT","holdings_percentage":10000,"slippage":1500,"target":20000,"expiry_hours":144} ``` ### buy (limit) ```json {"action":"buy","token_address":"MINT","amount":100000000,"slippage":1500,"target":5000,"expiry_hours":144} ``` ### trailing orders ```json {"action":"trailing_sell","token_address":"MINT","holdings_percentage":10000,"slippage":1500,"trail":1000,"expiry_hours":144} {"action":"trailing_buy","token_address":"MINT","amount":100000000,"slippage":1500,"trail":1000,"expiry_hours":144} ``` `trail` is bps callback from peak/trough. ### order_filled (server push) `order_filled` includes `order_id`, `server_signature`, and optionally `already_dispatched`, `dispatched_at`, `target_bps`/`trail_bps`. When received: 1. If `already_dispatched` is true: skip sign/submit; treat as idempotent ack (fill was already sent). 2. Else: verify `server_signature` (canonical payload + SHA-256 + Ed25519 with GET /security public key). On failure do not sign/submit. 3. Read `data.swap_tx` (base58) 4. Sign and convert to base64 5. Submit via `/protect` Notes: - `filled_mcap` can be `0` or `null` even for valid fills; do not reject fill solely on that field. - Runtime statuses include `success`, `trigger_failed`, and `error`. - Server may also emit `heartbeat` periodically, `error` for invalid actions/payloads, and `echo` for unknown actions. ### Other actions - `{"action":"check_order","order_id":"uuid"}` - `{"action":"list_orders"}` - `{"action":"cancel_order","order_id":"uuid"}` - `{"action":"extend_order","order_id":"uuid","expiry_hours":336}` (max 336) ### twap_buy / twap_sell Time-weighted average price: split total amount into `frequency` slices over `duration` seconds. - twap_sell: `token_address`, `frequency` (1–100), `duration` (seconds, min 60, max 30 days), and either `amount` (raw token units) or `holdings_percentage` (bps, resolved once at creation). Optional `slippage` (default 500). - twap_buy: `token_address`, `frequency`, `duration`, `amount` (SOL lamports). Optional `slippage`. Server sends `twap_order_created` on accept; `twap_execution` for each slice (with `data.swap_tx` and `server_signature` — verify before sign/submit); `twap_order_completed` when all done. Cancel returns `twap_order_cancelled`. ### Combo orders (limit + TWAP, trailing + TWAP, limit + trailing, limit + trailing + TWAP) - **limit_twap_sell / limit_twap_buy:** `token_address`, `target` (bps), `frequency`, `duration`, and for sell: `amount` or `holdings_percentage`; for buy: `amount`. When limit target is crossed, server spawns TWAP; client receives `limit_twap_triggered` then `twap_execution` per slice. - **trailing_twap_sell / trailing_twap_buy:** `token_address`, `trail` (bps), `frequency`, `duration`, and for sell: `amount` or `holdings_percentage`; for buy: `amount`. When trail triggers, server spawns TWAP; client receives `trailing_twap_triggered` then `twap_execution` per slice. - **limit_trailing_sell / limit_trailing_buy:** `token_address`, `target`, `trail`, and for sell: `amount` or `holdings_percentage`; for buy: `amount`. Wait for limit target → trailing phase activates (server sends `limit_trailing_activated`); when trail triggers, single `order_filled` with swap_tx. - **limit_trailing_twap_sell / limit_trailing_twap_buy:** `token_address`, `target`, `trail`, `frequency`, `duration`, and for sell: `amount` or `holdings_percentage`; for buy: `amount`. Limit → trail → on trail trigger, server spawns TWAP; client receives `limit_trailing_twap_triggered` then `twap_execution` per slice. ## DCA Not automatic chaining. Loop: 1. place buy order 2. wait for `order_filled` 3. sign + submit tx 4. place next buy order ## Conservative pacing defaults If no server limits are provided: - REST: <= 2 req/s sustained per wallet (burst <= 5) - WS mutating actions: <= 5 msg/s per wallet - 429 / repeated 5xx: exponential backoff with jitter