Skip to main content

Strategies to Optimize Solana Transactions

Updated on
Sep 23, 2025

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.

This guide is available in Solana Web3.js (Legacy v 1.x) and Solana Kit (v 2.x) versions. Select the appropriate tab to view the code snippets and instructions for your preferred library:

note

The explanations throughout this guide primarily reference the Solana Web3.js (Legacy) code examples. However, the Solana Kit implementations follow the same conceptual patterns and logic—just with updated syntax and method calls.

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:


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.


Priority Fees Resources


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.

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)

Compute Units Resources


Transaction Assembly Best Practices

Refer to the code below that combines priority fees and compute unit optimization during transaction building:

import { Connection, Keypair, Transaction, ComputeBudgetProgram } from "@solana/web3.js";
import { fetchEstimatePriorityFees, getSimulationUnits } from "./helpers"; // Update with your path to our methods

const endpoint = YOUR_QUICKNODE_ENDPOINT; // Replace with your QuickNode endpoint
const keyPair = Keypair.generate();// derive your keypair from your secret key

async function main(){
// 1. Establish a connection to the Solana cluster
const connection = new Connection(endpoint);

// 2. Create your transaction
const transaction = new Transaction();
// ... add instructions to the transaction

// 3. Fetch the recent priority fees
const { result } = await fetchEstimatePriorityFees({ endpoint });
const priorityFee = result.recommended; // Replace with your priority fee level based on your business requirements, e.g. result.per_compute_unit['high']

// 4. Create a PriorityFee instruction and add it to your transaction
const priorityFeeInstruction = ComputeBudgetProgram.setComputeUnitPrice({
microLamports: priorityFee,
});
transaction.add(priorityFeeInstruction);

// 5. Simulate the transaction and add the compute unit limit instruction to your transaction
let [units, recentBlockhash] =
await Promise.all([
getSimulationUnits(
connection,
transaction.instructions,
keyPair.publicKey

),
connection.getLatestBlockhash(),
]);
if (units) {
units = Math.ceil(units * 1.05); // margin of error
transaction.add(ComputeBudgetProgram.setComputeUnitLimit({ units }));
}

// 6. Sign and send your transaction
transaction.feePayer = keyPair.publicKey;
transaction.recentBlockhash = recentBlockhash.blockhash;
transaction.sign(keyPair);

const hash = await connection.sendRawTransaction(
transaction.serialize(),
{ skipPreflight: true, maxRetries: 0 }
);

return hash;
}

Blockhash Management & Expiration

Solana transactions include a recent blockhash, which validators use to ensure the transaction is both fresh and unique. This mechanism prevents replay attacks, where the same transaction could be resubmitted multiple times. Blockhashes expire after approximately 151 blocks (about 60 seconds at ~400 ms per block), after which any transaction referencing them becomes invalid. If a transaction is not confirmed within this window, it must be rebuilt with a new blockhash before being retried.

Commitment Levels

Each commitment level provides different guarantees about transaction finality:

PropertyProcessedConfirmedFinalized
Received blockXXX
Block on majority forkXXX
Block contains target txXXX
66%+ stake voted on block-XX
31+ confirmed blocks built atop block--X

Source: Anza Docs - Commitments

Transaction Retry Strategies

Implement custom retry logic for better transaction success rates during network congestion.

The maxRetries Strategy

Take manual control of transaction rebroadcasting during network congestion.

// Disable RPC retry for manual control
const signature = await connection.sendRawTransaction(
transaction.serialize(),
{
skipPreflight: false,
maxRetries: 0
}
);

A Retry Pattern Code Example

Complete implementation showing constant interval rebroadcasting with expiration handling.

import {
Keypair,
Connection,
LAMPORTS_PER_SOL,
SystemProgram,
Transaction,
} from "@solana/web3.js";

import { sha512 } from "@noble/hashes/sha2.js";

import * as ed from "@noble/ed25519";

import { setTimeout as delay } from "timers/promises";

ed.hashes.sha512 = sha512;

const BUFFER = 10;

const { secretKey, publicKey } = await ed.keygenAsync();
const secretKey64 = new Uint8Array(64);
secretKey64.set(secretKey, 0);
secretKey64.set(publicKey, 32);

const payer = Keypair.fromSecretKey(secretKey64);
const toAccount = Keypair.generate().publicKey;

console.log("💧 Airdropping 1 SOL to payer...");
const connection = new Connection("https://api.devnet.solana.com", "confirmed");
const airdropSignature = await connection.requestAirdrop(
payer.publicKey,
LAMPORTS_PER_SOL,
);
await connection.confirmTransaction(
{ signature: airdropSignature },
"confirmed",
);

const blockhashResponse = await connection.getLatestBlockhash();
const lastValidBlockHeight = blockhashResponse.lastValidBlockHeight - BUFFER;

const transaction = new Transaction({
feePayer: payer.publicKey,
blockhash: blockhashResponse.blockhash,
lastValidBlockHeight,
}).add(
SystemProgram.transfer({
fromPubkey: payer.publicKey,
toPubkey: toAccount,
lamports: 1_000_000,
}),
);

const message = transaction.serializeMessage();

const signature = ed.sign(message, secretKey);

transaction.addSignature(payer.publicKey, Buffer.from(signature));

const rawTransaction = transaction.serialize();

let blockheight = await connection.getBlockHeight();
let attempts = 0;
let landed = false;

// Retry loop
while (blockheight < lastValidBlockHeight && !landed) {
attempts++;
console.log(
`\n📤 Attempt #${attempts} — current block: ${blockheight}, cutoff: ${lastValidBlockHeight}`,
);

try {
const signature = await connection.sendRawTransaction(rawTransaction, {
skipPreflight: true,
maxRetries: 0,
});
console.log(`📝 Sent tx, signature: ${signature}`);

// Poll for confirmation after sending
const status = await connection.getSignatureStatus(signature, {
searchTransactionHistory: false,
});

console.log("🔎 Status check:", status.value);

if (
status.value?.confirmationStatus === "confirmed" ||
status.value?.confirmationStatus === "finalized"
) {
console.log(`✅ Transaction confirmed after ${attempts} attempt(s)!`);
landed = true;
break;
}
} catch (error) {
console.error("Error sending transaction:", error);
}

await delay(500);
blockheight = await connection.getBlockHeight();
}

if (!landed) {
console.warn("Transaction not confirmed before blockhash expired.");
}

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 with recommended priority fees or higher will now be routed through QuickNode's own stake pool.

  • Using Jito bundles, i.e. (Lil' JIT's sendTransaction or sendBundle 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.

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:


  1. 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)]

  1. 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
})

  1. Send the transaction(s) to the the Jito validator client using sendTransaction or sendBundle methods:
const bundleId = await lilJitRpc.sendBundle(base58EncodedTransactions).send()

Wrapping Up

For a comprehensive overview of Solana transaction optimization with detailed explanations, check out our complete transaction optimization guide.

We ❤️ Feedback!

If you have any feedback or questions about this documentation, let us know. We'd love to hear from you!

Share this doc