Swap XMR, SOL, USDT, and USDC, any chain
Pick a pair, enter an amount, and see what you'll get — XMR, SOL, USDT, or USDC on Solana, Ethereum, or Polygon. Then send to a one-time deposit address and track your order live.
Pick a pair, enter an amount, and see what you'll get — XMR, SOL, USDT, or USDC on Solana, Ethereum, or Polygon. Then send to a one-time deposit address and track your order live.
Base URL http://127.0.0.1:4100 · JSON in, JSON out · no auth on public endpoints · swap creation is rate-limited to 20/hour per IP.
Create a swap. Returns a one-time Monero deposit subaddress and an indicative quote. Anything sent to that address (≥ the minimum, dust ignored) is swapped after 1 confirmation.
| field | type | description |
|---|---|---|
| to_chain | "sol" | "eth" | "polygon" | Chain to receive the payout on |
| to_token | "usdc" | "usdt" | "sol" | Optional payout token, default usdc; usdt and native sol are Solana only |
| to_address | string | Destination address: base58 for Solana, 0x-hex for EVM chains |
curl -X POST http://127.0.0.1:4100/api/swaps \ -H 'Content-Type: application/json' \ -d '{"to_chain":"sol","to_address":"Apewggxx…XbLp"}' { "id": "984bcdce-4416-444b-bd28-28e8fd6d5af4", "status": "awaiting_deposit", "deposit_address": "88GHU2vA…5VA", "min_confirmations": 1, "min_deposit_xmr": 0.01, "expires_at": 1784655383973, "quote": { "usdc_per_xmr": 337.34, "source": "jupiter", "service_fee_bps": 100, "note": "float rate…" }, "depeg_warning": false }
| status | meaning |
|---|---|
| 400 | to_chain not one of sol/eth/polygon, or the address fails validation for that chain |
| 429 | rate limited (20 creations/hour per IP) |
Swap status snapshot. Live updates are pushed over the WebSocket, so polling is not required (this UI falls back to 5-second polling only if the socket cannot connect). Amounts are decimal strings; XMR has 12 decimals, USDC 6.
| field | description |
|---|---|
| status | see lifecycle below |
| deposit_address | the one-time XMR subaddress for this swap |
| deposited_xmr | confirmed XMR credited to the swap |
| unconfirmed_xmr | XMR seen in the mempool, not yet 1-conf |
| delivered_usdc | net USDC sent, once completed |
| delivery_ref | Solana tx signature, or the Mayan order signature for eth/polygon (track at explorer.mayan.finance/swap/<sig>) |
| delivery_fulfill_ref | eth/polygon only: the destination-chain tx hash of the observed Mayan fill, set when the swap completes |
| expires_at | epoch ms; deposits after expiry are held for manual review, never lost |
The deposit address as a QR code (SVG, monero: URI). Suitable for direct use in an <img>.
Current indicative rate and limits. The executable rate comes from Jupiter (selling wXMR→USDC on Solana: real liquidity, not an oracle); CoinGecko XMR/USD is returned as a reference. depeg_warning is set when they diverge >5%. max_xmr is the live wXMR reserve balance — the most a forward swap can pay out right now. Add ?amount_xmr= to price a specific size: the response gains sized with the effective rate at that amount, price_impact_pct, drift_bps vs the ambient quote, and would_park — true when the executor's drift guard would hold that size for review, i.e. the pool cannot absorb it. Add ?out=usdt|sol to quote a USDT or native-SOL payout instead; usdc_per_xmr and the sized fields are then denominated in that token (see out_token).
curl http://127.0.0.1:4100/api/quote { "usdc_per_xmr": 337.34, "xmr_usd_reference": 336.5, "source": "jupiter", "depeg_warning": false, "service_fee_bps": 100, "min_deposit_xmr": 0.01, "min_confirmations": 1, "chains": ["sol", "eth", "polygon"] }
Create a reverse swap: USDC on Solana in, XMR out. Returns a one-time Solana deposit address; send USDC there directly, or from any Mayan-supported chain by setting it as the recipient of a Mayan swap to USDC on Solana. Once at least min_usdc_in is finalized at the address, the service buys wXMR and redeems it through the wxmr.io bridge straight to your Monero address. Amounts are always read from the chain; nothing in the request body can influence them.
| field | type | description |
|---|---|---|
| xmr_address | string | Monero mainnet payout address (standard or subaddress; integrated addresses rejected) |
| from_token | "usdc" | "usdt" | "sol" | Optional deposit token on Solana, default usdc; the response's deposit_token names exactly what the address is watched for |
curl -X POST http://127.0.0.1:4100/api/reverse-swaps \ -H 'Content-Type: application/json' \ -d '{"xmr_address":"44AFFq5kSiGBoZ…Wjw"}' { "id": "7c2f1de0-9b31-47a5-a1c4-52ab61a1b2c3", "direction": "usdc_to_xmr", "status": "awaiting_payment", "deposit_address": "9x1FZk…Qm2", "deposit_chain": "sol", "deposit_token": "USDC", "min_usdc_in": "36.70", "expires_at": 1784655383973, "quote": { "usdc_per_xmr": 351.02, "source": "jupiter", "service_fee_bps": 100, "note": "float rate…" }, "depeg_warning": false }
| status | meaning |
|---|---|
| 400 | xmr_address fails wallet-side validation (wrong network, integrated, malformed) |
| 429 | rate limited (20 creations/hour per IP, separate budget from /api/swaps) |
Reverse swap status snapshot; live updates on the WebSocket reverse_swap channel.
| field | description |
|---|---|
| status | see reverse lifecycle below |
| deposit_address | the one-time Solana address to pay USDC to |
| min_usdc_in | USDC required to credit; covers the bridge's 0.1 XMR withdrawal minimum at the quoted rate |
| usdc_received | USDC observed (finalized) at the deposit address |
| delivered_xmr | net XMR the bridge sends, once known |
| xmr_txid, xmr_tx_key | the payout transaction and its tx key: verifiable proof of payment to your address |
| expires_at | epoch ms; payments after expiry are held for manual review, never lost |
The deposit address as a QR code (SVG, solana: Pay URI carrying the USDC mint).
Indicative rate for the reverse direction, quoted on the buy side (what USDC actually buys of wXMR on Jupiter), plus min_usdc_in. bridge_fee_bps is the wxmr.io bridge's withdrawal fee, read live from its on-chain config; it is taken out of the withdrawn amount before the XMR payout. Add ?amount_usdc= for a sized quote with the same sized shape as /api/quote; there is no reserve cap in this direction, so would_park is the effective liquidity ceiling. Add ?in=usdt|sol to quote a USDT or native-SOL deposit instead; rates, minimums, and sized amounts are then denominated in that token (see in_token).
awaiting_paymentdeposit address issued, nothing finalized yetpayment_received≥ minimum USDC finalized, queued for executionexecutingsweeping the deposit and buying wXMR at the live ratewithdrawingbridge withdrawal requested; XMR payout in flightcompleteddone; xmr_txid and xmr_tx_key are finalexpiredno payment within 24 h; late payments are held for reviewunder_reviewparked for a human; funds are never silently droppedDirected pairs the routing core serves: every pair across usdc-sol, usdc-eth, usdc-poly, sol (native SOL), and usdt-sol (USDT on Solana) — twenty in total, no XMR leg. Each routed swap is a chain of legs where every leg's input is the chain-observed output of the previous leg; pairs involving SOL or USDT add one Jupiter conversion leg through the USDC-on-Solana hub. EVM deposits are credited from finalized Transfer logs of the chain's native USDC contract only (named in the response), swept gaslessly via EIP-3009, and paid out of Solana reserves.
Composed quote for a route, priced at min_in — the worst case the loss budget permits, since the bridge's relayer fees are roughly fixed per order and weigh heaviest on small amounts; larger deposits only do better. Execution re-prices at the actual received amount and parks anything quoted to lose more than max_loss_bps. Add &amount= (in units of the from-asset; amount_usdc is an accepted alias) to price the route at that size instead of the floor. Amounts in and out are denominated in from_token / to_token. max_usdc is the live Solana USDC reserve balance; it caps EVM-in routes, whose deliveries are fronted from reserves, and is null for Solana deposits, which convert the user's own swept funds.
Create a routed swap. Body: {"from":"usdc-eth","to":"usdc-sol","to_address":"…"} (any pair from GET /api/routes). Returns a one-time deposit address on the from-chain (also as a QR at /api/routed-swaps/:id/qr) plus min_in, with deposit_token naming exactly what the address is watched for (USDC, USDT, or native SOL). EVM deposits pay a network_fee_usdc deducted from the output — the sweep transaction's actual gas cost (estimated in the quote, charged from the receipt). For EVM deposits send native USDC on the named chain only: the same 0x address exists on every chain, so wrong-chain or wrong-token funds are held for manual recovery, never credited or lost. Send at least min_in of the deposit token; the response's legs array reports each leg's status and chain-observed output as the swap runs. Statuses: awaiting_deposit → executing → delivering → completed with the same expired / under_review exits and never-drop-money rules as both other flows. delivered_usdc is the amount observed delivered on the destination chain, delivery_ref the Mayan order signature, delivery_fulfill_ref the destination-chain tx. Snapshot at GET /api/routed-swaps/:id; live pushes on the routed_swap WebSocket channel.
Service health: Monero wallet and daemon heights, sync state, and whether execution is dry-run or live. Returns 503 when the wallet RPC is unreachable.
Push channel so nothing has to poll. Send JSON subscribe / unsubscribe ops; every subscription is answered with a snapshot immediately, then swap, reverse_swap, and routed_swap push on every state change while quote and health push a fresh snapshot every 30 s. Payload shapes match the REST endpoints above.
// wss://<host>/api/ws (lines starting with > are sent, < received) > {"op":"subscribe", "channel":"swap", "id":"984bcdce-4416-444b-bd28-28e8fd6d5af4"} < {"channel":"swap", "swap":{"id":"984bcdce-…", "status":"awaiting_deposit", …}} < {"channel":"swap", "swap":{"id":"984bcdce-…", "status":"deposit_unconfirmed", …}} > {"op":"subscribe", "channel":"quote"} < {"channel":"quote", "quote":{"usdc_per_xmr": 337.34, …}}
awaiting_depositsubaddress issued, nothing received yetdeposit_unconfirmeddeposit seen in the mempooldeposit_confirmed1 confirmation reached, queued for executionexecutingselling wXMR reserves → USDC at the live ratedeliveringUSDC payout in flight; for eth/polygon the swap stays here until the Mayan order's fill is observed on the destination chaincompleteddone; delivered_usdc is the observed delivered amount and delivery_ref is finalexpiredno deposit within 24 h; late deposits are held for reviewunder_reviewparked for a human (late/extra deposit, price moved >3% against the quote, or an execution error); funds are never silently dropped