import {
getOrCreateAssociatedTokenAccount,
createTransferInstruction,
} from "@solana/spl-token";
import {
Connection,
PublicKey,
TransactionMessage,
VersionedTransaction,
Keypair,
ParsedAccountData,
ComputeBudgetProgram
} from "@solana/web3.js";
import bs58 from "bs58";
import "dotenv/config";
// Fetches the number of decimals for a given token to accurately handle token amounts.
async function getNumberDecimals(
mintAddress: PublicKey,
connection: Connection
): Promise<number> {
const info = await connection.getParsedAccountInfo(mintAddress);
const decimals = (info.value?.data as ParsedAccountData).parsed.info
.decimals as number;
console.log(`Token Decimals: ${decimals}`);
return decimals;
}
// Initializes a Keypair from the secret key stored in environment variables. Essential for signing transactions.
function initializeKeypair(): Keypair {
const privateKey = new Uint8Array(bs58.decode(process.env.PRIVATE_KEY!));
const keypair = Keypair.fromSecretKey(privateKey);
console.log(
`Initialized Keypair: Public Key - ${keypair.publicKey.toString()}`
);
return keypair;
}
// Sets up the connection to the Solana cluster, utilizing environment variables for configuration.
function initializeConnection(): Connection {
const rpcUrl = process.env.SOLANA_RPC!;
const connection = new Connection(rpcUrl, {
commitment: "confirmed",
wsEndpoint: process.env.SOLANA_WSS,
});
// Redacting part of the RPC URL for security/log clarity
console.log(`Initialized Connection to Solana RPC: ${rpcUrl.slice(0, -32)}`);
return connection;
}
async function main() {
const retryCount = Number(process.env.MAX_RETRY_FUNCTION);
// Default retry count set to 5
for (let attempt = 1; attempt <= retryCount; attempt++) {
try {
console.log(`Attempt ${attempt}: Starting Token Transfer Process`);
const connection = initializeConnection();
const fromKeypair = initializeKeypair();
const destinationWallet = new PublicKey(
"CzNGm14nMopjGYyycMbWqEF2e1aEHcJLKk2CHw9BiZwC"
);
const mintAddress = new PublicKey(
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
);
// Config priority fee and amount to transfer
const PRIORITY_RATE = 12345; // MICRO_LAMPORTS
const transferAmount = 0.01; // This will need to be adjusted based on the token's decimals
// Instruction to set the compute unit price for priority fee
const PRIORITY_FEE_INSTRUCTIONS = ComputeBudgetProgram.setComputeUnitPrice({microLamports: PRIORITY_RATE});
console.log("----------------------------------------");
const decimals = await getNumberDecimals(mintAddress, connection);
let sourceAccount = await getOrCreateAssociatedTokenAccount(
connection,
fromKeypair,
mintAddress,
fromKeypair.publicKey
);
console.log(`Source Account: ${sourceAccount.address.toString()}`);
let destinationAccount = await getOrCreateAssociatedTokenAccount(
connection,
fromKeypair,
mintAddress,
destinationWallet
);
console.log(
`Destination Account: ${destinationAccount.address.toString()}`
);
console.log("----------------------------------------");
const transferAmountInDecimals = transferAmount * Math.pow(10, decimals);
const transferInstruction = createTransferInstruction(
sourceAccount.address,
destinationAccount.address,
fromKeypair.publicKey,
transferAmountInDecimals
);
let latestBlockhash = await connection.getLatestBlockhash("confirmed");
const messageV0 = new TransactionMessage({
payerKey: fromKeypair.publicKey,
recentBlockhash: latestBlockhash.blockhash,
instructions: [PRIORITY_FEE_INSTRUCTIONS, transferInstruction],
}).compileToV0Message();
const versionedTransaction = new VersionedTransaction(messageV0);
versionedTransaction.sign([fromKeypair]);
const txid = await connection.sendTransaction(versionedTransaction, {
skipPreflight: false,
maxRetries: Number(process.env.MAX_RETRY_WEB3JS),
preflightCommitment: "confirmed",
});
console.log(`Transaction Submitted: ${txid}`);
const confirmation = await connection.confirmTransaction(
{
signature: txid,
blockhash: latestBlockhash.blockhash,
lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
},
"confirmed"
);
if (confirmation.value.err) {
throw new Error("🚨Transaction not confirmed.");
}
console.log(
`Transaction Successfully Confirmed! 🎉 View on SolScan: https://solscan.io/tx/${txid}`
);
return; // Success, exit the function
} catch (error) {
console.error(`Attempt ${attempt} failed with error: ${error}`);
if (attempt === retryCount) {
// Last attempt failed, throw the error
throw new Error(`Transaction failed after ${retryCount} attempts.`);
}
// Wait for 2 seconds before retrying
await new Promise((resolve) => setTimeout(resolve, 2000));
}
}
}
main()