Optimizing transactions on Solana 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 set compute unit limits accurately
- Use Stake-Weighted Quality of Service (SWQoS)
- Optimize Transaction Assembly
- Consider using Jito Bundles for complex, multi-transaction operations
- Confirm transaction landed and implement failover logic
Use Priority Fees
Solana's priority fees allow 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
. Additionally, the response includes a recommended
property that provides a recommended fee level for each compute unit based on the current network conditions.
During periods of high network congestion, to increase the likelihood of successful transaction processing, we recommend using the recommended
property of the response. We monitor the network conditions and adjust the fee level accordingly to maximize the likelihood of successful transaction processing.
Priority Fees Resources
- Understand Solana Priority Fees: Land Transactions Faster
- Solana Priority Fees Add-on
- Priority Fees Add-on Documentation
- Guide: How to Optimize Solana Transactions - Priority Fees
- Sample Code - Solana Kit
- Sample Code - Solana Web3.js (Legacy)
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
Use Stake-Weighted Quality of Service (SWQoS)
Stake-Weighted Quality of Service (SWQoS) is a newer mechanism to increase the likelihood of your transaction being included in a block. QuickNode provides a variety of SWQoS options:
-
Using Smart Transactions (or manually building the transaction following best practices) with a priority fee API setting of
recommended
or higher will use stake from QuickNode's own stake pool, and do the proper assembly of compute and priority fees for the user. Any user sending non-duplicate transactions withrecommended
priority fees or higher will now be routed through QuickNode's own stake pool. -
Using Jito bundles, i.e. (Lil' JIT's
sendTransaction
orsendBundle
methods) will use Jito's own stake pool, which has significant stake in the Solana network. -
For ultra-high priority transactions, including professional traders and high-frequency automated trading, Fastlane, enabled in the QuickNode marketplace, is a SWQoS option using a stake pool from the top 1% largest validators in Solana, and is the absolute best way to increase your transaction landing rate.
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 (with fee level set to the
recommended
value returned from the Priority Fee API) - 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: 'recommended',
})
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: Jito Bundles: What They Are 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!