Created
June 6, 2024 19:40
-
-
Save rubpy/dc8096e2348748044eca24dcb5ca9344 to your computer and use it in GitHub Desktop.
Finds Raydium AMM pool initialization transaction (partially deserializes pool state, calls getSignaturesForAddress, searches for a Raydium innerInstruction, etc.).
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
import * as web3 from "@solana/web3.js"; | |
import bs58 from "bs58"; | |
////////////////////////////////////////////////// | |
function findInstructionsInTransaction( | |
tx: web3.ParsedTransactionWithMeta, | |
predicate?: (instruction: web3.ParsedInstruction | web3.PartiallyDecodedInstruction, outerInstruction: (web3.ParsedInstruction | web3.PartiallyDecodedInstruction) | null) => boolean, | |
): Array<web3.ParsedInstruction | web3.PartiallyDecodedInstruction> { | |
if (!tx || !tx.meta || !tx.transaction || !tx.transaction.message || !tx.transaction.message.instructions) { | |
return []; | |
} | |
const instrs: Array<web3.ParsedInstruction | web3.PartiallyDecodedInstruction> = []; | |
for (const instr of tx.transaction.message.instructions) { | |
if (!instr) { | |
continue; | |
} | |
if (predicate && !predicate(instr, null)) { | |
continue; | |
} | |
instrs.push(instr); | |
} | |
if (tx.meta.innerInstructions) { | |
for (const innerInstr of tx.meta.innerInstructions) { | |
if (!innerInstr || !innerInstr.instructions || !innerInstr.instructions.length) { | |
continue; | |
} | |
if (typeof innerInstr.index !== "number" || innerInstr.index < 0 || tx.transaction.message.instructions.length <= innerInstr.index) { | |
continue; | |
} | |
const outerInstr = tx.transaction.message.instructions[innerInstr.index]; | |
for (const instr of innerInstr.instructions) { | |
if (predicate && !predicate(instr, outerInstr)) { | |
continue; | |
} | |
instrs.push(instr); | |
} | |
} | |
} | |
return instrs; | |
} | |
////////////////////////////////////////////////// | |
function readBytes(buf: Buffer, offset: number, length: number): Buffer { | |
const end = offset + length; | |
if (buf.length < end) throw new Error("range out of bounds"); | |
return buf.subarray(offset, end); | |
} | |
function readPublicKey(buf: Buffer, offset: number): web3.PublicKey { | |
return new web3.PublicKey(readBytes(buf, offset, web3.PUBLIC_KEY_LENGTH)); | |
} | |
////////////////////////////////////////////////// | |
const RAYDIUM_AMM_V4_PROGRAM_ID = new web3.PublicKey("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"); | |
const RAYDIUM_AMM_V4_STATE_SIZE = 0x2f0; | |
const RAYDIUM_AMM_V4_STATE_OFFSETS = { | |
LP_MINT: 0x1d0, | |
}; | |
const RAYDIUM_AMM_V4_INSTR_INIT = 0x01; | |
const RAYDIUM_AMM_V4_INSTR_INIT_SIZE = 0x1a; | |
interface RaydiumAmmV4State { | |
lpMint: web3.PublicKey | |
} | |
async function getRaydiumAmmV4State(conn: web3.Connection, poolAddress: web3.PublicKey): Promise<RaydiumAmmV4State> { | |
const response = await conn.getAccountInfo(poolAddress); | |
if (!response || !response.data || response.data.length < RAYDIUM_AMM_V4_STATE_SIZE) { | |
throw new Error("unexpected pool state"); | |
} | |
return { | |
lpMint: readPublicKey(response.data, RAYDIUM_AMM_V4_STATE_OFFSETS.LP_MINT), | |
}; | |
} | |
async function fetchRaydiumPoolInitTx(conn: web3.Connection, poolAddress: web3.PublicKey): Promise<web3.ParsedTransactionWithMeta | null> { | |
let poolState: RaydiumAmmV4State; | |
try { poolState = await getRaydiumAmmV4State(conn, poolAddress); } catch (e) { return null; } | |
if (!poolState || !poolState.lpMint) { | |
return null; | |
} | |
const signatureItems = (await conn.getSignaturesForAddress(poolState.lpMint, { limit: 8 }, "finalized") || []); | |
for (let i = signatureItems.length - 1; i >= 0; --i) { | |
const item = signatureItems[i]; | |
if (item.err) { | |
continue; | |
} | |
const tx = await conn.getParsedTransaction(item.signature, { maxSupportedTransactionVersion: 0 }); | |
if (!tx || !tx.meta || tx.meta.err || !tx.transaction) { | |
continue; | |
} | |
const ammInstructions = findInstructionsInTransaction(tx, instr => | |
(instr as any).data && instr.programId.equals(RAYDIUM_AMM_V4_PROGRAM_ID)); | |
const poolInitInstruction = ammInstructions.find(instr => { | |
let data: Uint8Array; | |
try { data = bs58.decode((instr as web3.PartiallyDecodedInstruction).data); } catch (e) { return false; }; | |
if (!data || data.length !== RAYDIUM_AMM_V4_INSTR_INIT_SIZE) { | |
return false; | |
} | |
const instrType = data.at(0); | |
if (instrType !== RAYDIUM_AMM_V4_INSTR_INIT) { | |
return false; | |
} | |
return true; | |
}); | |
if (!poolInitInstruction) { | |
continue; | |
} | |
return tx; | |
} | |
return null; | |
} | |
////////////////////////////////////////////////// | |
(async (rpcUrl: string) => { | |
const conn = new web3.Connection(rpcUrl, "confirmed"); | |
const poolAddress = new web3.PublicKey("FWZsUvr6K2YXemNiLSxWQQyQSdsy7LCg3Auuo9LDJB3f"); | |
const poolInitTx = await fetchRaydiumPoolInitTx(conn, poolAddress); | |
if (poolInitTx) { | |
console.log(`[+] found init tx for pool ${poolAddress.toBase58()}!`); | |
console.log(`tx signature: ${poolInitTx.transaction.signatures.join(",")}`); | |
console.log(` (slot: ${poolInitTx.slot})`); | |
} else { | |
console.log(`[-] could not find an 'add liquidity' tx for pool ${poolAddress.toBase58()}`); | |
} | |
})(process.env.SOL_RPC_URL || "https://mainnet.helius-rpc.com/?api-key=00000000-0000-0000-0000-000000000000"); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment