Skip to content

Instantly share code, notes, and snippets.

@rubpy
Created June 6, 2024 19:40
Show Gist options
  • Save rubpy/dc8096e2348748044eca24dcb5ca9344 to your computer and use it in GitHub Desktop.
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.).
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