Optimizing transactions on the Solana network is crucial for ensuring their inclusion in blocks, especially during periods of high network traffic. This document outlines key strategies for improving transaction performance and reliability.
There are several strategies you can take to increase the likelihood of your transaction being included in a block and confirmed by the network quickly. These include:
- Utilize priority fees and dynamically update them using the Priority Fee API
- Use transaction simulation to accurately set compute unit limits
- Optimize Transaction Assembly
- Consider using Jito Bundles for complex, multi-transaction operations
- Confirm transaction landed and implement failover logic
Use Priority Fees
Solana's fee priority system allows you to set an additional fee on top of the base fee for a transaction, which gives your transaction a higher priority in the leader's queue. By bidding more for priority status, your transaction will be more likely to be confirmed quickly by the network.
Priority fees can be added to your transaction by including a setComputeUnitPrice
instruction in your transaction:
import { Transaction, ComputeBudgetProgram } from '@solana/web3.js';
const transaction = new Transaction();
// Add your transaction instructions here
const priorityFeeInstruction = ComputeBudgetProgram.setComputeUnitPrice({
microLamports: priorityFeeAmount,
});
transaction.add(priorityFeeInstruction);
Calculating the Right Priority Fee
QuickNode provides a Priority Fee API (Add-on Details | Docs) to fetch recent priority fees for a given Program over a specified number of blocks.
curl https://docs-demo.solana-mainnet.quiknode.pro/ \
-X POST \
-H "Content-Type: application/json" \
-H "x-qn-api-version: 1" \
--data '{
"jsonrpc": "2.0",
"id": 1,
"method": "qn_estimatePriorityFees",
"params": {
"last_n_blocks": 100,
"account": "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4",
"api_version": 2
}
}'
The method returns an object with a per_compute_unit
property that includes estimates for priority fees (in microlamports) based on recent network activity. The response provides fee estimates in four categories: low
, medium
, high
, and extreme
.
During periods of high network congestion, to increase the likelihood of successful transaction processing, we recommend using the 80th percentile or higher of recent priority fees. This corresponds to either the high
or extreme
priority fees returned by the qn_estimatePriorityFees
method.
Priority Fees Resources
- How to Use Priority Fees on Solana
- Solana Priority Fees Add-on
- Priority Fees Add-on Documentation
- Guide: How to Optimize Solana Transactions - Priority Fees
- Sample Code - Solana Web3.js (Legacy)
- Sample Code - Solana Web3.js 2.0
Optimize Compute Units
Optimizing compute units (CU) helps prevent transactions from being dropped due to exceeding block limits. Every transaction on Solana uses CU to process. The more complex the transaction, the more compute units it will consume. The network has a limit on the number of compute units that can be processed in a single block. If your transaction exceeds this limit, it will be dropped. Additionally, priority fees are a function of compute units, so lower compute means savings on fees.
As of October 2024, the following limits/defaults are in place:
Limits | Compute Units |
---|---|
Max Compute per block | 48 million |
Max Compute per account per block | 12 million |
Max Compute per transaction | 1.4 million |
Transaction Default Compute | 200,000 |
There are two main ways you can optimize compute units:
- On your client-side, you can simulate the transaction to determine the compute units it will consume and set the limit accordingly.
- In your Solana programs, you can design your programs to be compute-conscious to limit unnecessary computation when your program is invoked.
Setting Compute Unit Limits
To set a compute unit limit for your transaction, you can use the setComputeUnitLimit
instruction. This value is what is evaluated in a block's compute limits.
const testTransaction = new Transaction().add(testInstruction);
const computeUnitInstruction = ComputeBudgetProgram.setComputeUnitLimit({
units: targetComputeUnitsAmount
});
testTransaction.add(computeUnitInstruction);
Calculating the Right Compute Units
To estimate a transaction's expected compute units, you can use the simulateTransaction
method to simulate the transaction and determine the compute units it will consume. To make sure you calculate the correct compute, your simulated transaction must include a setComputeUnitLimit
instruction.
Use simulateTransaction
to determine the compute units a transaction will consume:
const testTransaction = new Transaction().add(testInstruction);
const simulation = await connection.simulateTransaction(testTransaction, {
replaceRecentBlockhash: true,
sigVerify: false,
});
const targetComputeUnitsAmount = simulation.value.unitsConsumed;
Efficient Program Design
If you are writing Solana programs, you should be aware that your program code can contribute significantly to the compute units consumed by a transaction. To optimize your programs, you can use the following techniques:
- Minimize unnecessary logging (if you must log a key, make sure to use
.key().log()
instead of just.key()
because base58 encoding is expensive) - User smaller data types (e.g., use u8 instead of u64 if appropriate for your use case)
- Utilize Zero Copy for large data structures (this will result in lower CU for serializing and deserializing the data)
- Save bumps into PDAs. By saving your bumps you can reduce the compute required to
find_program_address
Compute Units Resources
- Guide: How to Optimize Solana Transactions - Priority Fees
- Optimize Compute in Solana Programs
- Introduction to Solana Compute Units and Transaction Fees
Transaction Assembly Best Practices
Combine priority fees and compute unit optimization in your transaction assembly:
- Create a transaction with your instructions
- Fetch and add priority fees
- Add a priority fee instruction to the transaction
- Simulate the transaction with a compute unit limit instruction
- Add a compute unit limit instruction to the transaction with the computed limit from the simulation
- Fetch and add a recent blockhash to the transaction
- Sign and send the transaction
Check out our sample code here.
Utilize QuickNode SDK - Smart Transaction
The QuickNode SDK provides methods for handling the above steps for your:
sendSmartTransaction
: Creates and sends a transaction with optimized settings.prepareSmartTransaction
: Prepares an optimized transaction.
Example usage:
const signature = await endpoint.sendSmartTransaction({
transaction,
keyPair: sender,
feeLevel: "high"
});
Check out our docs for more details.
Use Jito Bundles
For advanced transaction optimization and bundling, consider using the Lil' JIT marketplace add-on. This add-on enables the creation of Jito Bundles, allowing for atomic execution of multiple transactions.
The add-on enables you to provide a tip to Jito validator producing the next block to prioritize your transactions (or bundles of transactions). To use the add-on, you must:
- Include a SOL transfer instruction in your transaction (or last transaction if you are bundling multiple transactions) to a Jito Tip Account (accesible via
getTipAccounts
method):
const tipAccounts = await rpc.getTipAccounts().send();
const jitoTipAddress = tipAccounts[Math.floor(Math.random() * tipAccounts.length)];
- Serialize your transaction(s) into base58 strings:
const transactionEncoder = getTransactionEncoder();
const base58Decoder = getBase58Decoder();
const base58EncodedTransactions = signedTransactions.map((transaction) => {
const transactionBytes = transactionEncoder.encode(transaction);
return base58Decoder.decode(transactionBytes) as Base58EncodedBytes;
});
- Send the transaction(s) to the the Jito validator client using
sendTransaction
orsendBundle
methods:
const bundleId = await lilJitRpc
.sendBundle(base58EncodedTransactions)
.send();
- Lil' JIT Docs
- Lil' JIT Marketplace Add-on
- Guide: What are Jito Bundles and How to Use Them?
- Sample Code: Send Jito Bundles
Transaction confirmation
To ensure your transaction has landed in a block, you should utilize the getSignatureStatuses
method to check the status of your transaction:
const signature = await connection.sendTransaction(transaction, [signer]);
const { value: statuses } = await connection.getSignatureStatuses([signature]);
Create a simple polling function to check on the status of your transaction. If it is not confirmed after 150 slots (about 60 seconds), your blockhash will expire, and you can retry the transaction.
- Sample Confirm Transaction Polling Function
- Guide: Solana Transaction Propagation - Handling Dropped Transactions
We ❤️ Feedback!
If you have any feedback or questions about this documentation, let us know. We'd love to hear from you!