Send a Payment
This guide sends a payment from one account to another: build a transaction, sign it, and submit it to the network. It is the core write flow every app needs.
Prerequisites
- A funded source account and its keypair. If you do not have one, see Connect and Fund an Account.
- The examples run on testnet, so they are free and safe to repeat.
Build the transaction
A payment is one operation inside a transaction. Load the source account for its
current sequence number (the per-account counter Stellar uses to order
transactions), then build a transaction with a single
Operation.payment. Here
source is your funded keypair from
Connect and Fund an Account, and destinationId
is the recipient’s public key (a G... string):
import { Horizon, TransactionBuilder, Operation, Asset, Networks, BASE_FEE,} from "@stellar/stellar-sdk";
const horizon = new Horizon.Server("https://horizon-testnet.stellar.org");const account = await horizon.loadAccount(source.publicKey());
const tx = new TransactionBuilder(account, { fee: BASE_FEE, networkPassphrase: Networks.TESTNET,}) .addOperation( Operation.payment({ destination: destinationId, asset: Asset.native(), amount: "100", }), ) .setTimeout(30) .build();fee is the maximum per-operation fee you’re willing to pay, in stroops
(BASE_FEE is 100; one XLM is ten million stroops). It’s a cap, not a fixed
charge — the network only deducts what it actually needs at submission time, so
a high fee (say, 5 XLM) makes the transaction more likely to be included when
the network is busy without meaning all 5 XLM gets spent. amount is a string
in whole units ("100" is 100 XLM). The
destination must already exist on the network; to create and fund a brand-new
account, use Operation.createAccount instead.
Sign and submit
A built transaction is unsigned. Sign it with the source account’s keypair, then submit it to Horizon:
tx.sign(source);const result = await horizon.submitTransaction(tx);
result.hash; // the transaction hashresult.successful; // true when it was appliedIf submission fails, Horizon returns the error in the rejected promise, so wrap
the call in try/catch to inspect the failure. See
Handle Errors for reading result codes and
Horizon’s error responses.
Add a memo
Many services (exchanges, custodians) require a memo to route a payment. Add a
Memo to the builder chain before
.build():
import { Memo } from "@stellar/stellar-sdk";
// ...the same builder as above, with one more line in the chain: .addMemo(Memo.text("invoice-42"))Send an issued asset
To send an issued asset instead of XLM, pass an Asset
built from its code and the issuer account’s public key (issuerId). Everything
else is the same:
const usd = new Asset("USD", issuerId);
Operation.payment({ destination: destinationId, asset: usd, amount: "100" });The destination must already trust this asset, otherwise the payment fails. Setting up an issuer and trustlines is covered in Issue an Asset.
Put it together
The whole native-payment flow as one runnable script. It funds a throwaway
source and destination with friendbot so the example runs end to end; in
your app, source is your existing funded account and destination is any
account that already exists:
import { Keypair, Horizon, TransactionBuilder, Operation, Asset, Memo, Networks, BASE_FEE,} from "@stellar/stellar-sdk";
const horizon = new Horizon.Server("https://horizon-testnet.stellar.org");
async function main() { const source = Keypair.random(); const destination = Keypair.random(); await Promise.all([ horizon.friendbot(source.publicKey()).call(), horizon.friendbot(destination.publicKey()).call(), ]);
const account = await horizon.loadAccount(source.publicKey());
const tx = new TransactionBuilder(account, { fee: BASE_FEE, networkPassphrase: Networks.TESTNET, }) .addOperation( Operation.payment({ destination: destination.publicKey(), asset: Asset.native(), amount: "100", }), ) .addMemo(Memo.text("thanks!")) .setTimeout(30) .build();
tx.sign(source); const result = await horizon.submitTransaction(tx);
console.log("Submitted:", result.hash, "successful:", result.successful);}
main().catch(console.error);You can now move value on the network. Next, learn to issue your own asset and set up trustlines.