Skip to content

Instantly share code, notes, and snippets.

@p69
Created February 21, 2024 21:17
Show Gist options
  • Save p69/9bdabfe5032bf1c48238962ee967aff3 to your computer and use it in GitHub Desktop.
Save p69/9bdabfe5032bf1c48238962ee967aff3 to your computer and use it in GitHub Desktop.
Listening to new pools on Raydium and parse data to make LiquidityPoolKeysV4
// solana/web3js Connection
const connection = new Connection(config.rpcHttpURL, {
wsEndpoint: config.rpcWsURL
})
// Raydium program address
const raydium = new PublicKey('675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8')
// Subscribe to all logs for Raydium program
seenTransactions = new Set() // Some tx come multiple times
connection.onLogs(raydium, async (txLogs) => {
if (seenTransactions.has(txLogs.signature)) {
return;
}
seenTransactions.add(txLogs.signature);
if (!findLogEntry('init_pc_amount', txLogs.logs)) {
return; // If "init_pc_amount" is not in log entries then it's not LP initialization transaction
}
const results = await fetchPoolKeysForLPInitTransactionHash(txLogs.signature)
})
async function handleNewPoolMintTx(txId: string) {
}
async function fetchPoolKeysForLPInitTransactionHash(txSignature: string): Promise<{ poolKeys: PoolKeys, mintTransaction: ParsedTransactionWithMeta }> {
const tx = await connection.getParsedTransaction(txSignature, { maxSupportedTransactionVersion: 0 })
const tx = await retryGetParsedTransaction(connection, txSignature, 5)
if (!tx) {
throw new Error('Failed to fetch transaction with signature ' + txSignature);
}
const poolInfo = parsePoolInfoFromLpTransaction(tx)
const marketInfo = await fetchMarketInfo(connection, poolInfo.marketId)
const keys = {
id: poolInfo.id.toString(),
baseMint: poolInfo.baseMint.toString(),
quoteMint: poolInfo.quoteMint.toString(),
lpMint: poolInfo.lpMint.toString(),
baseDecimals: poolInfo.baseDecimals,
quoteDecimals: poolInfo.quoteDecimals,
lpDecimals: poolInfo.lpDecimals,
version: 4,
programId: poolInfo.programId.toString(),
authority: poolInfo.authority.toString(),
openOrders: poolInfo.openOrders.toString(),
targetOrders: poolInfo.targetOrders.toString(),
baseVault: poolInfo.baseVault.toString(),
quoteVault: poolInfo.quoteVault.toString(),
withdrawQueue: poolInfo.withdrawQueue.toString(),
lpVault: poolInfo.lpVault.toString(),
marketVersion: 3,
marketProgramId: poolInfo.marketProgramId.toString(),
marketId: poolInfo.marketId.toString(),
marketAuthority: Market.getAssociatedAuthority({ programId: poolInfo.marketProgramId, marketId: poolInfo.marketId }).publicKey.toString(),
marketBaseVault: marketInfo.baseVault.toString(),
marketQuoteVault: marketInfo.quoteVault.toString(),
marketBids: marketInfo.bids.toString(),
marketAsks: marketInfo.asks.toString(),
marketEventQueue: marketInfo.eventQueue.toString(),
}
return { mintTransaction: tx, poolKeys: keys }
}
async function fetchMarketInfo(connection: Connection, marketId: PublicKey) {
const marketAccountInfo = await connection.getAccountInfo(marketId);
if (!marketAccountInfo) {
throw new Error('Failed to fetch market info for market id ' + marketId.toBase58());
}
return MARKET_STATE_LAYOUT_V3.decode(marketAccountInfo.data);
}
function parsePoolInfoFromLpTransaction(txData: ParsedTransactionWithMeta) {
const initInstruction = findInstructionByProgramId(txData.transaction.message.instructions, new PublicKey(RAYDIUM_POOL_V4_PROGRAM_ID)) as PartiallyDecodedInstruction | null;
if (!initInstruction) {
throw new Error('Failed to find lp init instruction in lp init tx');
}
const baseMint = initInstruction.accounts[8];
const baseVault = initInstruction.accounts[10];
const quoteMint = initInstruction.accounts[9];
const quoteVault = initInstruction.accounts[11];
const lpMint = initInstruction.accounts[7];
const baseAndQuoteSwapped = baseMint.toBase58() === SOL_MINT;
const lpMintInitInstruction = findInitializeMintInInnerInstructionsByMintAddress(txData.meta?.innerInstructions ?? [], lpMint);
if (!lpMintInitInstruction) {
throw new Error('Failed to find lp mint init instruction in lp init tx');
}
const lpMintInstruction = findMintToInInnerInstructionsByMintAddress(txData.meta?.innerInstructions ?? [], lpMint);
if (!lpMintInstruction) {
throw new Error('Failed to find lp mint to instruction in lp init tx');
}
const baseTransferInstruction = findTransferInstructionInInnerInstructionsByDestination(txData.meta?.innerInstructions ?? [], baseVault, TOKEN_PROGRAM_ID);
if (!baseTransferInstruction) {
throw new Error('Failed to find base transfer instruction in lp init tx');
}
const quoteTransferInstruction = findTransferInstructionInInnerInstructionsByDestination(txData.meta?.innerInstructions ?? [], quoteVault, TOKEN_PROGRAM_ID);
if (!quoteTransferInstruction) {
throw new Error('Failed to find quote transfer instruction in lp init tx');
}
const lpDecimals = lpMintInitInstruction.parsed.info.decimals;
const lpInitializationLogEntryInfo = extractLPInitializationLogEntryInfoFromLogEntry(findLogEntry('init_pc_amount', txData.meta?.logMessages ?? []) ?? '');
const basePreBalance = (txData.meta?.preTokenBalances ?? []).find(balance => balance.mint === baseMint.toBase58());
if (!basePreBalance) {
throw new Error('Failed to find base tokens preTokenBalance entry to parse the base tokens decimals');
}
const baseDecimals = basePreBalance.uiTokenAmount.decimals;
return {
id: initInstruction.accounts[4],
baseMint,
quoteMint,
lpMint,
baseDecimals: baseAndQuoteSwapped ? SOL_DECIMALS : baseDecimals,
quoteDecimals: baseAndQuoteSwapped ? baseDecimals : SOL_DECIMALS,
lpDecimals,
version: 4,
programId: new PublicKey(RAYDIUM_POOL_V4_PROGRAM_ID),
authority: initInstruction.accounts[5],
openOrders: initInstruction.accounts[6],
targetOrders: initInstruction.accounts[13],
baseVault,
quoteVault,
withdrawQueue: new PublicKey("11111111111111111111111111111111"),
lpVault: new PublicKey(lpMintInstruction.parsed.info.account),
marketVersion: 3,
marketProgramId: initInstruction.accounts[15],
marketId: initInstruction.accounts[16],
baseReserve: parseInt(baseTransferInstruction.parsed.info.amount),
quoteReserve: parseInt(quoteTransferInstruction.parsed.info.amount),
lpReserve: parseInt(lpMintInstruction.parsed.info.amount),
openTime: lpInitializationLogEntryInfo.open_time,
}
}
function findInstructionByProgramId(instructions: Array<ParsedInstruction | PartiallyDecodedInstruction>, programId: PublicKey): ParsedInstruction | PartiallyDecodedInstruction | null {
for (let i = 0; i < instructions.length; i++) {
if (instructions[i].programId.equals(programId)) {
return instructions[i];
}
}
return null;
}
function findInitializeMintInInnerInstructionsByMintAddress(innerInstructions: Array<ParsedInnerInstruction>, mintAddress: PublicKey): ParsedInstruction | null {
for (let i = 0; i < innerInstructions.length; i++) {
for (let y = 0; y < innerInstructions[i].instructions.length; y++) {
const instruction = innerInstructions[i].instructions[y] as ParsedInstruction;
if (!instruction.parsed) { continue };
if (instruction.parsed.type === 'initializeMint' && instruction.parsed.info.mint === mintAddress.toBase58()) {
return instruction;
}
}
}
return null;
}
function findMintToInInnerInstructionsByMintAddress(innerInstructions: Array<ParsedInnerInstruction>, mintAddress: PublicKey): ParsedInstruction | null {
for (let i = 0; i < innerInstructions.length; i++) {
for (let y = 0; y < innerInstructions[i].instructions.length; y++) {
const instruction = innerInstructions[i].instructions[y] as ParsedInstruction;
if (!instruction.parsed) { continue };
if (instruction.parsed.type === 'mintTo' && instruction.parsed.info.mint === mintAddress.toBase58()) {
return instruction;
}
}
}
return null;
}
function findTransferInstructionInInnerInstructionsByDestination(innerInstructions: Array<ParsedInnerInstruction>, destinationAccount: PublicKey, programId?: PublicKey): ParsedInstruction | null {
for (let i = 0; i < innerInstructions.length; i++) {
for (let y = 0; y < innerInstructions[i].instructions.length; y++) {
const instruction = innerInstructions[i].instructions[y] as ParsedInstruction;
if (!instruction.parsed) { continue };
if (instruction.parsed.type === 'transfer' && instruction.parsed.info.destination === destinationAccount.toBase58() && (!programId || instruction.programId.equals(programId))) {
return instruction;
}
}
}
return null;
}
function extractLPInitializationLogEntryInfoFromLogEntry(lpLogEntry: string): { nonce: number, open_time: number, init_pc_amount: number, init_coin_amount: number } {
const lpInitializationLogEntryInfoStart = lpLogEntry.indexOf('{');
return JSON.parse(fixRelaxedJsonInLpLogEntry(lpLogEntry.substring(lpInitializationLogEntryInfoStart)));
}
function fixRelaxedJsonInLpLogEntry(relaxedJson: string): string {
return relaxedJson.replace(/([{,])\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*:/g, "$1\"$2\":");
}
function findInstructionByProgramId(instructions: Array<ParsedInstruction | PartiallyDecodedInstruction>, programId: PublicKey): ParsedInstruction | PartiallyDecodedInstruction | null {
for (let i = 0; i < instructions.length; i++) {
if (instructions[i].programId.equals(programId)) {
return instructions[i];
}
}
return null;
}
interface PoolKeys {
id: string,
baseMint: string,
quoteMint: string,
lpMint: string,
baseDecimals: number,
quoteDecimals: number,
lpDecimals: number,
version: number,
programId: string,
authority: string,
openOrders: string,
targetOrders: string,
baseVault: string,
quoteVault: string,
withdrawQueue: string,
lpVault: string,
marketVersion: number,
marketProgramId: string,
marketId: string,
marketAuthority: string,
marketBaseVault: string,
marketQuoteVault: string,
marketBids: string,
marketAsks: string,
marketEventQueue: string,
}
function convertStringKeysToDataKeys(poolInfo: PoolKeys): LiquidityPoolKeysV4 {
return {
id: new PublicKey(poolInfo.id),
baseMint: new PublicKey(poolInfo.baseMint),
quoteMint: new PublicKey(poolInfo.quoteMint),
lpMint: new PublicKey(poolInfo.lpMint),
baseDecimals: poolInfo.baseDecimals,
quoteDecimals: poolInfo.quoteDecimals,
lpDecimals: poolInfo.lpDecimals,
version: 4,
programId: new PublicKey(poolInfo.programId),
authority: new PublicKey(poolInfo.authority),
openOrders: new PublicKey(poolInfo.openOrders),
targetOrders: new PublicKey(poolInfo.targetOrders),
baseVault: new PublicKey(poolInfo.baseVault),
quoteVault: new PublicKey(poolInfo.quoteVault),
withdrawQueue: new PublicKey(poolInfo.withdrawQueue),
lpVault: new PublicKey(poolInfo.lpVault),
marketVersion: 3,
marketProgramId: new PublicKey(poolInfo.marketProgramId),
marketId: new PublicKey(poolInfo.marketId),
marketAuthority: new PublicKey(poolInfo.marketAuthority),
marketBaseVault: new PublicKey(poolInfo.baseVault),
marketQuoteVault: new PublicKey(poolInfo.quoteVault),
marketBids: new PublicKey(poolInfo.marketBids),
marketAsks: new PublicKey(poolInfo.marketAsks),
marketEventQueue: new PublicKey(poolInfo.marketEventQueue),
} as LiquidityPoolKeysV4;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment