Skip to content

Instantly share code, notes, and snippets.

@korrio
Created June 11, 2025 04:25
Show Gist options
  • Save korrio/80093e297b9186f2476df1fe65bae6bc to your computer and use it in GitHub Desktop.
Save korrio/80093e297b9186f2476df1fe65bae6bc to your computer and use it in GitHub Desktop.
Transaction size optimization techniques summary
const {
Connection,
PublicKey,
Keypair,
Transaction,
TransactionMessage,
VersionedTransaction,
AddressLookupTableAccount,
ComputeBudgetProgram,
SystemProgram,
} = require('@solana/web3.js');
const {
getAssociatedTokenAddress,
createAssociatedTokenAccountInstruction,
TOKEN_PROGRAM_ID,
createCloseAccountInstruction,
} = require('@solana/spl-token');
const { AmmImpl } = require('@meteora-ag/dlmm');
const BN = require('bn.js');
class OptimizedMeteoraSwapBot {
constructor(config) {
this.connection = new Connection(config.rpcUrl, 'confirmed');
this.wallet = Keypair.fromSecretKey(new Uint8Array(config.privateKey));
this.slippageBps = config.slippageBps || 100;
this.priorityFee = config.priorityFee || 100000;
// Pre-computed frequently used addresses
this.commonAddresses = new Map();
this.initializeCommonAddresses();
}
initializeCommonAddresses() {
// Cache common addresses to avoid repeated computations
this.commonAddresses.set('TOKEN_PROGRAM', TOKEN_PROGRAM_ID);
this.commonAddresses.set('SYSTEM_PROGRAM', SystemProgram.programId);
this.commonAddresses.set('WALLET', this.wallet.publicKey);
}
// ========== TECHNIQUE 1: INSTRUCTION BATCHING ==========
async batchInstructions(instructions, maxInstructionsPerTx = 10) {
const batches = [];
for (let i = 0; i < instructions.length; i += maxInstructionsPerTx) {
batches.push(instructions.slice(i, i + maxInstructionsPerTx));
}
return batches;
}
// ========== TECHNIQUE 2: ACCOUNT PRE-CREATION ==========
async preCreateRequiredAccounts(tokenMints) {
console.log('🔧 Pre-creating required token accounts...');
const instructions = [];
const existingAccounts = new Set();
for (const mint of tokenMints) {
const ata = await getAssociatedTokenAddress(
new PublicKey(mint),
this.wallet.publicKey
);
try {
const accountInfo = await this.connection.getAccountInfo(ata);
if (accountInfo) {
existingAccounts.add(mint);
continue;
}
} catch (error) {
// Account doesn't exist
}
const createATAIx = createAssociatedTokenAccountInstruction(
this.wallet.publicKey,
ata,
this.wallet.publicKey,
new PublicKey(mint)
);
instructions.push(createATAIx);
}
if (instructions.length > 0) {
const batches = await this.batchInstructions(instructions, 8);
for (const batch of batches) {
const tx = new Transaction().add(...batch);
const signature = await this.connection.sendTransaction(tx, [this.wallet]);
await this.connection.confirmTransaction(signature, 'confirmed');
console.log(`✅ Account creation batch sent: ${signature}`);
}
}
return existingAccounts;
}
// ========== TECHNIQUE 3: INSTRUCTION COMPRESSION ==========
compressInstructions(instructions) {
console.log('🗜️ Compressing instructions...');
// Remove duplicate instructions
const uniqueInstructions = instructions.filter((instruction, index, self) => {
return self.findIndex(i =>
i.programId.equals(instruction.programId) &&
JSON.stringify(i.keys) === JSON.stringify(instruction.keys) &&
Buffer.compare(i.data, instruction.data) === 0
) === index;
});
// Merge compatible compute budget instructions
const computeBudgetInstructions = uniqueInstructions.filter(ix =>
ix.programId.equals(ComputeBudgetProgram.programId)
);
const otherInstructions = uniqueInstructions.filter(ix =>
!ix.programId.equals(ComputeBudgetProgram.programId)
);
// Keep only the highest priority fee instruction if multiple exist
const priorityFeeInstructions = computeBudgetInstructions.filter(ix =>
ix.data[0] === 3 // SetComputeUnitPrice instruction discriminator
);
const highestPriorityFee = priorityFeeInstructions.reduce((max, current) => {
const currentFee = new BN(current.data.slice(1, 9), 'le').toNumber();
const maxFee = max ? new BN(max.data.slice(1, 9), 'le').toNumber() : 0;
return currentFee > maxFee ? current : max;
}, null);
const finalInstructions = [];
if (highestPriorityFee) {
finalInstructions.push(highestPriorityFee);
}
// Add compute unit limit if not present
const hasComputeLimit = computeBudgetInstructions.some(ix => ix.data[0] === 2);
if (!hasComputeLimit) {
finalInstructions.push(
ComputeBudgetProgram.setComputeUnitLimit({ units: 400000 })
);
}
finalInstructions.push(...otherInstructions);
console.log(`📉 Reduced from ${instructions.length} to ${finalInstructions.length} instructions`);
return finalInstructions;
}
// ========== TECHNIQUE 4: MINIMAL DATA ENCODING ==========
createMinimalSwapInstruction(pool, swapParams) {
// Use minimal instruction data by only including essential parameters
const minimalParams = {
inTokenAccount: swapParams.inTokenAccount,
outTokenAccount: swapParams.outTokenAccount,
inAmount: swapParams.inAmount,
minimumOutAmount: swapParams.minimumOutAmount,
user: swapParams.user,
};
return pool.swap(minimalParams);
}
// ========== TECHNIQUE 5: ACCOUNT CLEANUP ==========
async createAccountCleanupInstructions(temporaryAccounts) {
const cleanupInstructions = [];
for (const account of temporaryAccounts) {
try {
const accountInfo = await this.connection.getAccountInfo(account);
if (accountInfo && accountInfo.data.length > 0) {
// Only cleanup if account has zero balance
const balance = await this.connection.getTokenAccountBalance(account);
if (balance.value.uiAmount === 0) {
const closeIx = createCloseAccountInstruction(
account,
this.wallet.publicKey,
this.wallet.publicKey
);
cleanupInstructions.push(closeIx);
}
}
} catch (error) {
// Account doesn't exist or error reading it
}
}
return cleanupInstructions;
}
// ========== TECHNIQUE 6: TRANSACTION SPLITTING ==========
async executeTransactionWithSplitting(instructions, lookupTables = []) {
const MAX_TX_SIZE = 1232; // Solana's transaction limit
// Estimate transaction size
let estimatedSize = this.estimateTransactionSize(instructions, lookupTables);
if (estimatedSize <= MAX_TX_SIZE) {
// Single transaction
return await this.sendSingleTransaction(instructions, lookupTables);
}
console.log(`📦 Transaction too large (${estimatedSize} bytes), splitting...`);
// Split into multiple transactions
const criticalInstructions = instructions.filter(ix =>
!ix.programId.equals(ComputeBudgetProgram.programId)
).slice(0, 1); // Keep only the main swap instruction
const setupInstructions = instructions.filter(ix =>
ix.programId.equals(ComputeBudgetProgram.programId) ||
ix.programId.equals(SystemProgram.programId)
);
// Send setup transaction first
if (setupInstructions.length > 0) {
await this.sendSingleTransaction(setupInstructions, []);
await new Promise(resolve => setTimeout(resolve, 1000)); // Wait between transactions
}
// Send main transaction
return await this.sendSingleTransaction(criticalInstructions, lookupTables);
}
estimateTransactionSize(instructions, lookupTables) {
// Rough estimation of transaction size
let size = 64; // Base transaction overhead
for (const instruction of instructions) {
size += 1; // Program ID index or address
size += 1; // Accounts length
size += instruction.keys.length * (lookupTables.length > 0 ? 1 : 32); // Account indices or addresses
size += 4; // Data length
size += instruction.data.length; // Instruction data
}
return size;
}
async sendSingleTransaction(instructions, lookupTables) {
const { blockhash } = await this.connection.getLatestBlockhash();
const messageV0 = new TransactionMessage({
payerKey: this.wallet.publicKey,
recentBlockhash: blockhash,
instructions,
}).compileToV0Message(lookupTables);
const versionedTransaction = new VersionedTransaction(messageV0);
versionedTransaction.sign([this.wallet]);
const signature = await this.connection.sendTransaction(versionedTransaction);
await this.connection.confirmTransaction(signature, 'confirmed');
return signature;
}
// ========== TECHNIQUE 7: DYNAMIC COMPUTE BUDGET ==========
async calculateOptimalComputeBudget(instructions, lookupTables) {
try {
// Create a test transaction for simulation
const { blockhash } = await this.connection.getLatestBlockhash();
const testInstructions = [
ComputeBudgetProgram.setComputeUnitLimit({ units: 1400000 }), // Max limit for simulation
...instructions
];
const messageV0 = new TransactionMessage({
payerKey: this.wallet.publicKey,
recentBlockhash: blockhash,
instructions: testInstructions,
}).compileToV0Message(lookupTables);
const versionedTransaction = new VersionedTransaction(messageV0);
versionedTransaction.sign([this.wallet]);
const simulation = await this.connection.simulateTransaction(versionedTransaction);
if (simulation.value.unitsConsumed) {
// Add 20% buffer to the consumed units
const optimalUnits = Math.ceil(simulation.value.unitsConsumed * 1.2);
console.log(`🧮 Optimal compute units: ${optimalUnits} (consumed: ${simulation.value.unitsConsumed})`);
return optimalUnits;
}
} catch (error) {
console.log('Could not calculate optimal compute budget, using default');
}
return 300000; // Default fallback
}
// ========== MAIN OPTIMIZED SWAP FUNCTION ==========
async executeOptimizedSwap(poolAddress, inputMint, outputMint, amountIn) {
try {
console.log('\n🚀 Starting optimized swap execution...');
// Step 1: Pre-create accounts if needed
await this.preCreateRequiredAccounts([inputMint, outputMint]);
// Step 2: Get pool and prepare basic instructions
const pool = await AmmImpl.create(this.connection, new PublicKey(poolAddress));
const poolState = await pool.getPoolState();
const isXtoY = poolState.tokenXMint.toString() === inputMint;
// Step 3: Get quote
const swapAmount = new BN(amountIn);
const quote = await pool.getSwapQuote(swapAmount, isXtoY, this.slippageBps);
// Step 4: Get token accounts
const inputATA = await getAssociatedTokenAddress(
new PublicKey(inputMint),
this.wallet.publicKey
);
const outputATA = await getAssociatedTokenAddress(
new PublicKey(outputMint),
this.wallet.publicKey
);
// Step 5: Create minimal swap instruction
const swapInstruction = await this.createMinimalSwapInstruction(pool, {
inTokenAccount: inputATA,
outTokenAccount: outputATA,
inAmount: swapAmount,
minimumOutAmount: quote.minOutAmount,
user: this.wallet.publicKey,
});
const instructions = [swapInstruction];
// Step 6: Create optimized lookup table
const lookupAddresses = [
new PublicKey(poolAddress),
new PublicKey(inputMint),
new PublicKey(outputMint),
inputATA,
outputATA,
poolState.tokenXVault,
poolState.tokenYVault,
TOKEN_PROGRAM_ID,
];
const lookupTable = await this.getOrCreateLookupTable(lookupAddresses);
// Step 7: Calculate optimal compute budget
const optimalComputeUnits = await this.calculateOptimalComputeBudget(instructions, [lookupTable]);
// Step 8: Add optimized compute budget instructions
const computeInstructions = [
ComputeBudgetProgram.setComputeUnitLimit({ units: optimalComputeUnits }),
ComputeBudgetProgram.setComputeUnitPrice({ microLamports: this.priorityFee }),
];
// Step 9: Compress all instructions
const allInstructions = [...computeInstructions, ...instructions];
const compressedInstructions = this.compressInstructions(allInstructions);
// Step 10: Execute with transaction splitting if needed
const signature = await this.executeTransactionWithSplitting(
compressedInstructions,
[lookupTable]
);
console.log('✅ Optimized swap completed successfully!');
console.log(`📋 Transaction: ${signature}`);
console.log(`🔗 Explorer: https://solscan.io/tx/${signature}`);
return {
signature,
quote,
success: true,
optimizations: {
instructionCount: compressedInstructions.length,
computeUnits: optimalComputeUnits,
usedLookupTable: true,
}
};
} catch (error) {
console.error('❌ Optimized swap failed:', error);
return {
error: error.message,
success: false
};
}
}
// Placeholder for lookup table method (from previous implementation)
async getOrCreateLookupTable(addresses) {
// Implementation from previous code...
// This would be the same as in the previous implementation
return null; // Placeholder
}
// ========== TECHNIQUE 8: BULK OPERATIONS ==========
async executeBulkSwaps(swapParams) {
console.log(`🔄 Executing ${swapParams.length} bulk swaps...`);
const results = [];
const BATCH_SIZE = 3; // Process 3 swaps at a time to avoid rate limits
for (let i = 0; i < swapParams.length; i += BATCH_SIZE) {
const batch = swapParams.slice(i, i + BATCH_SIZE);
const batchPromises = batch.map(params =>
this.executeOptimizedSwap(
params.poolAddress,
params.inputMint,
params.outputMint,
params.amountIn
)
);
const batchResults = await Promise.allSettled(batchPromises);
results.push(...batchResults);
// Wait between batches to avoid overwhelming the RPC
if (i + BATCH_SIZE < swapParams.length) {
await new Promise(resolve => setTimeout(resolve, 2000));
}
}
return results;
}
// ========== TECHNIQUE 9: TRANSACTION RETRY WITH BACKOFF ==========
async sendTransactionWithRetry(versionedTransaction, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
console.log(`📤 Sending transaction (attempt ${attempt}/${maxRetries})...`);
const signature = await this.connection.sendTransaction(versionedTransaction, {
skipPreflight: attempt > 1, // Skip preflight on retries
maxRetries: 0, // Handle retries manually
});
const confirmation = await this.connection.confirmTransaction(signature, 'confirmed');
if (!confirmation.value.err) {
return signature;
}
throw new Error(`Transaction failed: ${JSON.stringify(confirmation.value.err)}`);
} catch (error) {
console.log(`⚠️ Attempt ${attempt} failed: ${error.message}`);
if (attempt === maxRetries) {
throw error;
}
// Exponential backoff
const delay = Math.pow(2, attempt) * 1000;
console.log(`⏳ Waiting ${delay}ms before retry...`);
await new Promise(resolve => setTimeout(resolve, delay));
// Update recent blockhash for retry
const { blockhash } = await this.connection.getLatestBlockhash();
versionedTransaction.message.recentBlockhash = blockhash;
versionedTransaction.sign([this.wallet]);
}
}
}
}
// ========== USAGE EXAMPLES ==========
async function demonstrateOptimizations() {
const config = {
rpcUrl: process.env.RPC_URL || 'https://api.mainnet-beta.solana.com',
privateKey: JSON.parse(process.env.PRIVATE_KEY || '[]'),
slippageBps: 100,
priorityFee: 100000,
};
const optimizedBot = new OptimizedMeteoraSwapBot(config);
// Example 1: Single optimized swap
const singleSwapResult = await optimizedBot.executeOptimizedSwap(
'ARwi1S4DaiTG5DX7S4M4ZsrXqpMD1MrTmbu9ue2tpmEq', // Pool address
'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // USDC
'So11111111111111111111111111111111111111112', // SOL
'1000000' // 1 USDC
);
console.log('Single swap result:', singleSwapResult);
// Example 2: Bulk swaps
const bulkSwapParams = [
{
poolAddress: 'ARwi1S4DaiTG5DX7S4M4ZsrXqpMD1MrTmbu9ue2tpmEq',
inputMint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
outputMint: 'So11111111111111111111111111111111111111112',
amountIn: '500000'
},
{
poolAddress: 'ARwi1S4DaiTG5DX7S4M4ZsrXqpMD1MrTmbu9ue2tpmEq',
inputMint: 'So11111111111111111111111111111111111111112',
outputMint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
amountIn: '100000000' // 0.1 SOL
}
];
const bulkResults = await optimizedBot.executeBulkSwaps(bulkSwapParams);
console.log('Bulk swap results:', bulkResults);
}
module.exports = OptimizedMeteoraSwapBot;
// Export optimization techniques summary
module.exports.OptimizationTechniques = {
LOOKUP_TABLES: 'Use address lookup tables to reduce transaction size',
INSTRUCTION_BATCHING: 'Split large transactions into smaller batches',
ACCOUNT_PRE_CREATION: 'Create required accounts in advance',
INSTRUCTION_COMPRESSION: 'Remove duplicate and merge compatible instructions',
MINIMAL_DATA_ENCODING: 'Use minimal instruction data',
ACCOUNT_CLEANUP: 'Close temporary accounts to reclaim rent',
TRANSACTION_SPLITTING: 'Split oversized transactions automatically',
DYNAMIC_COMPUTE_BUDGET: 'Calculate optimal compute units through simulation',
BULK_OPERATIONS: 'Process multiple operations efficiently',
RETRY_WITH_BACKOFF: 'Implement retry logic with exponential backoff'
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment