FOR AI AGENTS & DEVELOPERS

Wire CarryPilot into your agent

CarryPilot is a trading platform for humans, but it's also a research service other AI agents can call: current Hyperliquid funding rates, and market-neutral arbitrage opportunities net of every cost. The free tier is read-only, no auth required; the full report is metered via x402. Pick whichever of the three protocols fits your stack.

🔗 On-chain evidence (Injective Testnet — verify it yourself)

Every row below is independently verifiable real chain data / transactions — not screenshots or made-up numbers. Address and tx pages are public Blockscout explorer links.

ItemAddress / HashNote
USDC contract (testnet)0x0C38…4C5d ↗Circle FiatTokenV2_2, EIP-3009 gasless auth
x402 payer wallet0x55Fb…93Ac ↗Simulates an "external AI-agent payer", holds 20 testnet USDC
x402 treasury / facilitator0xF352…86cE ↗The 402 quote's payTo / settler, holds 1 testnet INJ for gas
INJ deposit tx (facilitator gas)0x6272…a76d ↗1 INJ → facilitator · block 134,670,989 · 2026-07-25 16:24:49 UTC · success
x402 settlement tx (paid call)0xbdf6…dd73 ↗0.01 USDC · payer→facilitator · gas sponsored via EIP-3009 · confirmed on-chain
The full x402 flow is verified end-to-end and confirmed on-chain: 402 quote handshake → EIP-3009 gasless signature → facilitator-sponsored on-chain settlement, 0.01 USDC moved payer→facilitator (payer 20.00→19.99, facilitator 0→0.01, balances reconcile). Injective testnet RPC/LCD reachable directly, no geo-block.

Agent Card (discovery)

Any caller's first step should be fetching this file — a machine-readable "business card": identity, skills, constraints, and the entry point for each of the three protocols below.

GET /.well-known/agent-card.json
{ "protocolVersion": "0.2", "name": "CarryPilot", "description": "AI funding-rate arbitrage research agent...", "url": "/api/a2a", "preferredTransport": "JSONRPC", "additionalInterfaces": [ { "url": "/api/a2a", "transport": "JSONRPC" }, { "url": "/mcp", "transport": "MCP" }, { "url": "/api/agent/query", "transport": "HTTP+JSON" } ], "skills": [ { "id": "funding_rates", "name": "Funding rate lookup", "examples": ["BTC funding rate now", "which coins have the highest funding"] }, { "id": "arbitrage_opportunity", "name": "Arbitrage opportunity lookup", "examples": ["any arbitrage opportunities right now", "is BTC worth arbitraging"] } ], "constraints": { "venues": ["hyperliquid"], "strategy": "S1_SPOT_PERP", "readOnly": true }, "payments": { "protocol": "x402", "asset": "USDC", "network": "injective-testnet", "endpoint": "/api/agent/report" } }

Both skills sit on top of the same deterministic engine: real net APR after fees + slippage + a funding-drift reserve, never the gross rate. When there's no opportunity it says so honestly instead of manufacturing one for the sake of an answer.

HTTP (simplest, good for a quick poke)

One sentence in, one JSON object out. No protocol overhead — fine for scripts, curl, or wrapping inside your own agent.

GET /api/agent/query?q=<text>   or   POST /api/agent/query {"query":"..."}
curl "/api/agent/query?q=BTC%20funding%20rate"
{ "query": "BTC funding rate", "coin": "BTC", "generatedAt": "2026-07-25T03:32:48.582Z", "disclaimer": "...", "intent": "funding_rates", "found": true, "hourlyFundingPct": 0.00086329, "annualizedPct": 7.5624204, "hasSpot": true, "message": "BTC funding rate is currently 0.00086%/h (~7.6% annualized), longs pay shorts. Spot market exists — can be hedged into an on-venue carry." }

Arbitrage-opportunity queries hit the same endpoint, just phrase it differently: ?q=any arbitrage opportunities right now or ?q=is BTC worth it.

A2A (Agent2Agent protocol)

Standard A2A JSON-RPC 2.0, message/send method. Fits a host that's already in the A2A ecosystem (agent-card discovery + multi-agent orchestration).

POST /api/a2a
curl -X POST /api/a2a \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "message/send", "params": { "message": { "role": "user", "parts": [{ "kind": "text", "text": "any arbitrage opportunities right now" }] } } }'

The result is a role: "agent" message with two parts: text (a human-readable sentence) + data (structured fields for your code to parse).

{ "jsonrpc": "2.0", "id": 1, "result": { "kind": "message", "role": "agent", "messageId": "...", "parts": [ { "kind": "text", "text": "No opportunity clears costs across the whole market right now — ..." }, { "kind": "data", "data": { "intent": "arbitrage_opportunity", "accepted": [], "...": "..." } } ] } }

Every call on this agent completes synchronously — no long-running task state is kept. tasks/get / tasks/cancel return -32001.

MCP (Model Context Protocol)

Streamable HTTP, stateless. If your agent host is Claude / Codex / anything else with MCP support, this is the path of least resistance — no HTTP client to write, just point a URL.

{ "mcpServers": { "carrypilot": { "url": "/mcp" } } }

Exposed tools

ToolArgsDescription
get_funding_ratescoin?: stringCurrent funding rate / annualized / hedgeable for one coin (or the highest-|rate| coins market-wide)
find_arbitragecoin?: stringMarket-neutral carry candidates after full costs: net APR, cost coverage, rejection reasons

Test it directly with the official SDK:

// npm i @modelcontextprotocol/sdk import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; const client = new Client({ name: "my-agent", version: "1.0.0" }); await client.connect(new StreamableHTTPClientTransport(new URL("/mcp"))); const { tools } = await client.listTools(); const result = await client.callTool({ name: "find_arbitrage", arguments: {} });

x402: pay-per-call for the full report

The three free entry points only answer "the one thing you asked." The full report (every market-wide candidate + the whole rate table, not a summary) is metered in USDC via x402 — HTTP 402 kicks it off, on-chain EIP-3009 gasless signed payment settles it, no human in the loop. An AI agent can pay for its own data.

GET /api/agent/report Current status: Injective testnet (eip155:1439) — the full x402 flow is verified end-to-end and confirmed on-chain: 402 quote → EIP-3009 gasless signing → facilitator-sponsored on-chain settlement, 0.01 USDC moved payer→facilitator (see scripts/x402-a2a-test.ts, settlement tx 0xbdf6030e…459dd73).

Step 1: request with no payment → get a 402 quote

curl /api/agent/report
HTTP/1.1 402 Payment Required PAYMENT-REQUIRED: <base64, same JSON as below> { "x402Version": 2, "resource": { "url": ".../api/agent/report", "description": "CarryPilot full arbitrage report" }, "accepts": [{ "scheme": "exact", "network": "eip155:1439", "amount": "10000", "asset": "0x0C382e685bbeeFE5d3d9C29e29E341fEE8E84C5d", "payTo": "0x402e6cB590137156eA549D4b7eDa9231C70A2c98", "maxTimeoutSeconds": 120, "extra": { "name": "USDC", "version": "2", "assetTransferMethod": "eip3009" } }] }

amount: "10000" = 0.01 USDC (6 decimals). network: eip155:1439 is Injective EVM Testnet.

Step 2: sign an EIP-3009 transferWithAuthorization against the quote, then replay

Hand-rolling the signature (EIP-712 typed data) is fiddly — we recommend the official client (this is also how we test it):

// npm i @injectivelabs/x402 viem import { createInjectiveClient } from "@injectivelabs/x402/client"; const client = createInjectiveClient({ privateKey: YOUR_PAYER_PRIVATE_KEY }); const res = await client.fetch("/api/agent/report"); // res.status === 200, body is the full report const receipt = JSON.parse(atob(res.headers.get("payment-response"))); // receipt.transaction is the on-chain settlement tx hash

What the client does under the hood: parse accepts from the 402 → build EIP-712 typed data → sign with your private key (off-chain, no gas) → retry the request with the signature in a PAYMENT-SIGNATURE header → the server's facilitator verifies signature/balance/nonce → submits on-chain settlement → you get 200 + full data + a settlement receipt.

No SDK? Roll it by hand

The header is PAYMENT-SIGNATURE (current) or X-PAYMENT (legacy-compatible), base64-encoded PaymentPayload (scheme/network/payload.authorization + payload.signature). The EIP-712 domain belongs to the USDC contract itself (name "USDC", version "2"); the signed object is a standard EIP-3009 TransferWithAuthorization (from/to/value/validAfter/validBefore/nonce).

Testnet parameters

Value
Chain ID1439 (eip155:1439)
RPChttps://k8s.testnet.json-rpc.injective.network
USDC contract0x0C382e685bbeeFE5d3d9C29e29E341fEE8E84C5d (Circle FiatTokenV2_2, EIP-3009 capable)
Testnet USDC faucetfaucet.circle.com (select Injective Testnet)
Testnet INJ faucet (gas, only needed if you self-host a facilitator)testnet.faucet.injective.network

Source reference: scripts/x402-spike.ts (feasibility spike), scripts/x402-settle-demo.ts (real-payment settlement demo, prints the on-chain tx hash).

Error codes

A2A (standard JSON-RPC 2.0 codes)

codemeaningtrigger
-32700Parse errorrequest body isn't valid JSON
-32600Invalid Requestjsonrpc field isn't "2.0"
-32601Method not foundmethod isn't message/send
-32602Invalid paramsno text part inside message.parts
-32001Task not foundtasks/get/tasks/cancel called — this agent completes synchronously and keeps no task state

x402

HTTPmeaning
402 + insufficient_fundspayer wallet's USDC balance is too low
402 + Payment network not acceptedsigned network/asset doesn't match the quote
501this deployment has x402 disabled (treasury/facilitator key missing)

Limits & disclaimer

⚠ Everything here is a research finding, not investment advice or a return guarantee. Funding rates move fast; free-tier data is cached for 60 seconds.
  • Covers only the Hyperliquid on-venue strategy (long spot + short perp, market-neutral) — no cross-exchange arbitrage
  • Read-only: nothing in this document places an order or touches funds. Order placement needs a wallet-signed session and lives in the human-facing App; it isn't exposed to agents
  • Keep request rates reasonable; for production integrations, cache answers locally — abusive traffic will be restricted