Skip to content

Instantly share code, notes, and snippets.

@korrio
Created June 11, 2025 04:05
Show Gist options
  • Save korrio/d867d099d78229ce7e4947eb765893f4 to your computer and use it in GitHub Desktop.
Save korrio/d867d099d78229ce7e4947eb765893f4 to your computer and use it in GitHub Desktop.
swap at meteora dex on solana with lookup table
const {
Connection,
PublicKey,
Keypair,
Transaction,
TransactionMessage,
VersionedTransaction,
AddressLookupTableAccount,
ComputeBudgetProgram,
} = require('@solana/web3.js');
const {
getAssociatedTokenAddress,
createAssociatedTokenAccountInstruction,
TOKEN_PROGRAM_ID,
} = require('@solana/spl-token');
const { AmmImpl, MAINNET_POOL } = require('@meteora-ag/dlmm');
const BN = require('bn.js');
class MeteoraSwapBot {
constructor(config) {
this.connection = new Connection(config.rpcUrl, 'confirmed');
this.wallet = Keypair.fromSecretKey(new Uint8Array(config.privateKey));
this.slippageBps = config.slippageBps || 100; // 1% default slippage
this.priorityFee = config.priorityFee || 100000; // 0.0001 SOL priority fee
console.log(`Wallet: ${this.wallet.publicKey.toString()}`);
}
async getOrCreateLookupTable(addresses) {
try {
// Check if we have a stored lookup table address
const lookupTableAddress = await this.findExistingLookupTable(addresses);
if (lookupTableAddress) {
console.log(`Using existing lookup table: ${lookupTableAddress.toString()}`);
const lookupTableAccount = await this.connection.getAddressLookupTable(lookupTableAddress);
return lookupTableAccount.value;
}
// Create new lookup table
console.log('Creating new lookup table...');
return await this.createLookupTable(addresses);
} catch (error) {
console.error('Error with lookup table:', error);
throw error;
}
}
async findExistingLookupTable(addresses) {
// In a real implementation, you might store lookup table addresses
// For now, we'll create a new one each time
return null;
}
async createLookupTable(addresses) {
const slot = await this.connection.getSlot();
// Create lookup table instruction
const [lookupTableInstruction, lookupTableAddress] =
AddressLookupTableProgram.createLookupTable({
authority: this.wallet.publicKey,
payer: this.wallet.publicKey,
recentSlot: slot,
});
// Extend lookup table with addresses
const extendInstruction = AddressLookupTableProgram.extendLookupTable({
payer: this.wallet.publicKey,
authority: this.wallet.publicKey,
lookupTable: lookupTableAddress,
addresses: addresses,
});
// Create and send transaction
const transaction = new Transaction()
.add(lookupTableInstruction)
.add(extendInstruction);
const signature = await this.connection.sendTransaction(transaction, [this.wallet]);
await this.connection.confirmTransaction(signature, 'confirmed');
console.log(`Lookup table created: ${lookupTableAddress.toString()}`);
console.log(`Transaction: ${signature}`);
// Wait a bit for the lookup table to be available
await new Promise(resolve => setTimeout(resolve, 2000));
const lookupTableAccount = await this.connection.getAddressLookupTable(lookupTableAddress);
return lookupTableAccount.value;
}
async getPoolInfo(poolAddress) {
try {
const pool = await AmmImpl.create(this.connection, new PublicKey(poolAddress));
const poolState = await pool.getPoolState();
return {
pool,
poolState,
tokenXMint: poolState.tokenXMint,
tokenYMint: poolState.tokenYMint,
tokenXReserve: poolState.tokenXReserve,
tokenYReserve: poolState.tokenYReserve,
};
} catch (error) {
console.error('Error getting pool info:', error);
throw error;
}
}
async getTokenAccounts(tokenMints) {
const accounts = {};
for (const mint of tokenMints) {
const ata = await getAssociatedTokenAddress(
new PublicKey(mint),
this.wallet.publicKey
);
accounts[mint] = ata;
}
return accounts;
}
async createATAIfNeeded(tokenMint, instructions) {
const ata = await getAssociatedTokenAddress(
new PublicKey(tokenMint),
this.wallet.publicKey
);
try {
await this.connection.getAccountInfo(ata);
} catch (error) {
// ATA doesn't exist, create it
const createATAIx = createAssociatedTokenAccountInstruction(
this.wallet.publicKey,
ata,
this.wallet.publicKey,
new PublicKey(tokenMint)
);
instructions.push(createATAIx);
}
return ata;
}
async executeSwap(poolAddress, inputMint, outputMint, amountIn, isExactIn = true) {
try {
console.log(`\n=== Executing Swap ===`);
console.log(`Pool: ${poolAddress}`);
console.log(`Input Token: ${inputMint}`);
console.log(`Output Token: ${outputMint}`);
console.log(`Amount: ${amountIn}`);
// Get pool information
const { pool, poolState } = await this.getPoolInfo(poolAddress);
// Determine swap direction
const isXtoY = poolState.tokenXMint.toString() === inputMint;
console.log(`Swap direction: ${isXtoY ? 'X to Y' : 'Y to X'}`);
// Get quote
const swapAmount = new BN(amountIn);
const quote = await pool.getSwapQuote(swapAmount, isXtoY, this.slippageBps);
console.log(`Quote - Input: ${quote.inAmount.toString()}, Output: ${quote.outAmount.toString()}`);
console.log(`Price impact: ${quote.priceImpact}%`);
// Prepare instructions array
const instructions = [];
// Add priority fee
const priorityFeeIx = ComputeBudgetProgram.setComputeUnitPrice({
microLamports: this.priorityFee,
});
instructions.push(priorityFeeIx);
// Create ATAs if needed
const inputATA = await this.createATAIfNeeded(inputMint, instructions);
const outputATA = await this.createATAIfNeeded(outputMint, instructions);
// Get swap instruction
const swapInstruction = await pool.swap({
inTokenAccount: inputATA,
outTokenAccount: outputATA,
inAmount: swapAmount,
minimumOutAmount: quote.minOutAmount,
user: this.wallet.publicKey,
});
instructions.push(swapInstruction);
// Collect all addresses for lookup table
const lookupAddresses = [
new PublicKey(poolAddress),
new PublicKey(inputMint),
new PublicKey(outputMint),
inputATA,
outputATA,
TOKEN_PROGRAM_ID,
pool.program.programId,
// Add more relevant addresses from the pool state
poolState.tokenXVault,
poolState.tokenYVault,
poolState.oracle,
].filter((addr, index, self) =>
self.findIndex(a => a.toString() === addr.toString()) === index
);
// Get or create lookup table
const lookupTable = await this.getOrCreateLookupTable(lookupAddresses);
// Get recent blockhash
const { blockhash } = await this.connection.getLatestBlockhash();
// Create versioned transaction with lookup table
const messageV0 = new TransactionMessage({
payerKey: this.wallet.publicKey,
recentBlockhash: blockhash,
instructions,
}).compileToV0Message([lookupTable]);
const versionedTransaction = new VersionedTransaction(messageV0);
versionedTransaction.sign([this.wallet]);
// Simulate transaction first
console.log('\nSimulating transaction...');
const simulation = await this.connection.simulateTransaction(versionedTransaction);
if (simulation.value.err) {
throw new Error(`Simulation failed: ${JSON.stringify(simulation.value.err)}`);
}
console.log(`Simulation successful. Compute units used: ${simulation.value.unitsConsumed}`);
// Send transaction
console.log('\nSending transaction...');
const signature = await this.connection.sendTransaction(versionedTransaction);
console.log(`Transaction sent: ${signature}`);
console.log(`Explorer: https://solscan.io/tx/${signature}`);
// Confirm transaction
const confirmation = await this.connection.confirmTransaction(signature, 'confirmed');
if (confirmation.value.err) {
throw new Error(`Transaction failed: ${JSON.stringify(confirmation.value.err)}`);
}
console.log('✅ Swap completed successfully!');
return {
signature,
quote,
success: true
};
} catch (error) {
console.error('❌ Swap failed:', error);
return {
error: error.message,
success: false
};
}
}
async getBalance(tokenMint) {
try {
if (tokenMint === 'So11111111111111111111111111111111111111112') {
// SOL balance
const balance = await this.connection.getBalance(this.wallet.publicKey);
return balance / 1e9; // Convert lamports to SOL
} else {
// Token balance
const ata = await getAssociatedTokenAddress(
new PublicKey(tokenMint),
this.wallet.publicKey
);
const balance = await this.connection.getTokenAccountBalance(ata);
return parseFloat(balance.value.uiAmount || 0);
}
} catch (error) {
console.log(`No balance found for ${tokenMint}`);
return 0;
}
}
async monitorPrices(poolAddress, intervalMs = 5000) {
console.log(`\n=== Starting Price Monitor ===`);
console.log(`Pool: ${poolAddress}`);
console.log(`Interval: ${intervalMs}ms`);
setInterval(async () => {
try {
const { poolState } = await this.getPoolInfo(poolAddress);
const price = poolState.tokenYReserve.toNumber() / poolState.tokenXReserve.toNumber();
console.log(`Price: ${price.toFixed(6)} | Reserve X: ${poolState.tokenXReserve.toString()} | Reserve Y: ${poolState.tokenYReserve.toString()}`);
} catch (error) {
console.error('Price monitoring error:', error.message);
}
}, intervalMs);
}
}
// Usage example
async function main() {
const config = {
rpcUrl: process.env.RPC_URL || 'https://api.mainnet-beta.solana.com',
privateKey: JSON.parse(process.env.PRIVATE_KEY || '[]'), // Your wallet private key as array
slippageBps: 100, // 1% slippage
priorityFee: 100000, // Priority fee in micro-lamports
};
const bot = new MeteoraSwapBot(config);
// Example: Swap USDC to SOL
const poolAddress = 'ARwi1S4DaiTG5DX7S4M4ZsrXqpMD1MrTmbu9ue2tpmEq'; // Example pool address
const usdcMint = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';
const solMint = 'So11111111111111111111111111111111111111112';
// Check balances
console.log('\n=== Current Balances ===');
const usdcBalance = await bot.getBalance(usdcMint);
const solBalance = await bot.getBalance(solMint);
console.log(`USDC: ${usdcBalance}`);
console.log(`SOL: ${solBalance}`);
// Execute swap (1 USDC to SOL)
const swapAmount = '1000000'; // 1 USDC (6 decimals)
const result = await bot.executeSwap(
poolAddress,
usdcMint,
solMint,
swapAmount
);
if (result.success) {
console.log('\n=== Updated Balances ===');
const newUsdcBalance = await bot.getBalance(usdcMint);
const newSolBalance = await bot.getBalance(solMint);
console.log(`USDC: ${newUsdcBalance}`);
console.log(`SOL: ${newSolBalance}`);
}
// Optional: Start price monitoring
// await bot.monitorPrices(poolAddress, 10000);
}
// Run the bot
if (require.main === module) {
main().catch(console.error);
}
module.exports = MeteoraSwapBot;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment