Created
June 11, 2025 04:05
-
-
Save korrio/d867d099d78229ce7e4947eb765893f4 to your computer and use it in GitHub Desktop.
swap at meteora dex on solana with lookup table
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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