跟踪 Raydium 上的新池

在本文中,我们将探讨如何使用 Shyft 的 gRPC 服务实时跟踪 Raydium 上新创建的池。

跟踪 Raydium 上的新池
一键发币: SOL | BNB | ETH | BASE | Blast | ARB | OP | POLYGON | AVAX | FTM | OK

Raydium 是 Solana 区块链上的自动做市商 (AMM),它通过利用流动性池来促进去中心化代币交换。这些池充当虚拟储存器,用户可以直接在其中交换代币,而无需中央订单簿。作为 Solana 上领先的 AMM,Raydium 为用户提供了一种无缝且高效的代币交换方式,同时使流动性提供者能够获得奖励。

对于积极交换和交易代币的用户,或者对于交易机器人或在 Raydium 上构建实时应用程序的用户,及时了解平台事件(例如新池的创建、池的当前状态或代币交换)至关重要。Shyft 的 gRPC 服务可以通过提供实时流式传输功能来满足这一需求,确保用户立即获知涉及 Raydium 上任何池的任何更改或更新。这允许在 Raydium 的动态环境中根据价格变动和实时技术分析进行更有效的监控和战略性地安排交换时间。

在本文中,我们将探讨如何使用 Shyft 的 gRPC 服务实时跟踪 Raydium 上新创建的池。

1、开始之前

要开始,我们需要一些东西。

  • 身份验证:你的 Shyft API 密钥、gRPC 端点和 gRPC 令牌

所有 Shyft 服务都需要一个称为 Shyft API 密钥的身份验证参数。你可以从 Shyft 网站获取自己的 API 密钥。只需在此处使用你的电子邮件 ID 注册即可免费获得。

Shyft 的 gRPC 节点遍布整个欧盟和美国地区的各个位置。要进行访问,我们需要一个特定于区域的 gRPC 端点(可在您的 Shyft 仪表板上使用)和一个访问令牌(您可以通过在 Shyft 的 Discord 服务器上打开票证来请求该令牌)。

  • 用于接收 gRPC 数据的服务器端后端(如 NodeJS)

由于 Web 浏览器不支持 gRPC 服务,因此你需要一个后端应用程序来接收 gRPC 数据。对于此示例,我们使用了 NodeJS,但也可以使用任何其他后端服务器端语言,例如 C#、Go、Java、Kotlin、Python 或 PHP。

2、简介

Shyft 的 geyser-fed gRPC 为账户更新、交易、区块和插槽更新提供流式传输服务,只要它们在链上发生。我们的计划包括集成 Shyft 的 gRPC 服务,为 Raydium Liquidity Pool v4 建立交易流式传输管道。这将使我们能够在区块链上发生交易时立即接收它们。一旦流式传输到我们的后端,这些交易将被及时解析和处理,从中我们可以轻松识别在 Raydium 上创建新池的交易。这种实时方法可确保您的用户不断了解平台上的新池,为他们探索新的投资可能性提供潜在优势。

3、初始化 - gRPC 访问和客户端

Shyft 的 gRPC 访问令牌可在其 discord 服务器中使用。有关更多详细信息,请参阅“入门前”部分。

一旦我们获得访问令牌和 gRPC URL,我们将继续从 Yellow Stone 服务导入客户端类。初始化新客户端需要两个参数:gRPC URL 和访问令牌。客户端可以按如下方式初始化:

Shyft 提供 Dragonmouth gRPC 节点,最初由 Triton One 作为其 Yellowstone 项目的一部分开发。这些节点支持从 Solana 网络实时传输数据。

要使用 Node.js 连接到这些节点,你需要 @triton-one/yellowstone-grpc SDK。安装后,可以初始化客户端对象。这涉及:

  • 导入客户端类:从 @triton-one/yellowstone-grpc SDK 导入客户端类。
  • 获取凭据:从 Shyft 获取您的访问令牌和 gRPC URL。
  • 初始化客户端:使用客户端类创建新的客户端对象。在初始化期间提供访问令牌和 gRPC URL 作为参数。

可以按以下方式初始化客户端:

import Client from "@triton-one/yellowstone-grpc";

const client = new Client(
  "https://grpc.us.shyft.to", //Your Region specific Shyft gRPC URL
  "hbdj-asjnf-access-token-asdh", //Shyft gRPC Access Token
  undefined,
);

有关如何使用 gRPC 设置客户端和流式传输交易的更详细指南,请随时查看有关流式传输实时 Solana 交易的文章。

4、启用实时更新 - 请求和接收流

Shyft 的 gRPC 服务利用订阅流来促进来自 Solana 区块链的实时更新。这些流充当持久连接,允许你的应用程序在链上发生事件时不断接收更新。要在上一步中创建的客户端上设置新的订阅流,请使用 client.subscribe() 方法。

const stream = await client.subscribe();

建立订阅流后,就该指定要接收的数据了。Shyft 的 gRPC 接口提供各种更新,包括帐户更改、交易、新区块,甚至插槽更新。为了避免信息过载,您可以通过订阅请求指定您实际需要的内容。

这些订阅请求采用不同的格式,每个格式都有自己的一组参数,它们看起来有点像这样:

import { CommitmentLevel } from "@triton-one/yellowstone-grpc";

const req: SubscribeRequest = {
  accounts: {},
  slots: {},
  transactions: {},
  transactionsStatus: {},
  entry: {},
  blocks: {},
  blocksMeta: {},
  accountsDataSlice: [],
  ping: undefined,
  commitment: CommitmentLevel.CONFIRMED,
};

大多数请求参数都是不言自明的,与它们的名称完全一致,

  • accounts:你可以通过指定此参数订阅特定帐户(例如,SOL-USDC OpenBook),并根据承诺级别(已处理、已确认、已完成)接收更新。
  • accountDataSlice:此字段可帮助您过滤 gRPC 流,以便您仅接收流数据的相关部分。例如,你正在流式传输帐户,其数据大小为 200 字节,但在一定偏移量后只需要 40 字节。此字段可以帮助您过滤流中每个更新的 40 字节。
  • transactions & transactionsStatus:你可以接收所有交易的更新,或根据特定标准(投票/失败交易,包括/排除帐户)对其进行过滤。也可以使用它来监控程序。
  • slotsblocks &  blocksMeta:随时了解区块链上正在生成的新区块和 slot。
  • commitment:这指定任何更新的提交级别,无论是已处理、已确认还是已完成。

以下是接收 Raydium 交易的订阅请求示例:

const req = {
  accounts: {},
  slots: {},
  transactions: {
    raydiumLiquidityPoolv4: {
      vote: false,
      failed: false,
      signature: undefined,
      accountInclude: [RAYDIUM_PUBLIC_KEY.toBase58()],
      accountRequired: [],
    },
  },
  transactionsStatus: {},
  entry: {},
  blocks: {},
  blocksMeta: {},
  accountsDataSlice: [],
  ping: undefined,
  commitment: CommitmentLevel.CONFIRMED, //for receiving confirmed txn updates
};

对于实时流式交易,我们使用交易参数。你还可以使用 raydiumLiquidityPoolV4 设置自定义标签(可以将其视为数据流的昵称)。投票和失败字段可让您选择是仅查看成功的交易(投票 = true)还是包含失败的交易(失败 = true)。

accountInclude 参数接受一组帐户(任何 solana 地址),并向你发送涉及提及帐户的交易,在我们的例子中是程序地址(特定程序的地址,例如 Raydiumv4)。最后,承诺字段定义了你希望交易确认的级别:已处理(如草稿)、已确认(更可靠)或已完成(最可靠,但可能需要更长的时间)。请记住,将所有字段留空将广播所有交易。否则,这些字段就像逻辑 AND 一样一起工作,数组中的值就像逻辑 OR 一样工作。

成功设置流后,必须有一个函数来处理收到的交易以进行进一步的操作。 stream.on('data',callbackFunc()) 方法处理流,如下所示:

//callback function that handles the stream
stream.on("data", (data) => {
    if (data?.transaction) {
      const txn = TXN_FORMATTER.formTransactionFromJson(
        data.transaction,
        Date.now(),
      );
      const decodedRaydiumIxs = decodeRaydiumTxn(txn);

      if (!decodedRaydiumIxs?.length) return;
      const createPoolIx = decodedRaydiumIxs.find((decodedRaydiumIx) => {
        if (
          decodedRaydiumIx.name === "raydiumInitialize" ||
          decodedRaydiumIx.name === "raydiumInitialize2"
        ) {
          return decodedRaydiumIx;
        }
      });
      if (createPoolIx) {
        console.log(
          `New LP Found https://translator.shyft.to/tx/${txn.transaction.signatures[0]} \n`,
          JSON.stringify(createPoolIx.args, null, 2) + "\n",
        );
      }
    }
  });

5、注意事项 — 重新连接机制

虽然 gRPC 流式传输提供了一种可靠的实时数据接收方式,但意外的网络问题仍然会中断连接。为了确保无缝体验,你可以在应用程序中实现重新连接机制。以下是示例机制,如果 gRPC 流断开连接,它将自动尝试重新建立该流。

async function subscribeCommand(client: Client, args: SubscribeRequest) {
  while (true) {
    try {
      await handleStream(client, args); //function which handles the stream
    } catch (error) {
	    //iff the stream disconnects due to any error, this will wait for a second and restart the stream
      console.error("Stream error, restarting in 1 second...", error);
      await new Promise((resolve) => setTimeout(resolve, 1000));
    }
  }
}

6、接收交易并识别新池

因此,我们已经了解了 Shyft 的 gRPC 服务,你可以毫不费力地实时传输 Solana 交易。这是从 gRPC 收到的原始交易:

{
  filters: [ 'raydiumLiquidityPoolV4' ],
  account: undefined,
  slot: undefined,
  transaction: {
    transaction: {
      signature: Buffer(64) [Uint8Array] [
        187,  60,  62,  68, 161, 246, 245, 181,   4,  69, 154.....
      ],
      isVote: false,
      transaction: {
        signatures: [
          Buffer(64) [Uint8Array] [
            187,  60,  62,  68, 161, 246, 245, 181,   4,  69, 154...
          ]
        ],
        message: {
          header: {
            numRequiredSignatures: 1,
            numReadonlySignedAccounts: 0,
            numReadonlyUnsignedAccounts: 9
          },
          accountKeys: [
            Buffer(32) [Uint8Array] [
              223,  88, 182,  18, 209,  53, 153, 11.....
            ]
          ],
          recentBlockhash: Buffer(32) [Uint8Array] [
            163,  84, 237, 169, 198,  24,  25, 131.....
          ],
          instructions: [
            {
              programIdIndex: 14,
              accounts: Uint8Array(0) [],
              data: Buffer(9) [Uint8Array] [
                3, 64, 66, 15, 0,
                0,  0,  0,  0
              ]
            },
            {
              programIdIndex: 15,
              accounts: Buffer(2) [Uint8Array] [ 0, 1 ],
              data: Buffer(124) [Uint8Array] [
                  3,   0,   0,   0, 223,  88, 182,  18, 209,  53, 153,  11,
                 ......
              ]
            },
            {
              programIdIndex: 16,
              accounts: Buffer(4) [Uint8Array] [ 1, 17, 0, 18 ],
              data: Buffer(1) [Uint8Array] [ 1 ]
            },
            {
              programIdIndex: 19,
              accounts: Buffer(18) [Uint8Array] [
                16, 2, 20, 3,  4,  5,  6,
                21, 7,  8, 9, 10, 11, 12,
                22, 1, 13, 0
              ],
              data: Buffer(17) [Uint8Array] [
                9, 184, 98, 141, 47,   0, 0,
                0,   0, 79,  50, 51, 244, 1,
                0,   0,  0
              ]
            },
            {
              programIdIndex: 16,
              accounts: Buffer(3) [Uint8Array] [ 1, 0, 0 ],
              data: Buffer(1) [Uint8Array] [ 9 ]
            }
          ],
          versioned: true,
          addressTableLookups: []
        }
      },
      meta: {
        err: undefined,
        fee: '805000',
        preBalances: [
          '116096294303',  '0',
          '6124800',       '23357760',
          '16258560',      '2039280',
          '7554878430798', '3591360',
          '101977920',     '101977920',
          '79594560',      '2039280',
          '2039280',       '2039280',
          '1',             '1',
          '934087680',     '583216369927',
          '1009200',       '1141440',
          '4224577002',    '1141440',
          '0'
        ],
        postBalances: [
          '115297694303',  '0',
          '6124800',       '23357760',
          '16258560',      '2039280',
          '7555676225798', '3591360',
          '101977920',     '101977920',
          '79594560',      '2039280',
          '2039280',       '2039280',
          '1',             '1',
          '934087680',     '583216369927',
          '1009200',       '1141440',
          '4224577002',    '1141440',
          '0'
        ],
        innerInstructions: [
          {
            index: 3,
            instructions: [
              {
                programIdIndex: 16,
                accounts: Buffer(3) [Uint8Array] [ 1, 6, 0 ],
                data: Buffer(9) [Uint8Array] [
                  3, 184, 98, 141, 47,
                  0,   0,  0,   0
                ],
                stackHeight: 2
              },
              {
                programIdIndex: 16,
                accounts: Buffer(3) [Uint8Array] [ 5, 13, 20 ],
                data: Buffer(9) [Uint8Array] [
                  3, 96, 181, 51, 249,
                  1,  0,   0,  0
                ],
                stackHeight: 2
              }
            ]
          }
        ],
        innerInstructionsNone: false,
        logMessages: [
          'Program ComputeBudget111111111111111111111111111111 invoke [1]',
          'Program ComputeBudget111111111111111111111111111111 success'
          //shortend
        ],
        logMessagesNone: false,
        preTokenBalances: [
          {
            accountIndex: 5,
            mint: 'G2JNEiqhWunMnDGxGxgPYppWiCiovqKezS6XBgjiinP6',
            uiTokenAmount: {
              uiAmount: 80473675.722615,
              decimals: 6,
              amount: '80473675722615',
              uiAmountString: '80473675.722615'
            },
            owner: '5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1',
            programId: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'
          } //shortened
        ],
        postTokenBalances: [
          {
            accountIndex: 5,
            mint: 'G2JNEiqhWunMnDGxGxgPYppWiCiovqKezS6XBgjiinP6',
            uiTokenAmount: {
              uiAmount: 80465199.839767,
              decimals: 6,
              amount: '80465199839767',
              uiAmountString: '80465199.839767'
            },
            owner: '5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1',
            programId: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'
          } //shortened
        ],
        rewards: [],
        loadedWritableAddresses: [],
        loadedReadonlyAddresses: [],
        returnData: undefined,
        returnDataNone: true,
        computeUnitsConsumed: '38821'
      },
      index: '322'
    },
    slot: '275598744'
  },
  block: undefined,
  ping: undefined,
  pong: undefined,
  blockMeta: undefined,
  entry: undefined
}

与传统 RPC 响应相比,gRPC 事务流提供类似的数据,但增加了一些字段。一些相关的附加字段包括 isVote(用于识别投票事务)、详细说明签名和签名者信息的消息头。响应还表示它是否是版本化事务,并且还提供了阻止、ping 和输入的规定。

为了将从 gRPC 收到的此事务转换为类似传统 RPC 的事务,我们使用 Shyft 创建的以下实用函数。

import { TransactionFormatter } from "./utils/transaction-formatter";

const TXN_FORMATTER = new TransactionFormatter();
const txn = TXN_FORMATTER.formTransactionFromJson(
    data.transaction,
    Date.now(),
);

该函数在响应方面与传统 Solana 的 getTransaction 相似,返回 slot、版本、区块时间、元数据和交易,从而轻松与现有 Solana 应用程序集成。

7、发现新的 Raydium 池

下一步涉及识别在 Raydium 上创建新池的交易。每当在 Raydium 中创建新池时,都会在区块链上执行包含 raydiumInitialize 或 raydiumInitialize2 指令的特定交易。由于我们已经实时流式传输与 Raydium 相关的交易,因此我们可以访问与 Raydium 协议相关的所有交易。

但是,我们需要分析从 gRPC 流中收到的这些原始交易,以识别包含 raydiumInitialize / raydiumInitialize2 指令的交易,并且只有在解析交易后才有可能。我们已经使用 Shyft 的交易解析器来解析从 gRPC 收到的原始 Raydium 交易。

解析后,交易会有一个指令列表,这些指令本质上是执行交易时执行的操作。每条指令都包含一个名称、与之关联的程序 ID、数据、涉及的帐户等。我们必须筛选交易中的指令,并找出 raydiumInitialize / raydiumInitialize2 指令是否存在(代码如下):

function decodeRaydiumTxn(tx: VersionedTransactionResponse) {
  if (tx.meta?.err) return;

  const allIxs = TXN_FORMATTER.flattenTransactionResponse(tx);
  
  //getting all instructions from the transaction 
  //and filtering them by program ID

  const raydiumIxs = allIxs.filter((ix) =>
    ix.programId.equals(RAYDIUM_PUBLIC_KEY),
  );

  const decodedIxs = raydiumIxs.map((ix) =>
    RAYDIUM_PARSER.parseInstruction(ix),
  );

  return decodedIxs;
}

在我们分解交易(解析)之后,我们得到了一个“指令”列表,这些指令本质上是执行交易时执行的操作。每条指令都有详细信息,例如其名称、所连接的程序(如 Raydium)以及所涉及的帐户。我们的工作是筛选这些指令,看看是否有一个名为“raydiumInitialize”或“raydiumInitialize2”的特定指令。如果我们找到其中一个,那么该交易正在创建一个新池!

8、适应其他 DEX — 适用于类似于 Raydium 的程序

这种池检测方法的优点在于它的适应性!它可以应用于使用类似创建池(初始化)指令创建池的其他去中心化交易所 (DEX)。一些例子包括 Meteora、Orca 和 Jupiter。

核心步骤基本相同,但有两个关键调整:

  • 程序 ID 替换:在订阅请求设置期间,你需要将 Raydium 程序 ID 替换为目标 DEX(例如 Meteora、Orca、Jupiter)的特定程序 ID。这可确保你订阅与该特定 DEX 相关的交易。
  • 相应的交易解析器:你不需要依赖 Shyft 的 Raydium 解析器,而是需要使用为你感兴趣的特定 DEX 设计的专用交易解析器。这些解析器本质上是经过优化的“代码转换器”,可以理解该 DEX 交易中的结构和指令,并且它们都可以通过 Shyft 获得。

通过实施这些调整,可以有效地识别 Solana 区块链上各个 DEX 的新池创建事件。这使你能够保持领先地位并探索 DeFi 生态系统中的各种池机会。


原文链接:How to track new pools on Raydium with Shyft gRPC

DefiPlot翻译整理,转载请标明出处

通过 NowPayments 打赏