Quote a swap, show the user the expected output, submit a Payment. This is the exact pipeline behind the xrpl.to swap widget, and you can call the same endpoints from your own app.
Raw XRPL gives you order books per pair and AMM pools per pair, but not a unified “what do I get if I sell X for Y” price that accounts for path-finding across both venues. That’s what /v1/dex/quote does — it walks the same path-finder the ledger will use at submission time and returns the expected output, price impact, and the route it picked. One call, one number, then render.
GET /v1/dex/quote returns expected output and impact for a proposed trade.Payment with the quote’s numbers plus a slippage buffer, post to /v1/submit.Quotes are free and cheap (10s edge cache). Submission always goes straight to the ledger through your signing flow — the API never touches a private key.
GET https://api.xrpl.to/v1/dex/quote ?from=XRP &to=534F4C4F00000000000000000000000000000000.rsoLo2S1kiGeCcn6hCUXVrCpGMWLrRrLZz &amount=100
from and to use either XRP or currencyHex.issuer. amount is the input size. Typical response:
{
"success": true,
"expectedOut": "420.5",
"priceImpact": 0.0041, // 0.41%
"route": [
{ "venue": "amm", "pool": "rAMM...", "share": 0.72 },
{ "venue": "book", "pair": "XRP/SOLO", "share": 0.28 }
],
"rate": 4.205, // out per unit in
"fresh_at": 1776519100
}Apply the user’s slippage tolerance (1% is a reasonable default, 0.1% for stables, 3%+ for thinly-traded tokens) and construct the tx. Set Amount to 2× the expected output to let path-finding consume all of SendMax without rounding dust — a safe pattern for MAX-style sells:
const slippage = 0.01;
const expectedOut = parseFloat(quote.expectedOut);
const deliverMin = expectedOut * (1 - slippage);
const tx = {
TransactionType: 'Payment',
Account: userAddress,
Destination: userAddress, // same-account = swap
SendMax: '100000000', // 100 XRP in drops
Amount: {
currency: '534F4C4F00000000000000000000000000000000',
issuer: 'rsoLo2S1kiGeCcn6hCUXVrCpGMWLrRrLZz',
value: String(expectedOut * 2) // 2x for safe routing
},
DeliverMin: {
currency: '534F4C4F00000000000000000000000000000000',
issuer: 'rsoLo2S1kiGeCcn6hCUXVrCpGMWLrRrLZz',
value: String(deliverMin)
},
Flags: 0x00020000, // tfPartialPayment
SourceTag: 161803 // your platform tag
};See the Payments engine spec for the full field reference including why the 2× Amount pattern works and which flags you need.
Sign client-side with xrpl.js, xrpl-py, or any wallet library. Never expose the seed to your server:
import { Wallet } from 'xrpl';
const wallet = Wallet.fromSeed(userSeed, {
algorithm: userSeed.startsWith('sEd') ? 'ed25519' : 'secp256k1'
});
const signed = wallet.sign(tx);
await fetch('https://api.xrpl.to/v1/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ tx_blob: signed.tx_blob })
});A 200 from /submit means the tx is in the mempool, not validated yet. Poll GET /v1/tx/<hash> until validated: true, then read meta.TransactionResult — tesSUCCESS means settled.
Book prices move constantly. Re-quote when the input amount changes, when a token is re-selected, and on a 10s interval while the user is hovering the button. The quote endpoint is fast enough (typically <100ms) that debounce-on-input is unnecessary — fire on every keystroke past 2 characters.
tecPATH_DRY — no liquidity for the requested size. Show user “size too large” and suggest halving.tecPATH_PARTIAL — partial fill when tfPartialPayment isn’t set. Re-submit with the flag on.priceImpact > 3%, warn the user in the UI before signing./v1/account/trustlines/<addr> and prompt a TrustSet first.Anonymous: 7 quotes/minute — enough to prototype. For any production app, get a free developer key (90/minute). Business and Enterprise tiers lift limits proportionally. The free tier covers most single-user wallets and small swap widgets out of the box.