XRPL Payments Engine Specification
The XRPL Payment transaction moves value between accounts, swaps across currencies, and routes through AMMs and the DEX in a single atomic tx. This is the developer reference.
What the Payments Engine Does
A Payment transaction on the XRP Ledger delivers an amount of one asset to a destination account, optionally converting from a different source asset along the way. The conversion runs through direct transfers, trust-line rippling, order-book offers, and AMM pools to find the cheapest route. All of this happens atomically in a single transaction — either the full delivery succeeds or nothing changes.
Because the same tx type handles straight XRP sends, token transfers, cross-currency swaps, and DEX-routed trades, understanding its field semantics is the single most important thing a dev building on XRPL needs to get right.
Core Fields
Destination— the receiving account (classic address).Amount— what the destination should receive. Drops string for XRP, or{currency,issuer,value}for issued tokens.SendMax— the maximum the sender will spend. Required for cross-currency payments. WithtfPartialPayment, acts as the upper bound.DeliverMin— minimum the destination will accept whentfPartialPaymentis set. Your slippage guard.Paths— optional routes. The engine always tries the default path (directly, or through XRP between two currencies) and adds only the paths you supply; it does not search for others while executing. Get them from thepath_findAPI method or a quote.DestinationTag— 32-bit tag for exchanges / memo routing.InvoiceID— 256-bit hash for idempotency / invoice reference.SourceTag— platforms should set a consistent value (xrpl.to uses161803).
Flags You Will Actually Use
tfPartialPayment(0x00020000) — allow the destination to receive less thanAmount, down toDeliverMin. Critical for any cross-currency send and for dust-safe MAX sends.tfLimitQuality(0x00040000) — refuse routes worse thanAmount / SendMax. Enforces a minimum exchange rate.tfNoRippleDirect(0x00010000) — do not use the default path; only the paths inPathsare tried.
Payments never pay more than SendMax, never deliver less than DeliverMin, and always settle at the best route the engine finds within those bounds.
Path-Finding
When SendMax and Amount are different assets, the payment converts through direct XRP transfer, trust-line rippling through gateways, DEX order books and AMM pools, along the default path and the paths supplied. The rippled path_find command returns ranked candidate paths; the best one is executed at submission time.
For production swaps, get a quote first so you can show the user an expected output before they sign; xrpl.to’s quote endpoint returns the paths to include as well (swap API guide):
curl -X POST 'https://api.xrpl.to/v1/dex/quote' \
-H 'Content-Type: application/json' \
-d '{ "source_account": "rUSER...", "source_token": "<XRP token id>",
"source_value": "100", "destination_token": "<token id>" }'Combine with OHLC data to warn users when the quote is abnormally far from recent market price.
Cross-Currency Payment Anatomy
A typical XRP → token swap that both a wallet and a DEX aggregator submit looks like:
{
"TransactionType": "Payment",
"Account": "rUSER...",
"Destination": "rUSER...",
"SendMax": "100000000", // 100 XRP in drops
"Amount": { "currency": "SOLO",
"issuer": "rsoLo2S1kiGeCcn6hCUXVrCpGMWLrRrLZz",
"value": "420.5" }, // expected output
"DeliverMin": { "currency": "SOLO",
"issuer": "rsoLo2S1kiGeCcn6hCUXVrCpGMWLrRrLZz",
"value": "416.3" }, // 1% slippage floor
"Flags": 131072, // tfPartialPayment
"SourceTag": 12345 // your app's own tag
}The destination is the sender — that’s how XRPL represents a same-account swap. tfPartialPayment tells the engine "deliver as much as possible for up to SendMax of XRP, but never below DeliverMin". The trustline for the output token must already exist.
Sell-All (MAX) Without Dust
When selling the entire balance of a token, naive sends leave sub-drop dust. The fix — verified in production — is to pair tfPartialPayment with an Amount set to 2× the expected output. The payment spends all of SendMax because the target has headroom, DeliverMin still enforces slippage, and the sender’s balance lands at exactly zero.
SendMax = { currency, issuer, value: sellBalance } // entire balance
Amount = String(expectedDrops * 2) // XRP is a string of drops
DeliverMin= String(expectedDrops * (1 - slippage))
Flags = 0x00020000 // tfPartialPaymentSetting Amount equal to the expected output fails on even 1-drop rounding. Doubling it costs nothing because the engine never over-delivers.
AMM Integration
Since the AMM amendment, the engine treats AMM pools as just another venue on any path it takes. Any Payment can route through an AMM with no special flags — the engine picks AMM vs. order book based on price. To introspect which pools are available for a route:
curl 'https://api.xrpl.to/v1/amm/info?amm_account=AMM_ACCOUNT'
For single-asset deposits that create LP positions, use AMMDeposit, not Payment. See the AMM API guide for the full flow.
Fees, Reserves, Failure Modes
- Transaction fee: base 10 drops; scales with load. Budget 12–15 drops for reliable queueing.
- Owner reserve: 0.2 XRP per trustline, offer, and escrow. A Payment never opens a trust line: the destination must already have one for the token.
tecPATH_DRY— no path with enough liquidity. Retry with a smaller amount or differentSendMax.tecPATH_PARTIAL— path exists but can’t fully deliverAmountwithouttfPartialPayment.tecUNFUNDED_PAYMENT— sender can’t coverSendMax+ fee + reserves.temREDUNDANT—Amount.currencyequalsSendMax.currencywithout a conversion purpose.
Submit Safely
Sign locally — never ship a seed to a server. Submit through any XRPL node or through the aggregated submit endpoint. Always pass SourceTag so origin analytics work downstream.
curl -X POST https://api.xrpl.to/v1/submit \
-H "Content-Type: application/json" \
-d '{"tx_blob": "SIGNED_HEX_HERE"}'A 200 response means the tx was accepted into the mempool, not that it succeeded. Poll /v1/tx/<hash> for the validated result.