更新于 2024/09/18 https://x.com/InkyWang
本教程实现了监听 solana 链上 raydium dex v4 的新流动性池创建,通过解析交易获得了配对代币名称和数量,可以按照类似方法获得池子更多信息。
如有错误,欢迎指正。
# 用到的库
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 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
在 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