Payments are the fundamental mechanism for transferring value on the XRP Ledger. They enable the movement of XRP, IOUs, and Multi-Purpose Tokens (MPTs) between accounts, either directly or through intermediary paths that leverage trust lines and order books.
A Payment transaction can operate in different modes:
- Direct XRP Payment: Simple transfer of XRP from one account to another
- Cross-Currency Payment: Converting one currency to another through intermediary steps, using paths through the decentralized exchange. With the MPTokensV2 amendment, cross-currency payments support all combinations of XRP, tokens, and MPTs.
- Since MPTokensV2, MPT->MPT payments are using the cross-currency payment execution. Please see MPT Payment Execution for a description of MPT transfer mechanics. Before it was moved to cross-currency payment system, Payment transactor completed the steps as outlined in that document manually.
The payment system uses the path finding algorithm and the Payment Engine to discover the most efficient routes for cross-currency transactions,
automatically handling currency conversion, fees, and liquidity constraints. Payments can optionally specify a DomainID field; when specified, the payment will consume offers only from that domain's order book (domain offers and hybrid offers within that domain). Without a DomainID, payments consume only from the open order book (open offers and hybrid offers). See Domain and Hybrid Offers and PermissionedDomains documentation for details.
Payments execute either fully or partially (when tfPartialPayment flag is set), or fail with an error code if insufficient liquidity exists.
The Payment transaction interacts with different ledger entry types depending on the payment mode:
- Direct XRP payments: Modify only
AccountRootentries to update XRP balances - Direct MPT payments: Modify
MPTokenentries (sender and receiver balances), andMPTokenIssuanceentry (to track outstanding amount and transfer fee burns) - Cross-currency payments: May modify
AccountRoot,RippleState(trust lines),DirectoryNode(owner directories and order book directories),Offer(consuming or partially consuming offers from the order book),AMM(when AMM liquidity is used for swaps),MPTokenIssuanceentry (to track outstanding amount), andMPTokenentries for sender and receiver holders
The AccountRoot ledger entry represents an account on the XRP Ledger and stores the account's XRP balance, sequence
number, and various flags that control account behavior.
The key of the AccountRoot object is the result
of SHA512-Half of the following values
concatenated in order:
- The
AccountRootspace key0x0061(lowercasea) - The
AccountIDof the account.
Please see AccountRoot Fields
Please see AccountRoot Flags
Key flags relevant to payments:
lsfRequireDestTag: Requires incoming payments to include a destination taglsfDepositAuth: Requires authorization for incoming payments (except XRP under specific conditions)lsfPasswordSpent: Tracks whether the account has used its one-time free SetRegularKey transaction. Cleared when the account receives a payment to allow another free regular key change
See Reserves for complete details on reserve requirements.
Payment-specific reserve behavior:
When a Payment transaction creates a new destination account (destination does not exist), the payment must deliver at
least the base reserve amount in XRP. If the XRP amount is below the base reserve, the payment fails with
tecNO_DST_INSUF_XRP.
See Trust Lines Documentation for complete details on
RippleState ledger entry.
See MPT Documentation for complete details on MPT related ledger entries.
See AMM Documentation for complete details on AMM related ledger entries.
The Payment transaction transfers value from one account to another, supporting XRP, IOUs, and MPTs. It can operate as a simple direct transfer or use pathfinding for cross-currency conversions.
Fields are described in Payment Fields
Flags are described in Payment Flags
Automatic Pathfinding with build_path
The sign and submit RPC commands accept a build_path parameter that triggers automatic pathfinding before signing/submitting the transaction:
{
"command": "sign",
"tx_json": {
"TransactionType": "Payment",
"Account": "rSource...",
"Destination": "rDest...",
"Amount": {
"currency": "USD",
"value": "100",
"issuer": "rGateway..."
}
},
"build_path": true
}When build_path is true:
- Pathfinding runs automatically using the
path_search_oldconfiguration value - Up to 4 best paths are found and inserted into the
Pathsfield (this is hardcoded inTransactionSign.cpp) - Only works if
Pathsis not already specified - Cannot be used for XRP-to-XRP payments
Static validation1
Assuming MPTokensV1 and MPTokensV2 is enabled:
temDISABLED:- transaction contains
sfCredentialIDsand Credentials amendment is not enabled. - transaction contains
sfDomainIDand PermissionedDEX is not enabled.
- transaction contains
temINVALID_FLAG: transaction flags contain invalid flags for the payment type.temMALFORMED:sfCredentialIDsarray is empty or exceeds maximum size of 8. To leave credential IDs out, leave out the entire field.sfCredentialIDsarray contains duplicate credential IDs
temBAD_AMOUNT:Amountis XRP and mantissa is bigger than100000000000000000ull.SendMaxis XRP and mantissa is bigger than100000000000000000ull.2SendMaxis specified but is negative or zero.Amountamount is negative or zero.DeliverMinis specified withouttfPartialPayment.DeliverMinis XRP and mantissa is bigger than100000000000000000ull, orDeliverMinis negative or zero.3DeliverMincurrency is different fromAmountcurrency.DeliverMinis greater thanAmount.
temBAD_CURRENCY: eitherAmountorSendMax(or the implied source amount) is a non-native asset that uses the XRP currency code.temDST_NEEDED: destination account is not specified.temREDUNDANT: payment is from account to itself with same currency and noPathsfield.Pathsare required because perhaps they will allow arbitrage.temBAD_SEND_XRP_MAX: XRP->XRP payment specifiesSendMax.temBAD_SEND_XRP_PATHS: XRP->XRP payment specifiesPaths.temBAD_SEND_XRP_PARTIAL: XRP->XRP payment hastfPartialPaymentflag.temBAD_SEND_XRP_LIMIT: XRP->XRP payment hastfLimitQualityflag, or MPT->MPT payment hastfLimitQualityflag (only if MPTokensV2 amendment is not enabled).temBAD_SEND_XRP_NO_DIRECT: XRP->XRP payment hastfNoRippleDirectflag, or MPT->MPT payment hastfNoRippleDirectflag (only if MPTokensV2 amendment is not enabled).
Validation against the ledger view4
- Destination account does not exist:
tecNO_DST: payment is not XRPtelNO_DST_PARTIAL:tfPartialPaymentflag is set. User cannot fund a new account with a partial payment.tecNO_DST_INSUF_XRP: XRP amount is below reserve.
tecDST_TAG_NEEDED: destination account haslsfRequireDestTagflag set and transaction did not specifyDestinationTagfield.telBAD_PATH_COUNT:- the
Pathsfield contains more than 6 paths. - any
PathinPathshas more than 8 elements.
- the
tecBAD_CREDENTIALS: Credential validation failed:- Any credential ID in
sfCredentialIDsdoesn't exist in the ledger - Any credential doesn't belong to the source account
- Any credential isn't accepted (missing
lsfAcceptedflag)
- Any credential ID in
tecNO_PERMISSION:DomainIDis present and either sending or receiving account is not in Domain (not the domain owner and does not hold a valid, non-expired accepted credential).terNO_DELEGATE_PERMISSION: Transaction specifies a delegate but:- The delegate authorization doesn't exist in the ledger
- The delegate doesn't have transaction-level permission for Payment
- For granular permissions: the payment is not a direct payment (has
PathsorSendMaxwith different asset), OR - For granular permissions: neither PaymentMint (when source is issuer) nor PaymentBurn (when destination is issuer) permission is granted
Validation during doApply
Common validations (all payment types):
tecNO_PERMISSION: For cross-currency payments, if DepositPreauth amendment is not enabled and destination haslsfDepositAuthflag
Direct XRP Payments:5
tefINTERNAL: Source account does not exist.tecUNFUNDED_PAYMENT: Source account cannot send the payment because doing it would leave it with insufficient funds to cover reserve and pay the fee.tecNO_PERMISSION: Destination is a pseudo-account.- If DepositAuth amendment is enabled and destination has
lsfDepositAuthflag:- Payment succeeds if source == destination (paying yourself)
- Payment succeeds if destination balance <= base reserve AND payment amount <= base reserve (prevents account wedging)
- Otherwise, deposit preauthorization is verified and may fail with:
tecEXPIRED: Any credential insfCredentialIDsis expiredtecNO_PERMISSION: Source is not deposit preauthorized by destination (either by account or by credentials)
Cross-Currency Payments:
- If DepositPreauth amendment is enabled and destination has
lsfDepositAuthflag, deposit preauthorization is verified and may fail with:tecEXPIRED: Any credential insfCredentialIDsis expiredtecNO_PERMISSION: Source is not deposit preauthorized by destination (either by account or by credentials)
- RippleCalc, which is a thin wrapper that calls the Flow engine, is invoked to execute the provided payment paths. Flow may fail during path conversion or execution. See Flow Validation and Error Codes for complete details on error codes that can be returned during cross-currency payment execution.
tecPATH_PARTIAL:DeliverMinwas specified and the delivered amount is less thanDeliverMin
XRP Payments:
-
AccountRootobject is modified:- Source account: Balance decreased by payment amount
- Destination account (if exists): Balance increased by delivered amount
- Destination account (if exists and has
lsfPasswordSpentflag): Clear the flag
-
AccountRootobject is created:- When the destination account does not exist
- Fields set:
Account: Destination account IDBalance: Payment amountSequence: Current ledger sequence if DeletableAccounts amendment is enabled, otherwise 1
Cross-Currency Payments:
State changes for cross-currency payments depend on the execution path determined by RippleCalc/Flow. See Flow documentation for detailed mechanics of path execution.
-
AccountRootobjects are modified:- Source account: Balance decreased by actual amount consumed
- Destination account: Balance increased by delivered amount
- Destination account (if has
lsfPasswordSpentflag): Clear the flag - Intermediate accounts in payment path: Balances adjusted according to path execution
-
RippleStateobjects are modified:- Trust lines along the payment path: Balances updated according to transfers
- Trust line quality and flags may affect transfer amounts
-
Offerobjects may be modified or deleted:- When: BookStep consumes order book liquidity
- Modified: Offer is partially consumed, amounts reduced
- Deleted: Offer is fully consumed or becomes unfunded
-
DirectoryNodeobjects may be modified:- When: Offers are consumed and removed from order book directories
- Directory entries updated to reflect consumed offers
-
AMMobjects may be modified:- When: AMM liquidity is used for currency conversion
- AMM pool balances adjusted according to swap amounts
MPT Payments:
-
Source's
MPTokenis modified (if source is not the issuer):MPTAmount: Decreased by sent amount (including transfer fee if applicable)
-
Destination's
MPTokenis modified (if destination is not the issuer):MPTAmount: Increased by received amount
-
Transfer fee effect (when neither source nor destination is the issuer):
MPTokenIssuanceOutstandingAmount: Decreased by transfer fee amount- The fee is effectively burned from circulation, reducing total supply
-
MPTokenIssuanceis modified:- When source is issuer:
OutstandingAmountincreased by sent amount (issuer minting tokens) - When destination is issuer:
OutstandingAmountdecreased by received amount (tokens burned, removed from circulation) - When neither is issuer:
OutstandingAmountdecreased by transfer fee amount (if transfer fee applies; unchanged if no fee or fee is waived, e.g., clawback)
- When source is issuer:
Important:
- The receiver must already have an
MPTokenentry before receiving a payment (unless the receiver is the issuer). If the receiver has noMPTokenand is not the issuer, the payment fails withtecNO_AUTH. Holders create theirMPTokenentries using theMPTokenAuthorizetransaction. - The issuer never has an
MPTokenentry and cannot hold a balance of their own issuance. When MPTs are sent to the issuer, they are burned from circulation by decreasingOutstandingAmount.
All payment types are processed through the Payment transaction but follow different execution paths based on the currencies and parameters involved:
- Direct XRP payments: Execute simple balance transfers when both
AmountandSendMax(if present) are XRP and noPathsare specified - Cross-currency payments: Invoke the Flow engine when
SendMaxis specified,Pathsare provided, orAmountis a non-XRP asset. - Direct MPT payments: Execute MPToken transfers when
Amountholds an MPT issue and MPTokensV2 amendment is not enabled
The Payment transaction determines which path to take during the doApply phase based on these conditions.
Direct XRP payments are the simplest payment type, transferring XRP directly from one account to another without any intermediate steps or currency conversion. The execution varies based on whether the destination account exists.
When the destination account exists: The payment decreases the source account's Balance by the payment amount and increases the destination account's Balance by the same amount. If the destination account has the lsfPasswordSpent flag set, it is cleared to allow another free SetRegularKey transaction.
When the destination account does not exist: A new AccountRoot entry is created for the destination with the payment amount as its initial balance. The account's Sequence is set to the current ledger sequence (if DeletableAccounts is enabled) or 1 otherwise. The payment must meet the base reserve requirement (see Reserves), or it fails with tecNO_DST_INSUF_XRP.
All validation checks-including reserve requirements, deposit authorization, and destination tags-are performed before execution. See Failure Conditions for complete validation rules.
Cross-currency payments convert one currency to another through intermediary steps, leveraging MPTs, trust lines, order books, and AMM liquidity. These payments are executed when the payment involves non-XRP currencies, specifies SendMax, or includes Paths.
Cross-currency payment execution involves two complementary components: path finding and flow execution.
Before a payment can execute, viable routes from source to destination must be discovered. This is handled by the Path Finding Protocol, which searches the ledger graph to find potential paths through trust lines (for tokens), order books (for XRP, tokens, and MPTs), and AMM pools (for XRP, tokens, and MPTs).
Path finding can be initiated in three ways:
- User-specified paths: Clients can provide explicit
Pathsin the Payment transaction, bypassing path finding entirely - RPC path finding: Clients use RPC endpoints to discover paths before submitting the transaction:
path_find- Modern interface with streaming subscriptions (path_find create,path_find status,path_find close)ripple_path_find- Legacy one-shot path finding (deprecated but still supported)
- Default paths: When no paths are specified, the payment engine automatically generates simple default paths (direct trust line to issuer, XRP-bridged paths)
The RPC endpoints accept parameters like source_account, destination_account, destination_amount, and optionally source_currencies (to constrain which currencies the pathfinder considers as potential sources). For each source currency, the pathfinder creates a separate search instance, discovers paths through the ledger graph, simulates each path to measure quality and liquidity, ranks paths by quality, and returns the best paths to the client.
Once paths are available (whether from RPC, user-specified, or default paths), the Payment transaction executes by delegating to the Flow engine. Flow converts the paths into strands - sequences of executable steps that move value:
- DirectStepI: Transfers tokens between accounts via trust lines
- BookStep: Converts currencies through order books and AMM pools
- XRPEndpointStep: Transfers XRP to/from accounts
- MPTEndpointStep: Transfers MPTs to/from accounts
Flow ranks strands by quality (exchange rate) and iteratively consumes liquidity from the best available strands, dynamically re-ranking as their quality changes, until the payment amount is satisfied or all liquidity is exhausted. Each strand evaluation uses a two-pass algorithm: a reverse pass works backwards from the desired output to calculate required input, then a forward pass verifies and executes the actual transfer.
See Flow documentation for detailed mechanics of path execution, strand evaluation, and step-by-step state changes.
Footnotes
-
Static validation (preflight):
checkExtraFeatures,getFlagsMask,preflight↩ -
Both Amount and SendMax checked via isLegalNet:
Payment.cpp↩ -
DeliverMin checked for legal amount and positive value:
Payment.cpp↩ -
Validation against ledger view (preclaim):
checkPermission,preclaim↩ -
Direct XRP payment execution:
Payment.cpp↩