diff --git a/10-example-subRaydiumNewPool/README.md b/10-example-subRaydiumNewPool/README.md new file mode 100644 index 0000000..516c11f --- /dev/null +++ b/10-example-subRaydiumNewPool/README.md @@ -0,0 +1,36 @@ +subscribes Raydium new pool + +This example subscribes to raydiumLiquidityPoolv4 new pool based on transactions filter conditions. + +Run using “npm start”, the output should be as follows:: + +```bash +{ + id: '7RvCz29ADuNRVhfKaz3bEqCGQU883fBz73zDfPjAKxJp', + baseMint: '558ELUVzEwiyaP81cNCbdr2xCM4GeYqwKLmPxZr93FF', + quoteMint: 'So11111111111111111111111111111111111111112', + lpMint: 'ApyQukcZxFeEGFHrAjogvUC7VGAzhFHVaqaaCVLv8smK', + baseDecimals: 9, + quoteDecimals: 9, + lpDecimals: 9, + version: 4, + programId: '675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8', + authority: '5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1', + openOrders: '81WGTTjWW7m5JzCVtT89bGv73b3c2M4arg7PFqSEJinp', + targetOrders: '9KBQPPNmSSRRVfVppuGTHCpnH3yJ9wqmuYtU76z4q4ov', + baseVault: 'EJn65uzLh5hJ3UNZ7itrCc8CG1kgPmmf1FBot4hEbHBQ', + quoteVault: 'CfCEDB6Tr6tHZ28wfyYf4UmdQ45SFWZLvuN6t9RwRGtE', + withdrawQueue: '11111111111111111111111111111111', + lpVault: '11111111111111111111111111111111', + marketVersion: 3, + marketProgramId: 'srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX', + marketId: 'bW3feQyNjjBvK1GnYg5MwKskZVMGfv6qsg2m5q7TmeD', + marketAuthority: 'FMHeesi6g72GA6mju247RTwQBmyTgjs7SCoW4Cavwvnb', + marketBaseVault: '4ZVNYCmWF7LmxNRPibfdZ8ZcX6Bhn5Q6s9eZ36J9VMFY', + marketQuoteVault: 'HjwE6ibPEUSEqJAeduYqzzXH8XsT4Cwmz5wL45ywFFob', + marketBids: 'FJkPv9yJGdiDb65EEY64ZH2vaeKwqvy9TUA2tUbooNVr', + marketAsks: '8pfZ6wSP5PbkFhFfU5DMcYKPBtAEFmEjQ7c6zCVVKW9C', + marketEventQueue: 'H4TCk4yeBWPjuQxCTwNi7JB2mRuBZE4qXFDtZrTMWQhQ', + lookupTableAccount: '11111111111111111111111111111111' + } +``` diff --git a/10-example-subRaydiumNewPool/index.ts b/10-example-subRaydiumNewPool/index.ts new file mode 100644 index 0000000..73fac78 --- /dev/null +++ b/10-example-subRaydiumNewPool/index.ts @@ -0,0 +1,175 @@ +import Client, { CommitmentLevel, SubscribeRequest } from "@triton-one/yellowstone-grpc"; +import { PublicKey, Connection } from "@solana/web3.js"; +import { ApiPoolInfoV4, Market, MARKET_STATE_LAYOUT_V3, SPL_MINT_LAYOUT } from "@raydium-io/raydium-sdk"; +import "dotenv/config"; +import bs58 from "bs58"; + +const RAYDIUM_PROGRAM_ID = '675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8'; +const RAYDIUM_POOL_FEE_ID = '7YttLkHDoNj9wyDur5pM1ejNaAvT9X4eqaYcHQqtj2G5'; +const connection = new Connection('https://api.mainnet-beta.solana.com', 'confirmed'); + +async function main() { + + // 创建订阅客户端 + // const client = new Client( + // 如遇到TypeError: Client is not a constructor错误 + // 请使用以下方式创建 + // 见 https://github.com/rpcpool/yellowstone-grpc/issues/428 + // @ts-ignore + const client = new Client.default( + "https://test-grpc.chainbuff.com", + undefined, + { + "grpc.max_receive_message_length": 16 * 1024 * 1024, // 16MB + } + ); + + // 创建订阅数据流 + const stream = await client.subscribe(); + + // 创建订阅请求 + const request: SubscribeRequest = { + slots: {}, + accounts: {}, + transactions: { + transactionsSubKey: { + accountInclude: [RAYDIUM_POOL_FEE_ID], + accountExclude: [], + accountRequired: [] + } + }, + transactionsStatus: {}, + blocks: {}, + blocksMeta: {}, + accountsDataSlice: [], + entry: {}, + commitment: CommitmentLevel.CONFIRMED + }; + + // 发送订阅请求 + await new Promise((resolve, reject) => { + stream.write(request, (err) => { + if (err === null || err === undefined) { + resolve(); + } else { + reject(err); + } + }); + }).catch((reason) => { + console.error(reason); + throw reason; + }); + + // 获取订阅数据 + stream.on("data", async (data) => { + if (data?.transaction) { + if (!data.filters.includes('transactionsSubKey')) return undefined + + const info = data.transaction + if (info.transaction.meta.err !== undefined) return undefined + + const formatData: { + updateTime: number, slot: number, txid: string, poolInfos: ApiPoolInfoV4[] + } = { + updateTime: new Date().getTime(), + slot: info.slot, + txid: bs58.encode(info.transaction.signature), + poolInfos: [] + } + + const accounts = info.transaction.transaction.message.accountKeys.map((i: Buffer) => bs58.encode(i)) + for (const item of [...info.transaction.transaction.message.instructions, ...info.transaction.meta.innerInstructions.map((i: any) => i.instructions).flat()]) { + if (accounts[item.programIdIndex] !== RAYDIUM_PROGRAM_ID) continue + + //if ([...(item.data as Buffer).values()][0] != 1) continue + if (Array.from(item.data as Buffer)[0] !== 1) continue; + + //const keyIndex = [...(item.accounts as Buffer).values()] + const keyIndex = Buffer.from(item.accounts as Buffer); + + const startTime = new Date().getTime() + console.info(new Date().toJSON(), 'new pool Id: ', accounts[keyIndex[4]]); + + const [baseMintAccount, quoteMintAccount, marketAccount] = await connection.getMultipleAccountsInfo([ + new PublicKey(accounts[keyIndex[8]]), + new PublicKey(accounts[keyIndex[9]]), + new PublicKey(accounts[keyIndex[16]]), + ]) + + if (baseMintAccount === null || quoteMintAccount === null || marketAccount === null) throw Error('get account info error') + + const baseMintInfo = SPL_MINT_LAYOUT.decode(baseMintAccount.data) + const quoteMintInfo = SPL_MINT_LAYOUT.decode(quoteMintAccount.data) + const marketInfo = MARKET_STATE_LAYOUT_V3.decode(marketAccount.data) + + formatData.poolInfos.push({ + id: accounts[keyIndex[4]], + baseMint: accounts[keyIndex[8]], + quoteMint: accounts[keyIndex[9]], + lpMint: accounts[keyIndex[7]], + baseDecimals: baseMintInfo.decimals, + quoteDecimals: quoteMintInfo.decimals, + lpDecimals: baseMintInfo.decimals, + version: 4, + programId: RAYDIUM_PROGRAM_ID, + authority: accounts[keyIndex[5]], + openOrders: accounts[keyIndex[6]], + targetOrders: accounts[keyIndex[12]], + baseVault: accounts[keyIndex[10]], + quoteVault: accounts[keyIndex[11]], + withdrawQueue: PublicKey.default.toString(), + lpVault: PublicKey.default.toString(), + marketVersion: 3, + marketProgramId: marketAccount.owner.toString(), + marketId: accounts[keyIndex[16]], + marketAuthority: Market.getAssociatedAuthority({ programId: marketAccount.owner, marketId: new PublicKey(accounts[keyIndex[16]]) }).publicKey.toString(), + marketBaseVault: marketInfo.baseVault.toString(), + marketQuoteVault: marketInfo.quoteVault.toString(), + marketBids: marketInfo.bids.toString(), + marketAsks: marketInfo.asks.toString(), + marketEventQueue: marketInfo.eventQueue.toString(), + lookupTableAccount: PublicKey.default.toString() + }) + } + + + + const poolInfo = formatData.poolInfos[0]; + + console.info(poolInfo) + } + }); + + // 为保证连接稳定,需要定期向服务端发送ping请求以维持连接 + const pingRequest: SubscribeRequest = { + accounts: {}, + slots: {}, + transactions: {}, + transactionsStatus: {}, + blocks: {}, + blocksMeta: {}, + entry: {}, + accountsDataSlice: [], + commitment: undefined, + ping: { id: 1 }, + }; + // 每5秒发送一次ping请求 + setInterval(async () => { + await new Promise((resolve, reject) => { + stream.write(pingRequest, (err) => { + if (err === null || err === undefined) { + resolve(); + } else { + reject(err); + } + }); + }).catch((reason) => { + console.error(reason); + throw reason; + }); + }, 5000); +} + + + +main(); \ No newline at end of file