I’m working on a Solana project where I need to swap tokens using the Jupiter API. The code occasionally runs properly (successful transaction), but I usually get an issue where the transaction expired and failed with the TransactionExpiredBlockheightExceededError even after retrying with a new blockhash.
I’ve tried hardcoding prioritizationFeeLamports
to a set value instead of auto, and also tried using dynamicComputeUnitLimit: true
import { Connection, Keypair, VersionedTransaction, TransactionExpiredBlockheightExceededError } from '@solana/web3.js';
import fetch from 'cross-fetch';
import { Wallet } from '@project-serum/anchor';
import bs58 from 'bs58';
import { privateKey } from './header.js';
import { publicKey } from './header.js';
// Connection to the Solana mainnet
const connection = new Connection('https://api.mainnet-beta.solana.com', 'confirmed');
// Convert privateKey to a Uint8Array using bs58 decoding
const wallet = new Wallet(Keypair.fromSecretKey(bs58.decode(privateKey)));
// Define the mint addresses as constants
const INPUT_MINT = 'So11111111111111111111111111111111111111112';
const OUTPUT_MINT = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';
const amount = 10000000;
// Define other parameters
const slippageBps = 50;
// Use the constants in the URL
const url = `https://quote-api.jup.ag/v6/quote?inputMint=${INPUT_MINT}&outputMint=${OUTPUT_MINT}&amount=${amount}&slippageBps=${slippageBps}`;
console.log(url); // Just to check the URL with the constants
// Fetch quote response
const quoteResponse = await fetch(url).then(res => res.json());
console.log({ quoteResponse });
// Get serialized transactions for the swap
const { swapTransaction } = await (
await fetch('https://quote-api.jup.ag/v6/swap', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
quoteResponse,
userPublicKey: publicKey,
wrapAndUnwrapSol: true,
prioritizationFeeLamports: "auto",
})
})
).json();
// Deserialize the transaction
const swapTransactionBuf = Buffer.from(swapTransaction, 'base64');
var transaction = VersionedTransaction.deserialize(swapTransactionBuf);
console.log(transaction);
// Sign the transaction
transaction.sign([wallet.payer]);
// Execute the transaction
const rawTransaction = transaction.serialize();
console.log(rawTransaction);
const sendTransaction = async () => {
let txid; // Declare txid here to use it later in case of retry
try {
// Fetch the latest blockhash just before sending the transaction
const latestBlockHash = await connection.getLatestBlockhash();
console.log(latestBlockHash);
// Send the transaction
txid = await connection.sendRawTransaction(rawTransaction, {
skipPreflight: true,
maxRetries: 2
});
// Confirm the transaction
await connection.confirmTransaction({
blockhash: latestBlockHash.blockhash,
lastValidBlockHeight: latestBlockHash.lastValidBlockHeight,
signature: txid
});
console.log(`Transaction confirmed: https://solscan.io/tx/${txid}`);
} catch (error) {
if (error instanceof TransactionExpiredBlockheightExceededError) {
console.error('Transaction expired, resubmitting...');
// Retry by getting the latest blockhash again
const newBlockHash = await connection.getLatestBlockhash();
console.log('Using new blockhash for retry:', newBlockHash);
// Update transaction with the new blockhash
const retryTransaction = VersionedTransaction.deserialize(swapTransactionBuf);
retryTransaction.sign([wallet.payer]);
// Resend with the new blockhash
txid = await connection.sendRawTransaction(retryTransaction.serialize(), {
skipPreflight: true,
maxRetries: 2
});
console.log("Retrying with new blockhash...");
// Confirm the transaction again with the new blockhash
await connection.confirmTransaction({
blockhash: newBlockHash.blockhash,
lastValidBlockHeight: newBlockHash.lastValidBlockHeight,
signature: txid
});
console.log(`Transaction retried and confirmed: https://solscan.io/tx/${txid}`);
} else {
console.error('Error confirming transaction:', error);
}
}
};
sendTransaction();
Output:
TransactionExpiredBlockheightExceededError: Signature 2PT8n78c9YC5CH7Kp6YfBHgwMHe67iRpirQN4aNKSqDAo8uYwenxCyhWjYzUKnb3u4GdLBgrw37jLkXgq9bWBQUK has expired: block height exceeded.