When interacting with Solana's blockchain, transactions may sometimes confirm successfully on-chain but fail to execute your desired actions due to common pitfalls in transaction preparation and submission.
Here’s a concise guide, based on practical experience, to ensure your transactions using @solana/web3.js
execute reliably and achieve the intended results.
Step 1: Use Versioned Transactions Correctly
Starting from web3.js version 1.95+
, Versioned Transactions are recommended for improved transaction handling:
const transactionMessage = new TransactionMessage({
instructions,
recentBlockhash: blockhash,
payerKey: publicKey,
}).compileToV0Message();
const versionedTx = new VersionedTransaction(transactionMessage);
Step 2: Avoid Duplicate Compute Unit Instructions
Solana does not allow duplicate ComputeBudgetProgram
instructions within the same transaction. To avoid the DuplicateInstruction
error, always remove existing ComputeBudgetProgram instructions from your original transaction before adding new ones:
const filteredInstructions = transaction.instructions.filter(
(ix) => !ix.programId.equals(ComputeBudgetProgram.programId)
);
const instructions = [
ComputeBudgetProgram.setComputeUnitLimit({ units: optimalUnits }),
ComputeBudgetProgram.setComputeUnitPrice({ microLamports }),
...filteredInstructions,
];
Step 2: Dynamically Calculate Optimal Compute Units (CU)
Instead of setting the CU limit to the maximum (1,400,000
) every time, dynamically calculate the actual required compute units by simulating your transaction first:
const simulation = await connection.simulateTransaction(transaction, { sigVerify: false });
const consumedCU = simulation.value.unitsConsumed || 200_000;
const optimalCU = Math.ceil(consumedCU * 1.2); // 20% buffer
This way, your transaction has a suitable CU buffer, reducing the chance of transaction failure without excessive fees.
Step 2: Dynamically Set Priority Fee
Rather than using a fixed priority fee, dynamically adjust based on network conditions or business urgency. For instance, to dynamically adjust fees:
const microLamports = businessPriority === 'high' ? 5000 : 1000;
const computePriceIx = ComputeBudgetProgram.setComputeUnitPrice({ microLamports });
Step 2: Correct Transaction Signing
When using VersionedTransaction
, always sign transactions in a single step. Splitting the signing process will overwrite previous signatures:
// Correct way (sign all at once)
opTx.sign([userKeypair, newAccountKeypair]);
Step 3: Ensure Successful Simulation Before Sending
Always simulate your transaction before sending to ensure there are no errors at execution time:
const simResult = await connection.simulateTransaction(opTx, { sigVerify: true });
if (simResult.value.err) {
throw new Error(`Simulation failed: ${JSON.stringify(simResult.value.err)}`);
}
Step 4: Confirm Transaction with Blockhash and Block Height
Finally, confirm your transaction robustly by explicitly passing the blockhash and the last valid block height:
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
const signature = await connection.sendTransaction(opTx, { maxRetries: 5 });
await connection.confirmTransaction(
{ signature, blockhash, lastValidBlockHeight },
'confirmed'
);
By following these guidelines, you can ensure reliable, efficient, and error-free transactions when working with Solana's web3.js library.