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. Quote
POST /v1/dex/quote: the best price over every book and pool.
- 2. Build
A Payment to yourself, with the quote’s numbers.
- 3. Sign
On the user’s device. No key ever reaches the API.
- 4. Submit
POST /v1/submit, or any XRP Ledger server.
- 5. Confirm
Final once in a validated ledger, 3–5 seconds later.
1. Get a quote
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"
}'{
"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": []
}
}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) ordestination_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’srequiresTrustlinesays 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.