使用 Solana gRPC 监控 Solana 程序(TypeScript)
本文介绍如何使用Solana gRPC(QuickNode的Yellowstone兼容Geyser gRPC)实时监控Solana链上活动。
更新:用于历史回放的 fromSlot 参数及其他
截至 2025 年 7 月,Quicknode 已启用 fromSlot 参数,允许客户端从最近 3000 个 slot(约 20 分钟)重放数据,为可能遇到网络不稳定或需要在恢复期间回填的客户端提供了更好的灵活性。
本指南已更新以反映这一新功能,同时更新为使用 Solana Kit、当前版本的 Node.js 以及其他较新的工具。
概述
在本指南中,我们将学习如何使用 Solana gRPC(兼容 Yellowstone 的 Geyser gRPC)来监控实时链上活动。具体来说,我们将创建一个 TypeScript 应用程序,跟踪 Solana 主网上 Pump.fun 程序的新代币发行。该项目将演示如何利用 Solana gRPC 的低延迟数据访问能力,构建响应迅速且高效的监控工具。
更喜欢视频形式?跟随视频学习如何在 9 分钟内使用 Solana gRPC 监控 Solana 程序数据。
如何使用 Solana gRPC Geyser 插件 Quicknode
订阅我们的 YouTube 频道以获取更多视频!订阅
你将做什么
- 了解 Geyser 和 Solana gRPC
- 使用 TypeScript 和 Solana gRPC 监控 Solana 上新的 Pump.fun 发行
- 能够将此逻辑复用到其他程序
程序输出如下所示:

你需要什么
- 对 Solana 基础 的基本了解
- Node.js(v20 或更高版本)
- 一个 Quicknode 账户,需为 Scale 或 Business 计划(包含 Solana gRPC),或 Build/Accelerate 计划并搭配 Solana gRPC 附加组件
- 你喜欢的代码编辑器(例如 VS Code)
什么是 Geyser?
Geyser 是 Solana 验证器的一个插件系统,它提供低延迟的区块链数据访问,而不会因为密集的 RPC 请求(例如 getProgramAccounts)使验证器过载。Geyser 插件不是直接查询验证器,而是将关于账户、交易、slot 和区块的实时信息流式传输到你选择的外部数据存储,例如关系型数据库、NoSQL 数据库或像 Kafka 这样的流式平台。这种方法显著减少了验证器的负载,同时提高了数据访问效率。
Geyser 插件的主要优势在于它们能够与高流量的 Solana 应用程序一起扩展。通过将数据查询路由到外部存储,开发人员可以实现优化的访问模式,如缓存和索引,这对于需要频繁访问大型数据集或历史信息的应用程序尤其有价值。这种分离允许验证器专注于其处理交易的主要角色,同时确保开发人员拥有他们所需的全面、实时数据访问。
什么是 Solana gRPC?
Solana gRPC 是 Quicknode 的兼容 Yellowstone 的 Geyser gRPC 服务——可与开源 Yellowstone Geyser gRPC 生态系统互操作。它利用 gRPC,谷歌的高性能框架,结合了用于序列化的 Protocol Buffers 和用于传输的 HTTP/2,实现了分布式系统之间快速且类型安全的通信。
Solana gRPC 提供以下内容的实时流:
- 账户更新
- 交易
- 条目
- 区块通知
- Slot 通知
与传统的 WebSocket 实现相比,Solana gRPC 提供更低的延迟和更高的稳定性。它还包含用于快速一次性数据检索的 unary 操作。gRPC 的高效性和类型安全性相结合,使得 Solana gRPC 特别适合基于云的服务和数据库更新。Solana gRPC 包含在 Scale 和 Business 计划中。在 Build 和 Accelerate 计划中,它仍然可以通过 Solana gRPC 附加组件 获得。
让我们通过编写一个脚本来监控 Solana 上新的 Pump.fun 发行,看看 Solana gRPC 的实际应用。
高流量数据流的性能注意事项
如果在 Node.js 上使用 Solana gRPC 处理非常大的数据量,可能会使单个 CPU 过载。如果你正在构建需要处理繁忙程序的所有交易或同时跟踪多个程序的系统,请考虑以下高性能替代方案:
-
使用多线程。Node.js 有两个模块:
cluster(多进程,每个进程一个线程)和worker(单进程,多线程)。两者都可以解决节点过载单个核心的问题,但cluster更简单,因为每个进程是内存隔离的。将 Solana gRPC 订阅分配到cluster或worker的多个实例之间,以分散负载。 -
使用 Go 上的 Solana gRPC(另请参见 Go 上的 Solana gRPC 指南)。
创建新项目
让我们开始设置一个新的 TypeScript 项目:
- 为你的项目创建一个新目录并进入该目录:
mkdir pump-fun-monitor && cd pump-fun-monitor
- 初始化一个新的 Node.js 项目:
npm init -y
然后在 package.json 文件中通过添加以下内容启用 ES6 模块:
{
...existing package.json content...
"type": "module"
}
- 安装所需的依赖:
npm install tsx @triton-one/yellowstone-grpc @types/node @solana/kit
- 保存你想监控的程序的 IDL。在这种情况下,我们将获取 Pump.fun 程序的 IDL。
curl -o program.json https://raw.githubusercontent.com/rckprtr/pumpdotfun-sdk/refs/heads/main/src/IDL/pump-fun.json
现在你已经准备好开始编写程序了!
编写脚本
让我们创建程序,使用 Solana gRPC 监控 Pump.fun 发行。我们将分解为以下几个步骤:
步骤 1:定义接口
我们主要会使用 @triton-one/yellowstone-grpc 包中的接口,因此在这里不需要定义太多。创建一个名为 lib/interfaces.ts 的新文件,并添加以下代码:
export interface CompiledInstruction {
programIdIndex: number;
accounts: Uint8Array;
data: Uint8Array;
}
export interface MintInformation {
mint: string;
transaction: string;
slot: number;
}
步骤 2:创建一个函数来获取你的 Solana gRPC 端点
Solana gRPC 使用与常规 RPC 端点不同的端口。此函数会将你的 Quicknode RPC 端点转换为 Solana gRPC 端点和 Token。创建一个名为 lib/quicknode.ts 的新文件,并添加以下代码:
// 将 RPC 端点转换为 Solana gRPC 端点和 Token
export const getYellowstoneEndpointAndToken = (rpcEndpoint: string) => {
// 将端点转换为 URL 对象
const url = new URL(rpcEndpoint);
const YELLOWSTONE_PORT = 10000;
// Solana gRPC 端点与 RPC 端点相同,但使用端口 10000 且没有路径名
const yellowstoneEndpoint = `${url.protocol}//${url.hostname}:${YELLOWSTONE_PORT}`;
// Token 是 RPC 端点的路径名,但没有前导斜杠
const yellowstoneToken = url.pathname.replace(/\//g, "");
return { yellowstoneEndpoint, yellowstoneToken };
};
步骤 3:创建辅助函数
Solana gRPC 返回交易签名和代币地址作为 Uint8Array。我们需要将它们转换为 base58 以使其可读。创建一个名为 lib/helpers.ts 的新文件,并添加以下代码:
import { getBase58Decoder } from "@solana/kit";
export const bufferToBase58 = (buffer: Uint8Array): string => {
return getBase58Decoder().decode(buffer);
};
接下来,我们将添加一个函数来获取地址或交易的浏览器 URL。现代终端应用程序支持点击链接,因此它将允许我们在 mint 创建时看到每个 mint:
export const getExplorerUrl = (address: string, type: "address" | "tx") => {
return `https://explorer.solana.com/${type}/${address}`;
};
接下来,我们将添加一个函数,从程序的 IDL 中获取指令处理器的鉴别器。我们可以稍后使用它来查找调用了此指令处理器的指令。
export const getInstructionHandlerDiscriminator = (
programIdl: any,
instructionName: string
) => {
const instruction = programIdl.instructions.find(
(instruction: any) => instruction.name === instructionName
);
const discriminatorBytes = instruction.discriminator;
return Buffer.from(discriminatorBytes);
};
接下来,我们将添加一个函数,获取指令处理器中使用的账户名称和索引。我们可以稍后使用它来过滤交易,仅包含特定指令处理器的指令。
export const getAccountsFromIdl = (
programIdl: any,
instructionName: string
): Array<{ name: string; index: number }> => {
const instruction = programIdl.instructions.find(
(instruction: any) => instruction.name === instructionName
);
if (!instruction) {
throw new Error(`IDL 中未找到指令 '${instructionName}'`);
}
return instruction.accounts.map((account: any, index: number) => ({
name: account.name,
index: index,
}));
};
步骤 4:创建常量
有些东西变化不大,所以让我们定义它们。创建一个名为 lib/constants.ts 的新文件,并添加以下代码:
// 由 Solana 设置
export const SOLANA_SLOT_TIME_MS = 400;
// 由 Quicknode 设置
export const MAX_SLOTS_TO_REPLAY = 3000;
// 由 18 世纪晚期的法国科学家设置
export const SECONDS = 1000;
// 由古代 Babylon 人设置
export const MINUTES = SECONDS * 60;
export const MAX_TIME_TO_REPLAY_MS = MAX_SLOTS_TO_REPLAY * SOLANA_SLOT_TIME_MS;
export const MAX_TIME_TO_REPLAY_MINUTES = MAX_TIME_TO_REPLAY_MS / 1000 / 60;
步骤 5:创建我们的 Solana gRPC 客户端
创建一个名为 lib/yellowstone.ts 的新文件。我们将使其尽可能通用,以便你可以在多个项目中重用。首先导入相关依赖——@triton-one/yellowstone-grpc 客户端、gRPC 以及我们的辅助函数、接口和常量。
import Client, {
CommitmentLevel,
SubscribeRequest,
SubscribeUpdate,
SubscribeUpdateTransaction,
} from "@triton-one/yellowstone-grpc";
import { ClientDuplexStream } from "@grpc/grpc-js";
import { bufferToBase58, getExplorerUrl } from "./helpers";
import { CompiledInstruction, MintInformation } from "./interfaces";
import {
MAX_SLOTS_TO_REPLAY,
MAX_TIME_TO_REPLAY_MINUTES,
SOLANA_SLOT_TIME_MS,
} from "./constants";
让我们创建几个方便的辅助函数。Solana gRPC 原生地将 slot 作为字符串返回,所以让我们修复这个问题:
export const getCurrentSlot = async (
yellowstoneClient: Client
): Promise<number> => {
const currentSlotString = await yellowstoneClient.getSlot();
return Number(currentSlotString);
};
创建订阅请求
此函数将创建一个 SubscribeRequest,监控给定的程序 ID 和必需的账户。
我们希望过滤器尽可能具体,以减少从服务器接收的数据量:
accountInclude:包含使用数组中任何账户的交易accountExclude:排除使用数组中任何账户的交易accountRequired:仅包含使用数组中所有账户的交易
我们倾向于使用 accountRequired,因为它比 accountInclude 和 accountExclude 更具体。
export const createSubscribeRequest = (
includedAccounts: Array<string>,
excludedAccounts: Array<string>,
requiredAccounts: Array<string>,
fromSlot: number | null = null
): SubscribeRequest => {
// 查看 https://github.com/rpcpool/yellowstone-grpc?tab=readme-ov-file#filters-for-streamed-data 获取完整过滤器列表。
const request: SubscribeRequest = {
commitment: CommitmentLevel.CONFIRMED,
accounts: {},
slots: {},
transactions: {
// 这里可以有多个过滤器,但在此演示中,我们只使用一个。
// 当收到事件时,我们可以检查匹配了哪个过滤器。
// https://github.com/rpcpool/yellowstone-grpc?tab=readme-ov-file#transactions
pumpFun: {
vote: false,
failed: false,
accountInclude: includedAccounts,
accountExclude: excludedAccounts,
accountRequired: requiredAccounts,
},
},
transactionsStatus: {},
entry: {},
blocks: {},
blocksMeta: {},
accountsDataSlice: [],
ping: undefined,
};
if (fromSlot) {
// Solana gRPC 期望 slot 作为字符串,所以让我们修复这个问题。
request.fromSlot = String(fromSlot);
}
return request;
};
让我们也创建一个函数,将订阅请求发送到 Solana gRPC 流——我们稍后将创建流。
export const sendSubscribeRequest = (
stream: ClientDuplexStream<SubscribeRequest, SubscribeUpdate>,
request: SubscribeRequest
): Promise<void> => {
return new Promise<void>((resolve, reject) => {
stream.write(request, (error: Error | null) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
};
添加一些客户端过滤器
我们的大部分过滤是通过 Solana gRPC 完成的,但它不直接按指令处理器过滤交易,因此我们将添加一个额外的客户端检查:
// Solana gRPC 不直接按指令处理器过滤,所以我们必须
// 在客户端执行此操作。
export const checkInstructionMatchesInstructionHandlers = (
instruction: CompiledInstruction,
instructionHandlerDiscriminators: Array<Uint8Array>
): boolean => {
return (
instruction?.data &&
instructionHandlerDiscriminators.some((instructionHandlerDiscriminator) =>
Buffer.from(instructionHandlerDiscriminator).equals(
instruction.data.slice(0, 8)
)
)
);
};
我们还将创建一个函数,生成一个包含名称/地址对的有用对象,以便我们可以获取此指令为特定账户名称(例如 Pump.fun 上每个新代币的 mint 账户)使用的地址,并在漂亮的表格中显示它们。
export const getAccountsByName = (
accountsToInclude: Array<{ name: string; index: number }>,
instruction: CompiledInstruction,
accountKeys: Array<Uint8Array>
): Record<string, string> => {
return accountsToInclude.reduce<Record<string, string>>(
(accumulator, account) => {
const accountIndex = instruction.accounts[account.index];
const address = bufferToBase58(accountKeys[accountIndex]);
accumulator[account.name] = address;
return accumulator;
},
{}
);
};
完成!现在让我们开始处理从 Solana gRPC 收到的更新,使用我们刚刚创建的函数。
创建一个函数,将 SubscribeUpdate 转换为格式良好的 mint 信息
我们将创建一个函数,将 Solana gRPC 的 SubscribeUpdate 转换为 MintInformation——一个包含 mint 地址、交易签名和 slot 编号的简单对象。
export const getMintInfoFromUpdate = (
update: SubscribeUpdate,
instructionHandlerDiscriminators: Array<Uint8Array>,
accountsToInclude: Array<{ name: string; index: number }>
): null | MintInformation => {
// 检查匹配的过滤器名称
//(Solana gRPC 还会发送其他内容,例如 'ping' 更新,但我们不关心这些)
if (!update.filters.includes("pumpFun")) {
return null;
}
// 在此演示中,这些情况不应该发生,
// 因为我们的过滤器匹配将包含正确的属性。
// 但让我们满足类型检查器。
const transaction = update.transaction?.transaction;
const message = transaction?.transaction?.message;
const slot = update.transaction?.slot;
if (!transaction || !message || !slot) {
return null;
}
// 查找与我们的目标指令处理器匹配的指令
const instruction =
message.instructions.find((instruction) =>
checkInstructionMatchesInstructionHandlers(
instruction,
instructionHandlerDiscriminators
)
) || null;
if (!instruction) {
return null;
}
// 生成一个包含账户值/地址对的有用对象,以便我们获取
// 此指令为每个账户名称使用的地址值。
const accountsByName = getAccountsByName(
accountsToInclude,
instruction,
message.accountKeys
);
const base58TransactionSignature = bufferToBase58(transaction.signature);
return {
mint: getExplorerUrl(accountsByName.mint, "address"),
transaction: getExplorerUrl(base58TransactionSignature, "tx"),
slot: Number(slot),
};
};
将流连接到我们的函数
现在我们准备将流连接到我们的函数。每当流发送更新时,我们将调用 getMintInfoFromUpdate 获取 mint 信息,并将其打印到控制台。打开 lib/yellowstone.ts 并添加以下代码:
export const handleStreamEvents = (
stream: ClientDuplexStream<SubscribeRequest, SubscribeUpdate>,
instructionDiscriminators: Array<Uint8Array>,
accountsToInclude: Array<{ name: string; index: number }>
): Promise<void> => {
return new Promise<void>((resolve, reject) => {
stream.on("data", (update: SubscribeUpdate) => {
const mintInfo = getMintInfoFromUpdate(
update,
instructionDiscriminators,
accountsToInclude
);
if (mintInfo) {
console.log("💊 检测到新的 Pump.fun Mint!");
console.table(mintInfo);
console.log("\n");
}
});
stream.on("error", (error: Error) => {
console.error("流错误:", error);
reject(error);
stream.end();
});
stream.on("end", () => {
console.log("流结束");
resolve();
});
stream.on("close", () => {
console.log("流关闭");
resolve();
});
});
};
yellowstone.ts 到此为止!
步骤 6:选择过滤器并整合所有内容
现在我们准备创建主脚本。在这里,我们将设置要监控的交易的值,连接到 Solana gRPC,然后调用我们刚刚创建的函数来监控交易。
我们希望过滤器尽可能具体,以减少从服务器接收的数据量,使用更少的 API 积分,并使我们的脚本更高效。我们可以只包含 PROGRAM_ID 来获取所有涉及该程序的交易,但由于我们只寻找指令的子集(在本例中是 create 指令),我们可以查看 IDL 并识别可能仅传入 create 指令处理器的任何额外账户。由于 Pump.fun Token Mint Authority 在每个 create 指令处理器中使用,因此通过要求两个账户,我们可以减少接收的数据量并使脚本更高效。
我们还想显示用于 'mint' 的特定地址。Solana 指令使用数组,而不是命名账户,因此我们需要像之前那样从程序的 IDL 中获取我们想要监控的账户的索引。随意包含你想要监控的其他账户(你可以从程序的 IDL 中获取它们的索引)。
创建一个名为 monitor-program.ts 的新文件,并添加以下代码:
import {
createSubscribeRequest,
handleStreamEvents,
sendSubscribeRequest,
getSlotFromTimeAgo,
} from "./lib/yellowstone";
import { getYellowstoneEndpointAndToken } from "./lib/quicknode";
import { env } from "node:process";
import { MINUTES } from "./lib/constants";
import Client from "@triton-one/yellowstone-grpc";
import {
getInstructionHandlerDiscriminator,
getAccountsFromIdl,
} from "./lib/helpers";
import programIdl from "./program.json";
// 我们正在监控 pump.fun 程序
const PROGRAM_ID = programIdl.address;
// 我们正在监控 create() 指令处理器
const PUMP_FUN_CREATE_INSTRUCTION_HANDLER_DISCRIMINATOR =
getInstructionHandlerDiscriminator(programIdl, "create");
const PUMP_FUN_MINT_AUTHORITY = "TSLvdd1pWpHVjahSpsvCXUbgwsL3JAcvokwaKt1eokM";
// 通过 Solana gRPC 监控的程序和必需账户
// 查看 https://github.com/rpcpool/yellowstone-grpc?tab=readme-ov-file#filters-for-streamed-data 获取完整过滤器列表。
const requiredAccounts: Array<string> = [PROGRAM_ID, PUMP_FUN_MINT_AUTHORITY];
// 从 Solana gRPC 获取事件后,我们将根据被调用的指令处理器(链上函数)进行过滤
const instructionDiscriminators: Array<Uint8Array> = [
PUMP_FUN_CREATE_INSTRUCTION_HANDLER_DISCRIMINATOR,
];
// 从 IDL 中获取 create 指令的账户信息
// 这将包括 create 指令中使用的所有账户及其名称和索引
const ACCOUNTS_TO_INCLUDE = getAccountsFromIdl(programIdl, "create");
const rpcEndpoint = env["QUICKNODE_SOLANA_MAINNET_ENDPOINT"];
if (!rpcEndpoint) {
throw new Error(
"需要 QUICKNODE_SOLANA_MAINNET_ENDPOINT 环境变量"
);
}
const { yellowstoneEndpoint, yellowstoneToken } =
getYellowstoneEndpointAndToken(rpcEndpoint);
const yellowstoneClient = new Client(yellowstoneEndpoint, yellowstoneToken, {});
// 有点令人困惑的是,我们需要在客户端上调用 `subscribe` 来获取流
// 然后向流发出订阅请求。
const stream = await yellowstoneClient.subscribe();
// 我们将在后面的指南中使用
const fromSlot = null;
// 使用 fromSlot 参数创建订阅请求
const request = createSubscribeRequest([], [], requiredAccounts, fromSlot);
await sendSubscribeRequest(stream, request);
console.log(
"🔌 Geyser 连接已建立 - 正在监控新的 Pump.fun 代币发行...\n"
);
await handleStreamEvents(
stream,
instructionDiscriminators,
ACCOUNTS_TO_INCLUDE
);
将你的 Quicknode 端点添加到 .env 文件
在运行脚本之前,从 Quicknode 仪表板获取你的端点,并将其添加到一个名为 .env 的新文件中,将端点替换为你自己的:
QUICKNODE_SOLANA_MAINNET_ENDPOINT="https://three-custom-words.solana-mainnet.quiknode.pro/1234567890abcdefghijklmnopqrstuvwxyz1234/"
不要将你的 .env 文件提交到仓库!
不要将你的 .env 文件提交到仓库,因为它包含你的 Quicknode 端点。这是一个安全风险,因为任何人都可以访问你的端点并在未经你许可的情况下使用它。将其添加到你的 .gitignore 文件中以避免这种情况。
运行脚本
现在我们准备运行脚本。在终端中,运行以下命令:
npx tsx --env-file=.env monitor-program.ts
你应该看到以下输出:
npx tsx --env-file=.env monitor-program.ts
🔌 Geyser 连接已建立 - 正在监控新的 Pump.fun 代币发行...
💊 检测到新的 Pump.fun Mint!
┌─────────────┬───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ (index) │ Values │
├─────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ mint │ 'https://explorer.solana.com/address/G8XYfdnujEiwivG8LZuj5NKppUkx6nn6Wo72A1Ckpump' │
│ transaction │ 'https://explorer.solana.com/tx/2sSHoWvNNuHVMJMPLeramPNKj8VJvtjhG6hLrci4FmnYG6xmPEqRiKHAWY15wcTGMxqhgnLi8DRBPWEM3r5bSAut' │
│ slot │ 358713246 │
└─────────────┴───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
💊 检测到新的 Pump.fun Mint!
┌─────────────┬───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ (index) │ Values │
├─────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ mint │ 'https://explorer.solana.com/address/EjrHSwxfZ3mCbLnnuXg4xYuHuzvMCbB5S4ptVzxAfonq' │
│ transaction │ 'https://explorer.solana.com/tx/2rR47UGUcv5EY5hTij8eVshZPHqLV2mUVJdZYr4kxm25EQwLdumqt3v9RfLMfGBM7ENVpRGbdD1b3Q7L4mo3W9TX' │
│ slot │ 358713253 │
└─────────────┴───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
💊 检测到新的 Pump.fun Mint!
┌─────────────┬───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ (index) │ Values │
├─────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ mint │ 'https://explorer.solana.com/address/8prBRtMZvYpppiUfcKbmteQx8gHqn8XicDZAPfPRpump' │
│ transaction │ 'https://explorer.solana.com/tx/5cS6buykKeFaE9bNDzoo8PvKixew4cV14hJMMLYSwuvQm5xWrxr2uRf2Hf3ZRrwizKbc8GsytmbxbpaXhHgYaZYJ' │
│ slot │ 358713276 │
└─────────────┴───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
你可以点击 mint 地址查看 Solana Explorer 上的 mint 信息:

有问题?完整代码可在 pump.fun monitor GitHub 仓库 中找到。
使用 fromSlot 进行历史回放
有时我们想从过去的某个时间点恢复——对于可能错过消息或在恢复期间需要回填的客户端。我们也可以从最近 3000 个 slot(20 分钟)内的任何时间点恢复。
通常我们会在新 slot 到达时保存最近的 slot 编号,并在恢复时将订阅请求中的 fromSlot 设置为该编号。
在此演示中,我们只需从特定时间前选择一个 slot——打开 lib/yellowstone.ts 并添加以下函数:
// 允许我们从过去的某个时间获取 slot
export const getSlotFromTimeAgo = async (
yellowstoneClient: Client,
timeAgo: number
) => {
const now = Date.now();
const fromTime = now - timeAgo;
const slotsAgo = Math.ceil(timeAgo / SOLANA_SLOT_TIME_MS);
if (slotsAgo > MAX_SLOTS_TO_REPLAY) {
throw new Error(
`过去的起始时间 ${new Date(
fromTime
).toISOString()} 太早了。最大可重放时间为 ${MAX_TIME_TO_REPLAY_MINUTES} 分钟。`
);
}
const currentSlot = await getCurrentSlot(yellowstoneClient);
const fromSlot = currentSlot - slotsAgo;
return fromSlot;
};
然后在 monitor-program.ts 中,我们可以将 fromSlot 设置为 5 分钟前的 slot:
// 通常,我们会在收到每个事件时记录最近的 slot,
// 并在需要恢复时从该 slot 重放。
// 在此演示中,让我们获取过去 5 分钟的交易
const fromSlot = await getSlotFromTimeAgo(yellowstoneClient, 5 * MINUTES);
重新运行脚本,你应该会看到一簇历史数据,然后追赶上实时数据。你可以点击任何 mint 或交易以在 Solana Explorer 上查看详细信息,包括时间。
总结
在本指南中,我们探索了如何使用 Solana gRPC 实时监控 Solana 程序。我们专注于跟踪 Pump.fun 程序的新代币发行,但我们所涵盖的原则可用于监控任何 Solana 程序或账户。
随着你继续在 Solana 上构建,请考虑如何利用 Solana gRPC 创建更具响应性和效率的应用程序。无论你是构建交易机器人、分析仪表板还是复杂的 DeFi 应用程序,低延迟的实时数据访问都可以为你带来显著优势。
资源
- GitHub: Pump.fun Monitor
- 文档: Solana gRPC
- Solana gRPC 市场附加组件
- Solana 文档: Solana Validator Geyser Plugins
- GitHub: Yellowstone Geyser gRPC Plugin
- GitHub: Geyser Park - Geyser 插件非官方列表
联系我们!
我们很想了解你是如何使用 Solana gRPC 的。通过 Twitter 或 Discord 向我们发送你的经验、问题或反馈。
我们 ❤️ 反馈!
如果你有任何反馈或对新主题的请求,请告诉我们。我们期待你的来信。
- 原文链接: quicknode.com/guides/sol...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~