I am trying to do a simple swap on Solana block chain and I have tried both Alchemy and Quicknode but the success rate of my swap is very low.. 2 out of three times it fails and I cant even figure a way to properly log the reason ofthe failure
below is my script:
const axios = require('axios');
const dotenv = require('dotenv');
const { Connection, Keypair, sendAndConfirmRawTransaction, VersionedTransaction, ComputeBudgetProgram, Transaction, TransactionInstruction, TransactionMessage, PublicKey } = require('@solana/web3.js');
const winston = require('winston');
require('winston-daily-rotate-file');
dotenv.config();
const ALCHEMY_RPC_ENDPOINT = process.env.ALCHEMY_RPC_ENDPOINT; // Alchemy's RPC endpoint
const QUICKNODE_ENDPOINT = process.env.QUICKNODE_ENDPOINT; // QuickNode's endpoint for fetching priority fees
const WALLET_PRIVATE_KEY = process.env.WALLET_PRIVATE_KEY;
const SOL_AMOUNT = 0.3 * 1e9; // Amount of SOL to swap in lamports
const MAX_RETRIES = 1; // Maximum number of retries for transactions
const SLIPPAGE_BPS = 1000; // Slippage in basis points (3%)
const DEFAULT_PRIORITY_FEE = 50000; // Default priority fee in microLamports
// Set up Winston logger
const transport = new winston.transports.DailyRotateFile({
filename: 'logs/application-%DATE%.log',
datePattern: 'YYYY-MM-DD',
zippedArchive: true,
maxSize: '20m',
maxFiles: '14d'
});
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.printf(({ timestamp, level, message }) => `${timestamp} [${level}]: ${message}`)
),
transports: [
transport,
new winston.transports.Console()
]
});
async function getQuote(inputMint, outputMint, amount, slippageBps) {
const url = `https://quote-api.jup.ag/v6/quote?inputMint=${inputMint}&outputMint=${outputMint}&amount=${amount}&slippageBps=${slippageBps}`;
logger.info(`Fetching quote from: ${url}`);
try {
const response = await axios.get(url);
logger.info('Quote fetched successfully:', JSON.stringify(response.data));
return response.data;
} catch (error) {
logger.error('Error fetching quote:', error.response?.data || error.message);
return null;
}
}
async function checkTransactionStatus(connection, txid) {
try {
const confirmation = await connection.confirmTransaction(txid, 'confirmed');
logger.info(`Transaction confirmation response: ${JSON.stringify(confirmation)}`);
if (confirmation.value.err) {
logger.error(`Transaction failed with error: ${JSON.stringify(confirmation.value.err)}`);
throw new Error(`Transaction failed with error: ${JSON.stringify(confirmation.value.err)}`);
}
return confirmation;
} catch (error) {
logger.error(`Transaction status check failed: ${error.message}`);
throw new Error(`Transaction status check failed: ${error.message}`);
}
}
async function simulateTransaction(connection, transaction) {
try {
const simulation = await connection.simulateTransaction(transaction);
logger.info('Simulation result:', JSON.stringify(simulation));
if (simulation.value.err) {
logger.error('Simulation failed with error:', JSON.stringify(simulation.value.err));
logger.error('Simulation logs:', JSON.stringify(simulation.value.logs));
throw new Error(`Simulation failed with error: ${JSON.stringify(simulation.value.err)}`);
}
logger.info('Transaction simulation logs:', JSON.stringify(simulation.value.logs));
return simulation.value.unitsConsumed;
} catch (error) {
logger.error(`Transaction simulation failed: ${error.message}`, { error: error });
throw new Error(`Transaction simulation failed: ${error.message}`);
}
}
async function executeSwap(quoteResponse, payer, connection, priorityFee, computeUnitLimit) {
logger.info("Preparing to execute swap...");
const postData = {
quoteResponse: quoteResponse,
userPublicKey: payer.publicKey.toString(),
wrapAndUnwrapSol: true,
priorityFee: priorityFee
};
try {
const swapResponse = await axios.post('https://quote-api.jup.ag/v6/swap', postData, {
headers: { 'Content-Type': 'application/json' }
});
logger.info('Swap API response:', JSON.stringify(swapResponse.data));
const transactionBuffer = Buffer.from(swapResponse.data.swapTransaction, 'base64');
const transaction = VersionedTransaction.deserialize(transactionBuffer);
transaction.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
transaction.sign([payer]);
const unitsConsumed = await simulateTransaction(connection, transaction);
logger.info(`Units consumed by the transaction: ${unitsConsumed}`);
logger.info("Sending transaction...");
let txid = null;
let attempt = 0;
while (attempt < MAX_RETRIES) {
try {
txid = await sendAndConfirmRawTransaction(connection, transaction.serialize(), {
skipPreflight: true,
preflightCommitment: 'confirmed'
});
logger.info(`Transaction sent, ID: ${txid}`);
const status = await checkTransactionStatus(connection, txid);
logger.info(`Transaction status: ${JSON.stringify(status)}`);
return txid;
} catch (error) {
logger.error(`Attempt ${attempt + 1} failed: ${error.message}`, { error: error });
attempt++;
}
}
throw new Error(`Swap failed after ${MAX_RETRIES} attempts.`);
} catch (error) {
logger.error('Swap execution failed:', error.response?.data || error.message);
return null;
}
}
async function getSimulationUnits(connection, instructions, payer) {
const testInstructions = [
ComputeBudgetProgram.setComputeUnitLimit({ units: 1_400_000 }),
...instructions,
];
const testVersionedTxn = new VersionedTransaction(
new TransactionMessage({
instructions: testInstructions,
payerKey: payer,
recentBlockhash: (await connection.getLatestBlockhash()).blockhash,
}).compileToV0Message()
);
const simulation = await connection.simulateTransaction(testVersionedTxn, {
replaceRecentBlockhash: true,
sigVerify: false,
});
if (simulation.value.err) {
logger.error('Simulation failed with error:', JSON.stringify(simulation.value.err));
return undefined;
}
return simulation.value.unitsConsumed;
}
async function startSwap() {
let useQuickNode = true;
while (true) {
try {
const mintAddress = '43uhykFm8Y9gLvHrWs7r7w1HCKu6vikDi7j394FaSfNz'; // Hardcoded mint address
const quote = await getQuote('So11111111111111111111111111111111111111112', mintAddress, SOL_AMOUNT, SLIPPAGE_BPS);
if (!quote) {
logger.error('Failed to fetch quote. Aborting swap.');
continue;
}
const privateKeyArray = JSON.parse(WALLET_PRIVATE_KEY);
const payer = Keypair.fromSecretKey(Uint8Array.from(privateKeyArray));
const connection = new Connection(useQuickNode ? QUICKNODE_ENDPOINT : ALCHEMY_RPC_ENDPOINT, 'confirmed');
logger.info(`Swapping ${SOL_AMOUNT / 1e9} SOL for token ${mintAddress}`);
const selectedPriorityFee = DEFAULT_PRIORITY_FEE;
logger.info(`Selected priority fee: ${selectedPriorityFee}`);
// Fetch the instructions to include in the simulation
const instructions = [
// Add your necessary instructions here
];
const unitsConsumed = await getSimulationUnits(connection, instructions, payer.publicKey);
logger.info(`Units consumed in the simulation: ${unitsConsumed}`);
if (unitsConsumed) {
const computeUnitLimit = Math.ceil(unitsConsumed * 1.05); // Adding a margin of error
logger.info(`Setting compute unit limit to: ${computeUnitLimit}`);
const priorityFeeInstruction = ComputeBudgetProgram.setComputeUnitPrice({
microLamports: selectedPriorityFee,
});
const computeUnitLimitInstruction = ComputeBudgetProgram.setComputeUnitLimit({
units: computeUnitLimit,
});
instructions.push(priorityFeeInstruction);
instructions.push(computeUnitLimitInstruction);
const transaction = new Transaction().add(...instructions);
transaction.feePayer = payer.publicKey;
transaction.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
transaction.sign(payer);
const txid = await executeSwap(quote, payer, connection, selectedPriorityFee, computeUnitLimit);
if (txid) {
logger.info('Swap successful:', txid);
break;
} else {
logger.error('Swap failed.');
}
} else {
logger.error('Failed to simulate transaction. Aborting swap.');
}
} catch (error) {
logger.error('An unexpected error occurred:', error);
}
useQuickNode = !useQuickNode; // Alternate between QuickNode and Alchemy
}
}
startSwap();
the code does have a success rate but 2 out of three times it fails . I want to have near to 100 percent success rate even if it means increasing the priority fee
I get the following error
2024-07-28T19:47:41.058Z [info]: Fetching quote from: https://quote-api.jup.ag/v6/quote?inputMint=43uhykFm8Y9gLvHrWs7r7w1HCKu6vikDi7j394FaSfNz&outputMint=So11111111111111111111111111111111111111112&amount=300000000&slippageBps=1000&swapMode=ExactOut
2024-07-28T19:47:42.036Z [info]: Quote fetched successfully:
2024-07-28T19:47:42.039Z [info]: Swapping token 43uhykFm8Y9gLvHrWs7r7w1HCKu6vikDi7j394FaSfNz for 0.3 SOL
2024-07-28T19:47:42.039Z [info]: Selected priority fee: 500000
2024-07-28T19:47:43.099Z [info]: Units consumed in the simulation: 150
2024-07-28T19:47:43.099Z [info]: Setting compute unit limit to: 158
2024-07-28T19:47:43.301Z [info]: Preparing to execute swap...
2024-07-28T19:47:44.160Z [info]: Swap API response:
2024-07-28T19:47:44.631Z [info]: Simulation result:
2024-07-28T19:47:44.631Z [info]: Transaction simulation logs:
2024-07-28T19:47:44.631Z [info]: Units consumed by the transaction: 108149
2024-07-28T19:47:44.631Z [info]: Sending transaction...
2024-07-28T19:48:15.124Z [error]: Attempt 1 failed: Transaction was not confirmed in 30.00 seconds. It is unknown if it succeeded or failed. Check signature 2hP6XxMeuhY5w3Vw4MPjt3h76mgJ3rdNQByoPKSeX9pijJg3wAKBWd9ig5VxLKP1BXVR11i69viurrLjApPHpmxD using the Solana Explorer or CLI tools.
2024-07-28T19:48:15.128Z [error]: Swap execution failed:
2024-07-28T19:48:15.128Z [error]: Swap failed.
please help me figure out what is wrong
2
Answers
You can control the priority fee and computeUnitLimit in code.
Please check solana docs.
https://solana.com/developers/guides/advanced/how-to-use-priority-fees
https://solana.com/docs/core/fees
And one more thing is I think it’s not good to swap on alchemy.
I think solana mainnet api is better than alchemy in swap case.
Just my experience and opinion, not a perfect answer.
Sahil, this side from QuickNode.
I noticed you’re using a default priority fee, which is okay. However, to increase the chances of your transaction getting ahead in a slot leader’s queue (which increases your transaction’s probability of getting accepted), you must set the priority fee for a specific Solana program you’re interacting with. You can use QuickNode’s Solana Priority Fee API to get the average priority fee for a specific program account you’re interacting with for a range of the latest blocks (you can set this block range).
Here are a few resources on the Solana Priority Fee API:
Also, I saw that you use Jupiter API to get quotes. You can try QuickNode’s Metis—Jupiter V6 Swap API for getting quotes and even swap, with lower latency and higher rate limits.
Feel free to ask more questions, happy hacking 🙂