Insights

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 Paymenttransaction 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 ledger’s path-finding engine automatically searches 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. With tfPartialPayment, acts as the upper bound.
  • DeliverMin — minimum the destination will accept when tfPartialPayment is set. Your slippage guard.
  • Paths — optional array of alternative routes. XRPL auto-fills when omitted, but supplying paths from path_find is more reliable for complex swaps.
  • 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 uses 161803).

Flags You Will Actually Use

  • tfPartialPayment (0x00020000) — allow the destination to receive less than Amount, down to DeliverMin. Critical for any cross-currency send and for dust-safe MAX sends.
  • tfLimitQuality (0x00040000) — refuse routes worse than Amount / SendMax. Enforces a minimum exchange rate.
  • tfNoDirectRipple (0x00010000) — skip the default same-issuer rippling path. Forces the engine to use the DEX/AMM even when rippling would be cheaper.

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.currency !== Amount.currency, the engine runs path-finding across four venue types: direct XRP transfer, trust-line rippling through gateways, DEX order books, and AMM pools. The rippled path_find command returns ranked candidate paths; the best one is executed at submission time.

For production swaps, query pricing first through our aggregator so you can show the user an expected output before they sign:

curl "https://api.xrpl.to/v1/dex/quote?from=XRP&to=534F4C4F00000000000000000000000000000000.rsoLo2S1kiGeCcn6hCUXVrCpGMWLrRrLZz&amount=100"

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":     161803
}

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. Path-finding consumes all of SendMax because the target has headroom, DeliverMinstill enforces slippage, and the sender’s balance lands at exactly zero.

SendMax   = { currency, issuer, value: sellBalance }       // entire balance
Amount    = { currency: "XRP", value: String(expectedDrops * 2) }
DeliverMin= String(expectedDrops * (1 - slippage))
Flags     = 0x00020000                                      // tfPartialPayment

Setting 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 in path-finding. 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?ammId=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 that creates a new trustline (rare, usually blocked) counts against reserve.
  • tecPATH_DRY — no path with enough liquidity. Retry with a smaller amount or different SendMax.
  • tecPATH_PARTIAL — path exists but can’t fully deliver Amount without tfPartialPayment.
  • tecUNFUNDED_PAYMENT — sender can’t cover SendMax + fee + reserves.
  • temREDUNDANTAmount.currency equals SendMax.currency without 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.

Build On the Engine