文章/教程 如何监控 solana raydium v4 新流动性池创建

InkyWang · 2024年09月21日 · 最后由 matt 回复于 2024年12月08日 · 785 次阅读
本帖已被管理员设置为精华贴

更新于 2024/09/18 https://x.com/InkyWang

本教程实现了监听 solana 链上 raydium dex v4 的新流动性池创建,通过解析交易获得了配对代币名称和数量,可以按照类似方法获得池子更多信息。

如有错误,欢迎指正。

基本思路

  1. 监听 Raydium Liquidity Pool V4 的 log,过滤出包含 initialize2 方法的交易
  2. 解析交易,获得流动池信息
# 用到的库
const solanaWeb3 = require('@solana/web3.js');
const splToken = require('@solana/spl-token');
const { Metadata, deprecated } = require('@metaplex-foundation/mpl-token-metadata');
const bs58 = require('bs58');
const { struct, u8, nu64 } = require('@solana/buffer-layout');

1. 监听 Raydium Liquidity Pool V4 的 log

const connection = new solanaWeb3.Connection('{rpc}', 'confirmed'); // {rpc}处填写为自己的rpc
const raydiumV4Address = '675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8'
const raydiumV4PublicKey = new solanaWeb3.PublicKey('675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8');

connection.onLogs(
        raydiumV4PublicKey,
        ({ logs, err, signature }) => {
            if (err) return;

            if (logs && logs.some(log => log.includes("initialize2"))) {
                console.log("Signature for 'initialize2':", signature);
            }
        },
        "confirmed"
    );

这样我们就可以获取到所有刚创建池子的交易,输出应该如下:

Monitoring logs for program: 675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8
Signature for 'initialize2': 2YY7BM9NMH8fbSgNsy6YhWg6hEvnbpau4kNdmTXYcqs7UHo6NxY8MuZZH7qJke71vuNvmQmUUDA8LdkdgqYyrFP9

2. 解析交易

在 solscan 里可以看到交易2YY7BM9NMH8fbSgNsy6YhWg6hEvnbpau4kNdmTXYcqs7UHo6NxY8MuZZH7qJke71vuNvmQmUUDA8LdkdgqYyrFP9的详细信息。

找到 Instruction Details 部分,找到与 Raydium Liquidity Pool V4 交互的指令部分,如下图:

在这里,我们关注两部分:一个 Input Accounts,可以获得代币信息;另一个是 Instruction Data 可以获得数量信息

# 解析交易
const tx = await connection.getParsedTransaction(
        signature,
        {
            maxSupportedTransactionVersion: 0,
            commitment: 'confirmed'
        });
# 获取Input Accounts
const accounts = tx?.transaction.message.instructions.find(ix => ix.programId.toBase58() === raydiumV4Address).accounts;

# 获取池子地址和配对的代币地址,index分别为4,8和9
const LPIndex = 4;
const tokenAIndex = 8;
const tokenBIndex = 9;

const LPAccount = accounts[LPIndex]
const tokenAAccount = accounts[tokenAIndex];
const tokenBAccount = accounts[tokenBIndex];

# 获取两种代币的名字和精度
async function fetchTokenInfo(tokenPublicKey){

    const mintAddress = new solanaWeb3.PublicKey(tokenPublicKey);
    const mintInfo = await splToken.getMint(connection, mintAddress);
    // console.log("Decimals: " + mintInfo.decimals);

    let metadataPda = await deprecated.Metadata.getPDA(mintAddress);
    let metdadataContent =  await Metadata.fromAccountAddress(connection, metadataPda);

    return [metdadataContent.data.name, mintInfo.decimals];
}

const tokenA = await fetchTokenInfo(tokenAAccount.toBase58());
const tokenB = await fetchTokenInfo(tokenBAccount.toBase58());
# 解码指令数据
const instructionData = bs58.decode(tx?.transaction.message.instructions.find(ix => ix.programId.toBase58() === raydiumV4Address).data);
const RAYDIUM_INSTRUCTION_LAYOUT = struct([
        u8('discriminator'),
        u8('nonce'),
        nu64('opentime'),
        nu64('initPcAmount'),
        nu64('initCoinAmount')
    ]);
const decodedInstruction = RAYDIUM_INSTRUCTION_LAYOUT.decode(instructionData);

opentime 字段应该是开盘时间,有的是 0(立即开盘),有的是 linux 时间戳,还在观察

最后打印一下结果

console.log(`TX: https://solscan.io/tx/${txId}`);
const displayData = [
    { "Token": tokenA[0].replace(/\x00/g, ''), "Account Public Key": tokenAAccount.toBase58(), "Amount": decodedInstruction.initCoinAmount/Math.pow(10, tokenA[1]) },
    { "Token": tokenB[0].replace(/\x00/g, ''), "Account Public Key": tokenBAccount.toBase58(), "Amount": decodedInstruction.initPcAmount/Math.pow(10, tokenB[1]) }
];
console.log(`New LP: ${LPAccount.toBase58()}`)
if(decodedInstruction.opentime !== 0) {
    const opentime = moment.unix(decodedInstruction.opentime).tz('Asia/Shanghai').format('YYYY-MM-DD HH:mm:ss');
    console.log(`opentime: ${opentime}`)
}
console.table(displayData);

完整代码

const solanaWeb3 = require('@solana/web3.js');
const splToken = require('@solana/spl-token');
const { Metadata, deprecated } = require('@metaplex-foundation/mpl-token-metadata');
const bs58 = require('bs58');
const { struct, u8, nu64 } = require('@solana/buffer-layout');
// const moment = require('moment-timezone');

const connection = new solanaWeb3.Connection('http://45.250.254.126:1256', 'confirmed');
const raydiumV4Address = '675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8'
const raydiumV4PublicKey = new solanaWeb3.PublicKey('675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8');

async function main() {

    console.log("Monitoring logs for program:", raydiumV4PublicKey.toString());

    connection.onLogs(
        raydiumV4PublicKey,
        ({ logs, err, signature }) => {
            if (err) return;

            if (logs && logs.some(log => log.includes("initialize2"))) {
                // console.log("Signature for 'initialize2':", signature);
                fetchPoolInfo(signature, connection);
            }
        },
        "confirmed"
    );

    // fetchPoolInfo('2isUeFKrgwpfh6kGKjaQ6SrrjxAHycyxqnvaDbR8EVFLo4R2jadxGp8o4gsHkBZgru2U3DRDRHggttfmbNj37gun', connection);
}

async function fetchPoolInfo(signature, connection) {
    const tx = await connection.getParsedTransaction(
        signature,
        {
            maxSupportedTransactionVersion: 0,
            commitment: 'confirmed'
        });

    const accounts = tx?.transaction.message.instructions.find(ix => ix.programId.toBase58() === raydiumV4Address).accounts;

    if (!accounts) {
        console.log("No accounts found in the transaction.");
        return;
    }

    const LPIndex = 4;
    const tokenAIndex = 8;
    const tokenBIndex = 9;

    const LPAccount = accounts[LPIndex]
    const tokenAAccount = accounts[tokenAIndex];
    const tokenBAccount = accounts[tokenBIndex];

    const tokenA = await fetchTokenInfo(tokenAAccount.toBase58());
    const tokenB = await fetchTokenInfo(tokenBAccount.toBase58());

    const instructionData = bs58.decode(tx?.transaction.message.instructions.find(ix => ix.programId.toBase58() === raydiumV4Address).data);
    // console.log(instructionData)
    const RAYDIUM_INSTRUCTION_LAYOUT = struct([
        u8('discriminator'),
        u8('nonce'),
        nu64('opentime'),
        nu64('initPcAmount'),
        nu64('initCoinAmount')
    ]);

    const decodedInstruction = RAYDIUM_INSTRUCTION_LAYOUT.decode(instructionData);
    // console.log('Decoded instruction:', {
    //     discriminator: {
    //         type: 'u8',
    //         data: decodedInstruction.discriminator
    //     },
    //     nonce: {
    //         type: 'u8',
    //         data: decodedInstruction.nonce
    //     },
    //     opentime: {
    //         type: 'u64',
    //         data: decodedInstruction.opentime
    //     },
    //     initPcAmount: {
    //         type: 'u64',
    //         data: decodedInstruction.initPcAmount.toString()
    //     },
    //     initCoinAmount: {
    //         type: 'u64',
    //         data: decodedInstruction.initCoinAmount.toString()
    //     }
    // });

    console.log(`TX: https://solscan.io/tx/${signature}`);
    const displayData = [
        { "Token": tokenA[0].replace(/\x00/g, ''), "Account Public Key": tokenAAccount.toBase58(), "Amount": decodedInstruction.initCoinAmount/Math.pow(10, tokenA[1]) },
        { "Token": tokenB[0].replace(/\x00/g, ''), "Account Public Key": tokenBAccount.toBase58(), "Amount": decodedInstruction.initPcAmount/Math.pow(10, tokenB[1]) }
    ];
    console.log(`New LP: ${LPAccount.toBase58()}`)
    // if(decodedInstruction.opentime !== 0) {
    //     const opentime = moment.unix(decodedInstruction.opentime).tz('Asia/Shanghai').format('YYYY-MM-DD HH:mm:ss');
    //     console.log(`opentime: ${opentime}`)
    // }
    console.table(displayData);
}

async function fetchTokenInfo(tokenPublicKey){

    const mintAddress = new solanaWeb3.PublicKey(tokenPublicKey);
    const mintInfo = await splToken.getMint(connection, mintAddress);
    // console.log("Decimals: " + mintInfo.decimals);
    // console.log("Supply: " + mintInfo.supply);

    let metadataPda = await deprecated.Metadata.getPDA(mintAddress);
    let metdadataContent =  await Metadata.fromAccountAddress(connection, metadataPda);

    return [metdadataContent.data.name, mintInfo.decimals];
}

main().catch(console.error);
// https://dexscreener.com/?rankBy=pairAge&order=asc&chainIds=solana&dexIds=raydium

shooter 将本帖设为了精华贴。 10月05日 12:34
const accounts = tx?.transaction.message.instructions.find(ix => ix.programId.toBase58() === raydiumV4Address).accounts;

这里可能有问题了,直接拿不到 accounts 了

需要 登录 后方可回复, 如果你还没有账号请 注册新账号