Created
February 21, 2024 21:17
-
-
Save p69/9bdabfe5032bf1c48238962ee967aff3 to your computer and use it in GitHub Desktop.
Listening to new pools on Raydium and parse data to make LiquidityPoolKeysV4
This file contains 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
// 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