Insights

XRPL Swap API

A swap on the XRP Ledger is a Payment to yourself. The hard part is the numbers: which route, how much comes out, how much slippage to allow. One POST to xrpl.to’s quote endpoint answers all three across every order book and AMM pool; the rest is a transaction your app signs.

The pipeline

  1. 1. Quote

    POST /v1/dex/quote: the best price over every book and pool.

  2. 2. Build

    A Payment to yourself, with the quote’s numbers.

  3. 3. Sign

    On the user’s device. No key ever reaches the API.

  4. 4. Submit

    POST /v1/submit, or any XRP Ledger server.

  5. 5. Confirm

    Final once in a validated ledger, 3–5 seconds later.

1. Get a quote

Request
curl -X POST 'https://api.xrpl.to/v1/dex/quote' \
  -H 'Content-Type: application/json' \
  -d '{
  "source_account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
  "source_token": "84e5efeb89c4eae8f68188982dc290d8",
  "source_value": "10",
  "destination_token": "0dd550278b74cb6690fdae351e8e0df3"
}'
Response
{
  "status": "success",
  "requiresTrustline": {
    "currency": "524C555344000000000000000000000000000000",
    "issuer": "rMxCKbEDwqr76QuheSUMdEGf4B9xJ8m5De",
    "limit": "13.101899999999997"
  },
  "quote": {
    "source_amount": {
      "currency": "XRP",
      "value": "10.000000"
    },
    "destination_amount": {
      "currency": "524C555344000000000000000000000000000000",
      "issuer": "rMxCKbEDwqr76QuheSUMdEGf4B9xJ8m5De",
      "value": "13.101899999999997"
    },
    "execution_rate": "0.76324808",
    "price_impact": {
      "percent": "-0.06%",
      "xrp": "0.01 XRP"
    },
    "suggested_slippage": 0.6,
    "route": "xrp",
    "paths": [],
    "warnings": []
  }
}
A live quote for 10 XRP into RLUSD, as served at 2026-10-19 05:32 UTC (refreshed hourly). Tokens are named by their xrpl.to id; this account has no RLUSD trust line yet, so the answer includes requiresTrustline. XRP comes back as an object in XRP when it is what you give and as a string of drops when it is what you get; the builder below takes both.
  • Exact input or exact output: send source_value (what you give) or destination_value (what you want).
  • suggested_slippage is the tolerance, in percent, this trade needs; use it rather than a fixed figure.
  • paths must go into the Payment for token-to-token routes; it is empty when one side is XRP.

2. Build the Payment

import { PaymentFlags, xrpToDrops } from 'xrpl'

// The quote writes XRP as drops (a string) or as { currency: 'XRP', value };
// a token as { currency, issuer, value }.
const isXrp = (a) => typeof a === 'string' || a.currency === 'XRP'
const valueOf = (a) => (typeof a === 'string' ? Number(a) / 1e6 : Number(a.value))
const amount = (a, v) => isXrp(a)
  ? xrpToDrops(v.toFixed(6))
  : { currency: a.currency, issuer: a.issuer, value: v.toPrecision(15) }

function buildSwap(account, quote) {
  const give = quote.source_amount, get = quote.destination_amount
  const out = valueOf(get)
  return {
    TransactionType: 'Payment',
    Account: account,
    Destination: account,                         // paying yourself is a swap
    SendMax: amount(give, valueOf(give)),         // the most you give
    Amount: amount(get, out * 2),                 // a ceiling; DeliverMin is the real limit
    DeliverMin: amount(get, out * (1 - quote.suggested_slippage / 100)),
    Flags: PaymentFlags.tfPartialPayment,
    ...(quote.paths?.length ? { Paths: quote.paths } : {}),
  }
}

With the partial-payment flag, the ledger delivers as much as your SendMax buys, and refuses the whole trade if that would be less than DeliverMin. Both directions of this builder were dry-run on Mainnet with rippled’s simulate and came back tesSUCCESS. To tell your app’s trades apart on the ledger, add a SourceTag of your own.

3. Sign, submit, confirm

import { Client, Wallet } from 'xrpl'

const client = new Client('wss://xrplcluster.com')
await client.connect()
const wallet = Wallet.fromSeed(seed)                      // on the user's device
const tx = await client.autofill(buildSwap(wallet.address, quote))
const { tx_blob, hash } = wallet.sign(tx)

const res = await fetch('https://api.xrpl.to/v1/submit', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ tx_blob }),
}).then((r) => r.json())                                  // res.engine_result is provisional

// later: final once validated
const done = await fetch(`https://api.xrpl.to/v1/tx/${hash}`).then((r) => r.json())
// done.validated === true, done.meta.TransactionResult === 'tesSUCCESS'

The result from /submit is the server’s first opinion; only the validated ledger is final. Or let xrpl.js do steps 3 and 4 at once with client.submitAndWait(tx, { wallet }) (xrpl.js guide).

When it fails

  • tecPATH_PARTIAL (“Path could not send full amount”): the price moved past your DeliverMin. Quote again.
  • tecPATH_DRY (“Path could not send partial amount”): no route at all, often a missing trust line for what you are buying; the quote’s requiresTrustline says which to open, with NoRipple set.
  • tecUNFUNDED_PAYMENT: not enough to send once the reserve is kept back.

Quoted messages are rippled’s own (TER.cpp).

Limits and credit

Quotes run the heaviest routing the API does, so they have their own limit: 1/min anonymous, 3/min free, 45/min partner (10 credits/call). Higher limits come with a key (plans). Any app, website, bot, or dashboard that displays xrpl.to data must show a visible, clickable credit to xrpl.to on the same screen as that data — not buried in an about page.

Questions

Is there a swap API for the XRP Ledger?
Yes. xrpl.to’s POST /v1/dex/quote returns the best price for a swap across every order book and AMM pool on the ledger, with the paths and slippage to use; your app then signs and submits the Payment.
Does the swap API hold or sign anything?
No. It only quotes. The transaction is built and signed on the user’s side and can be submitted to any XRP Ledger server.
How many quotes can I request?
Per the API’s own documentation: 1/min anonymous, 3/min free, 45/min partner (10 credits/call). A key raises the limit.