Skip to main content

Fees, Surge Pricing, and Fee Strategies

Stellar requires a small fee for all transactions to prevent ledger spam and prioritize transactions during surge pricing. All fees are paid in lumens (XLM).

Network fees on Stellar

The fee for a given transaction equals the number of operations in the transaction multiplied by the effective base fee for the given ledger (transaction fee = # of operations * effective base fee).

Effective base fee: the fee required per operation for a transaction to make it to the ledger. This cannot be lower than 100 stroops per operation (the network minimum).

Stroop: the smallest unit of a lumen, one ten-millionth of a lumen (.0000001 XLM).

When you decide on a base fee for a transaction, you specify the maximum amount that you’re willing to pay per operation in that transaction. That does not necessarily mean that you’ll pay that amount, you will only be charged the lowest amount needed for your transaction to make it to the ledger. If network traffic is light and the number of submitted operations is below the network ledger limit (which is configured by validators, currently 1,000 ops per ledger on the pubnet and 100 ops per ledger on the testnet), you will only pay the network minimum (also configured by validators, currently 100 stroops).

Alternatively, your transaction may not make it to the ledger if the effective base fee is higher than your base fee bid. When network traffic exceeds the ledger limit, the network enters into surge pricing mode, and your fee becomes a max bid. This amount can vary depending on network activity, but we recommend submitting a base fee of 100,000 stroops for consumer-facing applications. But it's up to you.

Fees are deducted from the source account unless there is a fee-bump transaction that states otherwise. To learn about fee-bump transactions, see our Fee-Bump Transaction Encyclopedia Entry

The lumens collected from transaction fees go into a locked account and are not given to or used by anyone.

Surge pricing

When the number of operations submitted to a ledger exceeds the network capacity (1,000 ops per ledger on the Pubnet; 100 ops per ledger on the Testnet), the network enters surge pricing mode. During this time, the network uses market dynamics to decide which submissions to include in the ledger — transactions that offer a higher fee per operation make it to the ledger first.

If multiple transactions offer the same base fee during surge pricing, the transactions are shuffled randomly and the transactions at the top make the ledger. The rest of the transactions are pushed to the next ledger or discarded if they’ve been waiting for too long. If your transaction is discarded, Horizon will return a timeout error.

The goal of the transaction pricing specification, which you can read in full here, is to maximize network throughput while minimizing transaction fees.

Fee strategies

There are three primary methods to deal with fee fluctuations and surge pricing:

  • Method 1: Set the highest fee you’re comfortable paying. This does not mean that you’ll pay that amount on every transaction- you will only pay what’s necessary to get you into the ledger. Under normal (non-surge) circumstances, you will only pay the standard fee even if you have a higher maximum fee set. This method is simple, convenient, and efficient but can still potentially fail.

  • Method 2: Track fee fluctuations with the fee_stats endpoint. Use this to make specific, informed choices about the fee you’re comfortable paying. All three of the SDF-maintained SDKs allow you to poll the /fee_stats endpoint: Go, Java, JavaScript. This method provides reliable submissions but is more inefficient.

  • Method 3: Use a fee-bump transaction

Set the highest fee you’re comfortable paying

In general, it’s a good idea to choose the highest fee you’re willing to pay per operation for your transaction to make it to the ledger.

Wallet developers may want to offer users a chance to specify their own base fee, though it may make more sense to set a persistent global base fee that’s above the market rate since the average user probably doesn’t care if they’re paying 0.8 cents or 0.00008 cents.

Track fee fluctuations

In general, it is important to track fee costs. If network fees surge beyond what you’re willing to pay, consider waiting for activity to die down or periodically trying to resubmit the transaction with the same fee.

If you want to match a fee error exactly, you may write something like this:

function isFeeError(error) {
return (
error.response !== undefined &&
error.status === 400 &&
error.extras &&
error.extras.result_codes.transaction === sdk.TX_INSUFFICIENT_FEE
);
}

There are more streamlined ways to combine errors, which we use below to demonstrate the (very) specific check.

Example: suppose we want a fairly conservative fee-paying strategy- we’re only willing to pay a 10% higher fee than the average transaction paid.

// when submitting any transaction, first query fee stats
server.feeStats().then(function (response) {
let avgFee = parseFloat(response.fee_charged.p50);
let tx = base.TransactionBuilder(someAccount, {
fee: (avgFee * 1.10).toFixed(0), // bump & convert to int
networkPassphrase: // ...
});
// ...build the rest of the tx...
tx.sign(someAccount);
return server.submitTransaction(tx);
});

Fee-bumps on past transactions

Even with a liberal fee-paying policy, your transaction may fail to make it into the ledger due to insufficient funds or untimely surges. Fee-bump transactions can solve this problem. The following snippet shows you how to resubmit a transaction with a higher fee (as long as you have the original transaction envelope):

// Let `lastTx` be some transaction that fails submission due to high fees, and
// `lastFee` be the maximum fee (expressed as an int) willing to be paid by
// `account` for `lastTx`.
server.submitTransaction(lastTx).catch(function (error) {
if (isFeeError(error)) {
let bump = sdk.TransactionBuilder.buildFeeBumpTransaction(
account, // account that will PAY the new fee
lastFee * 10, // new fee
lastTx, // the (entire) failing transaction
server.networkPassphrase
);
bump.sign(someAccount);
return server.submitTransaction(bump);
}
// ...other error conditions...
}).then(...);

Suppose you submit two distinct transactions with the same source account and sequence number, and the second transaction is a fee-bump transaction. In that case, the second transaction will be included in the transaction queue, replacing the first transaction if and only if the fee bid of the second transaction is at least 10x the fee bid of the first transaction.

This value can typically be found in the fee_charged field of the transaction response under the tx_insufficient_fee error case.