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:
- Solana Web3.js (Legacy)
- Solana Kit
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:
- Utilize priority fees and dynamically update them using the Priority Fee API
- Simulate transactions to set compute unit limits accurately
- Optimize Transaction Assembly
- Implement proper blockhash management and expiration handling
- Use advanced retry strategies for better success rates
- Use Smart Transactions from the QuickNode SDK
- Consider using Jito Bundles for complex, multi-transaction operations
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:
- Solana Web3.js (Legacy)
- Solana Kit
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)
import {
Rpc,
createDefaultRpcTransport,
createRpc,
RpcTransport,
createJsonRpcApi,
RpcRequest
} from "@solana/kit";
import {
EstimatePriorityFeesResponse,
SolanaKitEstimatePriorityFeesParams
} from "./types";
interface createQuickNodeTransportParams {
endpoint: string;
}
type PriorityFeeApi = {
qn_estimatePriorityFees(params: SolanaKitEstimatePriorityFeesParams): EstimatePriorityFeesResponse;
// Add other methods here
}
function createQuickNodeTransport({ endpoint }: createQuickNodeTransportParams): RpcTransport {
const jsonRpcTransport = createDefaultRpcTransport({ url: endpoint });
return async <TResponse>(...args: Parameters<RpcTransport>): Promise<TResponse> => {
return await jsonRpcTransport(...args);
};
}
export function createPriorityFeeApi(endpoint: string): Rpc<PriorityFeeApi> {
const api = createJsonRpcApi<PriorityFeeApi>({
requestTransformer: (request: RpcRequest<any>) => {
const transformedRequest = {
...request,
params: request.params[0]
};
return transformedRequest;
},
responseTransformer: (response: any) => response.result,
});
const transport = createQuickNodeTransport({
endpoint,
});
return createRpc({ api, transport });
}
async function main() {
const quickNodeRpc = createPriorityFeeApi(''); // 👈 Replace with your QuickNode endpoint
const priorityFees = await quickNodeRpc.qn_estimatePriorityFees({
last_n_blocks: 100,
account: 'JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4',
api_version: 2
}).send();
console.log(priorityFees);
}
main();
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
- 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
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.
- Solana Web3.js (Legacy)
- Solana Kit
const testTransaction = new Transaction().add(testInstruction)
const computeUnitInstruction = ComputeBudgetProgram.setComputeUnitLimit({
units: targetComputeUnitsAmount,
})
testTransaction.add(computeUnitInstruction)
import {
pipe,
createTransactionMessage,
appendTransactionMessageInstructions,
} from "@solana/kit";
import {
getSetComputeUnitLimitInstruction,
getSetComputeUnitPriceInstruction,
} from "@solana-program/compute-budget";
// Increase CU limit from default 200,000 to 400,000
const computeUnitLimitInstruction = getSetComputeUnitLimitInstruction({
units: 400_000,
});
// Set priority fee (optional - without this, incremental cost per CU is 0)
const computeUnitPriceInstruction = getSetComputeUnitPriceInstruction({
microLamports: 10_000,
});
// Build transaction message
const transactionMessage = pipe(
createTransactionMessage({ version: 0 }),
(tx) => appendTransactionMessageInstructions([
computeUnitLimitInstruction,
computeUnitPriceInstruction,
...yourInstructions, // Add your other instructions here
], tx)
);
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
Refer to the code below that combines priority fees and compute unit optimization during transaction building:
- Solana Web3.js (Legacy)
- Solana Kit
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;
}
import {
Instruction,
Rpc,
SolanaRpcApi,
pipe,
createTransactionMessage,
appendTransactionMessageInstructions,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
getBase64EncodedWireTransaction,
createSolanaRpc,
lamports,
generateKeyPairSigner,
getAddressFromPublicKey,
signTransactionMessageWithSigners,
createDefaultRpcTransport,
createRpc,
RpcTransport,
createJsonRpcApi,
RpcRequest,
airdropFactory,
createSolanaRpcSubscriptions
} from "@solana/kit";
import {
getSetComputeUnitLimitInstruction,
getSetComputeUnitPriceInstruction,
} from "@solana-program/compute-budget";
import {
getTransferSolInstruction,
} from "@solana-program/system";
import {
EstimatePriorityFeesResponse,
SolanaKitEstimatePriorityFeesParams
} from "./types";
// Types for QuickNode priority fee estimation
type PriorityFeeApi = {
qn_estimatePriorityFees(params: SolanaKitEstimatePriorityFeesParams): EstimatePriorityFeesResponse;
};
// Helper function to create QuickNode RPC transport
function createQuickNodeTransport({ endpoint }: { endpoint: string }): RpcTransport {
const jsonRpcTransport = createDefaultRpcTransport({ url: endpoint });
return async <TResponse>(...args: Parameters<RpcTransport>): Promise<TResponse> => {
return await jsonRpcTransport(...args);
};
}
// Create priority fee API client
function createPriorityFeeApi(endpoint: string): Rpc<PriorityFeeApi> {
const api = createJsonRpcApi<PriorityFeeApi>({
requestTransformer: (request: RpcRequest<any>) => {
const transformedRequest = {
...request,
params: request.params[0]
};
return transformedRequest;
},
responseTransformer: (response: any) => response.result,
});
const transport = createQuickNodeTransport({ endpoint });
return createRpc({ api, transport });
}
// Simulate transaction to get compute units
async function getSimulationUnits(
rpc: Rpc<SolanaRpcApi>,
instructions: Instruction[],
payerSigner: any
): Promise<number | undefined> {
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
const testInstructions = [
getSetComputeUnitLimitInstruction({ units: 1_400_000 }),
...instructions,
];
const transactionMessage = pipe(
createTransactionMessage({ version: 0 }),
(tx) => appendTransactionMessageInstructions(testInstructions, tx),
(tx) => setTransactionMessageFeePayerSigner(payerSigner, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx)
);
const transaction = await signTransactionMessageWithSigners(transactionMessage);
const rawTx = getBase64EncodedWireTransaction(transaction);
try {
const simulation = await rpc.simulateTransaction(rawTx, {
commitment: 'confirmed',
encoding: 'base64',
sigVerify: false,
replaceRecentBlockhash: true,
}).send();
if (simulation.value.err != null || simulation.value.unitsConsumed == null) {
console.warn('Simulation failed:', simulation.value.err);
return undefined;
}
return Number(simulation.value.unitsConsumed);
} catch (error) {
console.error('Error during simulation:', error);
return undefined;
}
}
// Main function - Optimized transaction with priority fees and compute units
async function main() {
// Use devnet for testing (has faucet for funding accounts)
const rpc = createSolanaRpc('http://127.0.0.1:8899');
const rpcSubscriptions = createSolanaRpcSubscriptions('ws://127.0.0.1:8900');
const airdrop = airdropFactory({ rpc, rpcSubscriptions });
const QUICKNODE_ENDPOINT = "YOUR_QUICKNODE_ENDPOINT";
console.log('=== GENERATING ACCOUNTS ===');
// Generate sender and receiver accounts
const senderSigner = await generateKeyPairSigner();
const recipientSigner = await generateKeyPairSigner();
const senderAddress = await getAddressFromPublicKey(senderSigner.keyPair.publicKey);
const recipientAddress = await getAddressFromPublicKey(recipientSigner.keyPair.publicKey);
console.log('Sender:', senderAddress);
console.log('Recipient:', recipientAddress);
console.log('\n=== FUNDING SENDER ACCOUNT ===');
await airdrop({
recipientAddress: senderAddress,
lamports: lamports(1_000_000_000n),
commitment: 'confirmed',
});
console.log('\n=== CREATING TRANSFER INSTRUCTION ===');
// Create transfer instruction
const instructions: Instruction[] = [
getTransferSolInstruction({
source: senderSigner,
destination: recipientAddress,
amount: lamports(1_000_000n), // 0.001 SOL
}),
];
console.log('\n=== FETCHING PRIORITY FEES ===');
// Fetch priority fee using QuickNode API
const quickNodeRpc = createPriorityFeeApi(QUICKNODE_ENDPOINT);
const priorityFeesResponse = await quickNodeRpc.qn_estimatePriorityFees({
last_n_blocks: 100,
api_version: 2
}).send();
const priorityFee = priorityFeesResponse.recommended;
console.log('\n=== ADDING PRIORITY FEE INSTRUCTION ===');
// Add priority fee instruction
const priorityFeeInstruction = getSetComputeUnitPriceInstruction({
microLamports: priorityFee,
});
// Add priority fee instruction to the beginning of the instructions array
instructions.unshift(priorityFeeInstruction);
console.log(`Added priority fee instruction with ${priorityFee} microlamports`);
console.log('\n=== SIMULATING FOR COMPUTE UNITS ===');
// Simulate the transaction and add the compute unit limit instruction
const computeUnits = await getSimulationUnits(rpc, instructions, senderSigner);
if (computeUnits) {
console.log(`Transaction would consume: ${computeUnits} compute units`);
const adjustedUnits = Math.ceil(computeUnits * 1.1); // 10% margin
console.log(`Using compute units with 10% margin: ${adjustedUnits}`);
const computeUnitLimitInstruction = getSetComputeUnitLimitInstruction({
units: adjustedUnits
});
instructions.unshift(computeUnitLimitInstruction);
console.log('Added compute unit limit instruction');
} else {
console.log('Could not determine compute units from simulation, using default limit');
const computeUnitLimitInstruction = getSetComputeUnitLimitInstruction({
units: 200_000 // Default fallback
});
instructions.unshift(computeUnitLimitInstruction);
console.log('Added default compute unit limit instruction');
}
console.log('\n=== BUILDING TRANSACTION ===');
// Get latest blockhash
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
console.log('Latest blockhash:', latestBlockhash.blockhash);
// Create transaction message
const transactionMessage = pipe(
createTransactionMessage({ version: 0 }),
(tx) => appendTransactionMessageInstructions(instructions, tx),
(tx) => setTransactionMessageFeePayerSigner(senderSigner, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx)
);
console.log('\n=== SIGNING AND SENDING ===');
// Sign transaction
const signedTransaction = await signTransactionMessageWithSigners(transactionMessage);
const serializedTransaction = getBase64EncodedWireTransaction(signedTransaction);
// Send transaction
const signature = await rpc
.sendTransaction(serializedTransaction, {
preflightCommitment: 'confirmed',
encoding: 'base64',
skipPreflight: false,
maxRetries: 0n
})
.send();
console.log('\n✅ TRANSACTION COMPLETED!');
console.log('Signature:', signature);
}
main()
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:
Property | Processed | Confirmed | Finalized |
---|---|---|---|
Received block | X | X | X |
Block on majority fork | X | X | X |
Block contains target tx | X | X | X |
66%+ stake voted on block | - | X | X |
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.
- Solana Web3.js (Legacy)
- Solana Kit
// Disable RPC retry for manual control
const signature = await connection.sendRawTransaction(
transaction.serialize(),
{
skipPreflight: false,
maxRetries: 0
}
);
const signature = await rpc
.sendTransaction(serializedTransaction, {
preflightCommitment: 'confirmed',
encoding: 'base64',
skipPreflight: false,
maxRetries: 0n
})
.send();
A Retry Pattern Code Example
Complete implementation showing constant interval rebroadcasting with expiration handling.
- Solana Web3.js (Legacy)
- Solana Kit
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.");
}
import {
Instruction,
Rpc,
SolanaRpcApi,
pipe,
createTransactionMessage,
appendTransactionMessageInstructions,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
getBase64EncodedWireTransaction,
createSolanaRpc,
lamports,
generateKeyPairSigner,
getAddressFromPublicKey,
signTransactionMessageWithSigners,
createDefaultRpcTransport,
createRpc,
RpcTransport,
createJsonRpcApi,
RpcRequest,
airdropFactory,
createSolanaRpcSubscriptions
} from "@solana/kit";
import {
getSetComputeUnitLimitInstruction,
getSetComputeUnitPriceInstruction,
} from "@solana-program/compute-budget";
import {
getTransferSolInstruction,
} from "@solana-program/system";
import {
EstimatePriorityFeesResponse,
SolanaKitEstimatePriorityFeesParams
} from "./types";
// Types for QuickNode priority fee estimation
type PriorityFeeApi = {
qn_estimatePriorityFees(params: SolanaKitEstimatePriorityFeesParams): EstimatePriorityFeesResponse;
};
// Helper function to create QuickNode RPC transport
function createQuickNodeTransport({ endpoint }: { endpoint: string }): RpcTransport {
const jsonRpcTransport = createDefaultRpcTransport({ url: endpoint });
return async <TResponse>(...args: Parameters<RpcTransport>): Promise<TResponse> => {
return await jsonRpcTransport(...args);
};
}
// Create priority fee API client
function createPriorityFeeApi(endpoint: string): Rpc<PriorityFeeApi> {
const api = createJsonRpcApi<PriorityFeeApi>({
requestTransformer: (request: RpcRequest<any>) => {
const transformedRequest = {
...request,
params: request.params[0]
};
return transformedRequest;
},
responseTransformer: (response: any) => response.result,
});
const transport = createQuickNodeTransport({ endpoint });
return createRpc({ api, transport });
}
// Retry logic with blockheight-based boundary
async function sendTransactionWithRetries(
rpc: Rpc<SolanaRpcApi>,
serializedTransaction: string,
lastValidBlockHeight: bigint,
maxRetryBuffer: number
): Promise<string> {
const maxRetryBlockHeight = lastValidBlockHeight - BigInt(maxRetryBuffer);
console.log(`=== RETRY CONFIGURATION ===`);
console.log(`Last valid block height: ${lastValidBlockHeight}`);
console.log(`Max retry block height (with ${maxRetryBuffer} buffer): ${maxRetryBlockHeight}`);
let attempt = 0;
let lastError: any = null;
while (true) {
attempt++;
// Get current block height
const currentBlockHeight = await rpc.getBlockHeight({ commitment: 'confirmed' }).send();
console.log(`Attempt ${attempt}: Current block height: ${currentBlockHeight}`);
// Check if we've exceeded our retry boundary
if (currentBlockHeight > maxRetryBlockHeight) {
console.log(`❌ Retry boundary exceeded. Current: ${currentBlockHeight}, Max retry: ${maxRetryBlockHeight}`);
throw new Error(`Transaction retry timeout: exceeded block height boundary (${currentBlockHeight} >= ${maxRetryBlockHeight})`);
}
console.log(`📤 Sending transaction attempt ${attempt}...`);
// Send the transaction
const signature = await rpc.sendTransaction(serializedTransaction as any, {
encoding: 'base64',
skipPreflight: true,
maxRetries: 0n // We handle retries ourselves
}).send();
console.log(`✅ Transaction sent successfully on attempt ${attempt}!`);
console.log(`Signature: ${signature}`);
// Wait a bit for confirmation
console.log('Waiting for transaction confirmation...');
// Check transaction status
const status = await rpc.getSignatureStatuses([signature]).send();
const signatureStatus = status.value[0];
if (signatureStatus?.confirmationStatus === 'confirmed' || signatureStatus?.confirmationStatus === 'finalized') {
console.log(`🎉 Transaction confirmed with status: ${signatureStatus.confirmationStatus}`);
return signature;
} else if (signatureStatus?.err) {
console.log(`❌ Transaction failed with error:`, signatureStatus.err);
lastError = signatureStatus.err;
} else {
console.log(`⏳ Transaction still processing, will retry...`);
await new Promise(resolve => setTimeout(resolve, 500));
}
}
}
// Simulate transaction to get compute units
async function getSimulationUnits(
rpc: Rpc<SolanaRpcApi>,
instructions: Instruction[],
payerSigner: any
): Promise<number | undefined> {
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
const testInstructions = [
...instructions,
];
const transactionMessage = pipe(
createTransactionMessage({ version: 0 }),
(tx) => appendTransactionMessageInstructions(testInstructions, tx),
(tx) => setTransactionMessageFeePayerSigner(payerSigner, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx)
);
const transaction = await signTransactionMessageWithSigners(transactionMessage);
const rawTx = getBase64EncodedWireTransaction(transaction);
try {
const simulation = await rpc.simulateTransaction(rawTx, {
commitment: 'confirmed',
encoding: 'base64',
sigVerify: false,
replaceRecentBlockhash: true,
}).send();
if (simulation.value.err != null || simulation.value.unitsConsumed == null) {
console.warn('Simulation failed:', simulation.value.err);
return undefined;
}
return Number(simulation.value.unitsConsumed);
} catch (error) {
console.error('Error during simulation:', error);
return undefined;
}
}
// Main function - Optimized transaction with priority fees and compute units
async function main() {
// Use devnet for testing (has faucet for funding accounts)
const rpc = createSolanaRpc('http://127.0.0.1:8899');
const rpcSubscriptions = createSolanaRpcSubscriptions('ws://127.0.0.1:8900');
const airdrop = airdropFactory({ rpc, rpcSubscriptions });
const QUICKNODE_ENDPOINT = "YOUR_QUICKNODE_ENDPOINT";
console.log('=== GENERATING ACCOUNTS ===');
// Generate sender and receiver accounts
const senderSigner = await generateKeyPairSigner();
const recipientSigner = await generateKeyPairSigner();
const senderAddress = await getAddressFromPublicKey(senderSigner.keyPair.publicKey);
const recipientAddress = await getAddressFromPublicKey(recipientSigner.keyPair.publicKey);
console.log('Sender:', senderAddress);
console.log('Recipient:', recipientAddress);
await airdrop({
recipientAddress: senderAddress,
lamports: lamports(1_000_000_000n),
commitment: 'confirmed',
});
// Create transfer instruction
const instructions: Instruction[] = [
getTransferSolInstruction({
source: senderSigner,
destination: recipientAddress,
amount: lamports(1_000_000n), // 0.001 SOL
}),
];
console.log('\n=== FETCHING PRIORITY FEES ===');
// Fetch priority fee using QuickNode API
const quickNodeRpc = createPriorityFeeApi(QUICKNODE_ENDPOINT);
const priorityFeesResponse = await quickNodeRpc.qn_estimatePriorityFees({
last_n_blocks: 100,
api_version: 2
}).send();
const priorityFee = priorityFeesResponse.recommended;
console.log('\n=== ADDING PRIORITY FEE INSTRUCTION ===');
// Add priority fee instruction
const priorityFeeInstruction = getSetComputeUnitPriceInstruction({
microLamports: priorityFee,
});
// Add priority fee instruction to the beginning of the instructions array
instructions.unshift(priorityFeeInstruction);
console.log(`Added priority fee instruction with ${priorityFee} microlamports`);
console.log('\n=== SIMULATING FOR COMPUTE UNITS ===');
// Simulate the transaction and add the compute unit limit instruction
const computeUnits = await getSimulationUnits(rpc, instructions, senderSigner);
if (computeUnits) {
console.log(`Transaction would consume: ${computeUnits} compute units`);
const adjustedUnits = Math.ceil(computeUnits * 1.1); // 10% margin
console.log(`Using compute units with 10% margin: ${adjustedUnits}`);
const computeUnitLimitInstruction = getSetComputeUnitLimitInstruction({
units: adjustedUnits
});
instructions.unshift(computeUnitLimitInstruction);
console.log('Added compute unit limit instruction');
} else {
console.log('Could not determine compute units from simulation, using default limit');
const computeUnitLimitInstruction = getSetComputeUnitLimitInstruction({
units: 200_000 // Default fallback
});
instructions.unshift(computeUnitLimitInstruction);
console.log('Added default compute unit limit instruction');
}
console.log('\n=== BUILDING TRANSACTION ===');
// Get latest blockhash with current block height info
const { value: latestBlockhash } = await rpc.getLatestBlockhash({ commitment: 'confirmed' }).send();
const currentBlockHeight = await rpc.getBlockHeight({ commitment: 'confirmed' }).send();
console.log('Latest blockhash:', latestBlockhash.blockhash);
console.log('Current block height:', currentBlockHeight);
console.log('Last valid block height:', latestBlockhash.lastValidBlockHeight);
// Create transaction message
const transactionMessage = pipe(
createTransactionMessage({ version: 0 }),
(tx) => appendTransactionMessageInstructions(instructions, tx),
(tx) => setTransactionMessageFeePayerSigner(senderSigner, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx)
);
// Sign transaction
const signedTransaction = await signTransactionMessageWithSigners(transactionMessage);
const serializedTransaction = getBase64EncodedWireTransaction(signedTransaction);
console.log('\n=== SENDING WITH RETRY LOGIC ===');
// Send transaction with retry logic
try {
const signature = await sendTransactionWithRetries(
rpc,
serializedTransaction,
latestBlockhash.lastValidBlockHeight,
10 // 10 block buffer
);
console.log('\n✅ TRANSACTION COMPLETED!');
console.log('Final Signature:', signature);
} catch (error) {
console.error('\n❌ TRANSACTION FAILED AFTER ALL RETRIES');
console.error('Error:', error);
}
}
main().catch(console.error);
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.
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
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!