Path payments
In a path payment, the asset received differs from the asset sent. Rather than the operation transferring assets directly from one account to another, path payments cross through the SDEX and/or liquidity pools before arriving at the destination account. For the path payment to succeed, there has to be a DEX offer or liquidity pool exchange path in existence. It can sometimes take several hops of conversion to succeed.
For example:
Account A sells XLM → [buy XLM / sell ETH → buy ETH / sell BTC → buy BTC / sell USDC] → Account B receives USDC
It is possible for path payments to fail if there are no viable exchange paths.
For more information on the Stellar Decentralized Exchange and Liquidity Pools, see our Liquidity on Stellar: SDEX and Liquidity Pools Encyclopedia Entry.
Operations
Path payments use the Path Payment Strict Send or Path Payment Strict Receive operations.
Path Payment Strict Send
Allows a user to specify the amount of the asset to send. The amount received will vary based on offers in the order books and/or liquidity pools.
Path Payment Strict Receive
Allows a user to specify the amount of the asset received. The amount sent will vary based on the offers in the order books/liquidity pools.
Path payments - more info
- Path payments don’t allow intermediate offers to be from the source account as this would yield a worse exchange rate. You’ll need to either split the path payment into two smaller path payments or ensure that the source account’s offers are not at the top of the order book.
- Balances are settled at the very end of the operation.
- This is especially important when (
Destination, Destination Asset) == (Source, Send Asset
) as this provides a functionality equivalent to getting a no-interest loan for the duration of the operation.
- This is especially important when (
Destination min
is a protective measure, it allows you to specify a lower bound for an acceptable conversion. If offers in the order books are not favorable enough for the operation to deliver that amount, the operation will fail.
Example
First, ensure the receiver has a trustline established for the asset they will receive. In this example, we will use USDC as the asset received. The sender will send XLM, which will be converted to USDC through the path payment operation.
import {
Horizon,
Asset,
Keypair,
TransactionBuilder,
Networks,
BASE_FEE,
Operation,
Memo,
} from "@stellar/stellar-sdk";
const USDC_ISSUER = "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5"; // USDC issuer on Stellar Testnet
const USDC_ASSET = new Asset("USDC", USDC_ISSUER); // USDC asset on Stellar Testnet
const RECEIVER_SECRET = "S..."; // Receiver's secret key
const SENDER_SECRET = "S..."; // Sender's secret key
const horizonServer = new Horizon.Server("https://horizon-testnet.stellar.org");
// Create a USDC trustline for the receiver
const receiverKP = Keypair.fromSecret(RECEIVER_SECRET);
let account = await horizonServer.loadAccount(receiverKP.publicKey());
let transaction = new TransactionBuilder(account, {
fee: BASE_FEE * 100,
networkPassphrase: Networks.TESTNET,
})
.addOperation(
Operation.changeTrust({
asset: USDC_ASSET,
limit: "10",
}),
)
.addMemo(Memo.text("Trusting USDC"))
.setTimeout(30)
.build();
transaction.sign(receiverKP);
const resp = await SERVER.submitTransaction(transaction);
console.log("resp", resp);
Now let's send a path payment from the sender to the receiver, converting XLM to USDC.
// Use path payment to send XLM from the receiver to the sender, who receives USDC
let senderKP = Keypair.fromSecret(SENDER_SECRET);
let account = await horizonServer.loadAccount(senderKP.publicKey());
let transaction = new TransactionBuilder(account, {
fee: BASE_FEE * 100,
networkPassphrase: Networks.TESTNET,
})
.addOperation(
Operation.pathPaymentStrictReceive({
sendAsset: Asset.native(), // Sending XLM
sendMax: "10", // Maximum amount of XLM to send
destAsset: USDC_ASSET, // Receiving USDC
destAmount: "1", // Amount of USDC to receive
destination: receiverKP.publicKey(), // Receiver's public key
}),
)
.addMemo(Memo.text("XLM to USDC"))
.setTimeout(30)
.build();
transaction.sign(senderKP);
const resp = await horizonServer.submitTransaction(transaction);
console.log("resp", resp);
Guides in this category:
📄️ Create an account
Learn about creating Stellar accounts, keypairs, funding, and account basics.
📄️ Send and receive payments
Learn to send payments and watch for received payments on the Stellar network.
📄️ Channel accounts
Create channel accounts to submit transactions to the network at a high rate.
📄️ Claimable balances
Split a payment into two parts by creating a claimable balance.
📄️ Clawbacks
Use clawbacks to burn a specific amount of a clawback-enabled asset from a trustline or claimable balance.
📄️ Fee-bump transactions
Use fee-bump transactions to pay for transaction fees on behalf of another account without re-signing the transaction.
📄️ Sponsored reserves
Use sponsored reserves to pay for base reserves on behalf of another account.
📄️ Path payments
Send a payment where the asset received differs from the asset sent.
📄️ Pooled accounts: muxed accounts and memos
Use muxed accounts to differentiate between individual accounts in a pooled account.
📄️ Install and deploy a smart contract with code
Install and deploy a smart contract with code.
📄️ Install WebAssembly (Wasm) bytecode using code
Install the Wasm of the contract using js-stellar-sdk.
📄️ Invoke a contract function in a Stellar transaction using SDKs
Use the Stellar SDK to create, simulate, and assemble a transaction.
📄️ simulateTransaction RPC method guide
simulateTransaction examples and tutorials guide.
📄️ Submit a transaction to Stellar RPC using the JavaScript SDK
Use a looping mechanism to submit a transaction to the RPC.