Swapper

Swap API for AI agents

Connect over MCP in one line, or call the key-less HTTPS API directly. Either way your agent can quote, create and track an XMR/USDC swap end to end.

Open the swap app →

Discovery

Fastest: connect over MCP

Public MCP endpoint https://xmrtousdc.xyz/mcp (Streamable HTTP, stateless, no auth, no API key). Three calls: get_quote → create_swap → get_swap_status. A quote needs only amount; direction defaults to XMR→USDC and chain to Arbitrum.

ClientSetup
Claude Codeclaude mcp add --transport http swapper https://xmrtousdc.xyz/mcp
Claude Desktop / claude.aiSettings -> Connectors -> Add custom connector -> https://xmrtousdc.xyz/mcp
Cursor (~/.cursor/mcp.json){ "mcpServers": { "swapper": { "url": "https://xmrtousdc.xyz/mcp" } } }
VS Code (.vscode/mcp.json){ "servers": { "swapper": { "type": "http", "url": "https://xmrtousdc.xyz/mcp" } } }
Windsurf (~/.codeium/windsurf/mcp_config.json){ "mcpServers": { "swapper": { "serverUrl": "https://xmrtousdc.xyz/mcp" } } }
Older clients (stdio bridge){ "mcpServers": { "swapper": { "command": "npx", "args": ["-y", "mcp-remote", "https://xmrtousdc.xyz/mcp"] } } }
ToolRequiredNotes
get_quoteamountStep 1, read-only. direction defaults to x2u (XMR->USDC), chain to Arbitrum.
create_swapamount, toStep 2. Creates the order; pass idempotencyKey. includeQuote:true quotes and orders in one call. Returns orderId + deposit {address, amount, expiresAt}.
get_swap_statusorderIdStep 3, read-only. Poll every next.pollIntervalSec until terminal:true.
plan_swapnoneOptional. Validates partial intent, returns the next question to ask.
get_capabilitiesnoneOptional. Directions, chains, defaults, rules.
create_support_ticketmessageFor failed or stuck swaps (include orderId).

Then ask your agent: "Swap 0.5 XMR to USDC on Arbitrum to 0xYourAddress." Every tool result carries structuredContent with userMessage and a next hint. Full client guide: docs/user/mcp-integration.md on the docs site.

REST endpoints

MethodPathPurpose
GET/api/v1/agent/quickstartDiscovery, trust metadata, example calls (always reachable).
GET/api/v1/agent/capabilitiesDirections, live chains, default chain, validation rules.
POST/api/v1/agent/swap/planValidate partial intent; returns state + guidance.questionsToAsk.
POST/api/v1/agent/swap/quotePrice a swap: {direction, amount, chain?}.
POST/api/v1/agent/swap/createCreate order (send idempotency-key); returns fundingInstructions.
POST/api/v1/agent/swap/executeQuote + create in one call (send idempotency-key).
GET/api/v1/agent/swap/status/{orderId}Normalized order state; poll until terminal.
POST/api/v1/agent/support/ticketsOpen a support ticket: {message, orderId?}.
GET/api/v1/chainsChain list incl. minSwapUsd per chain.
GET/api/v1/healthService health (status ok | degraded).

Inputs: direction is x2u (XMR→USDC) or u2x (USDC→XMR); amount is a decimal string in source-token units; to is an EVM address for x2u or a Monero address for u2x; chain is optional (auto-filled to Arbitrum while it is the only live chain).

1. Plan

curl -sS https://xmrtousdc.xyz/api/v1/agent/swap/plan \
  -H "content-type: application/json" \
  -d '{"direction":"x2u","amount":"0.8"}'

Returns state (needs_input, needs_destination, invalid_input, ready_to_execute) and data.guidance.questionsToAsk.

2. Execute (quote + create)

curl -sS https://xmrtousdc.xyz/api/v1/agent/swap/execute \
  -H "content-type: application/json" \
  -H "idempotency-key: swap_user123_20261007_01" \
  -d '{"direction":"x2u","amount":"0.8","to":"0xYOUR_ARBITRUM_ADDRESS"}'

Returns state: "waiting_for_funding", data.order.orderId, data.swapDetails and data.fundingInstructions (depositAddress, depositAmount, depositTokenSymbol, expiresAt). Reusing the same idempotency-key replays the same order; a different body with the same key returns 409.

3. Poll status

curl -sS https://xmrtousdc.xyz/api/v1/agent/swap/status/ord_xxx
stateMeaning
waiting_for_fundingOrder created; user must send the exact depositAmount to depositAddress before expiresAt.
deposit_detectedDeposit seen on-chain, waiting for confirmations.
swap_processingDeposit confirmed; swap executing (or refund in progress).
completedTerminal success. Funds sent to the destination.
terminal_failureTerminal failure (failed, expired or refunded). Open a support ticket.

Full loop (JavaScript)

const API = "https://xmrtousdc.xyz/api/v1/agent";
const post = (p, body, headers = {}) => fetch(API + p, {
  method: "POST",
  headers: { "content-type": "application/json", ...headers },
  body: JSON.stringify(body)
}).then((r) => r.json());

// 1) plan -> ask the user only for data.guidance.questionsToAsk
const plan = await post("/swap/plan", { direction: "x2u", amount: "0.8" });
// 2) execute (quote + create) with a stable idempotency key
const ex = await post("/swap/execute",
  { direction: "x2u", amount: "0.8", to: "0xYOUR_ARBITRUM_ADDRESS" },
  { "idempotency-key": "swap_user123_20261007_01" });
// 3) show ex.data.swapDetails.userChecklist + ex.data.fundingInstructions to the user
const { orderId } = ex.data.order;
// 4) poll until terminal
let st;
do {
  await new Promise((r) => setTimeout(r, 30_000));
  st = await fetch(API + "/swap/status/" + orderId).then((r) => r.json());
} while (!st.terminal);

System prompt for your agent

You execute XMR<->USDC swaps via the Swapper Agent API (https://xmrtousdc.xyz/api/v1/agent).
1) POST /swap/plan with known fields; ask ONE question at a time from data.guidance.questionsToAsk.
2) POST /swap/quote (or go straight to /swap/execute) and show data.swapDetails to the user.
3) After the user confirms, POST /swap/execute or /swap/create with an idempotency-key.
4) Show data.swapDetails.userChecklist and data.fundingInstructions exactly as returned.
5) Poll GET /swap/status/{orderId} until terminal (completed | terminal_failure).
6) On terminal_failure, POST /support/tickets with the orderId.
Never invent addresses or amounts. Never ask the user to fund before fundingInstructions exist.

Rules and limits

Open the swap app →