本文介绍了如何使用Shyft的gRPC服务实时流式传输Solana交易。该方法利用gRPC的高性能通信框架,通过订阅特定数据流,可以近乎实时地接收链上发生的事件、账户更新或交易信息,并结合Shyft提供的工具,如交易解析器,快速解析和利用这些数据,适用于构建交易机器人、实时数据应用等。
Solana 的架构以极快的交易速度和低费用为特点,为构建去中心化应用程序 (dApp) 提供了一个对开发者友好的环境。Solana 上的许多应用程序,如交易机器人、分析和基于监控的应用程序,都依赖于实时数据来为用户提供有价值的见解并做出明智的决策。获取更新的传统方法可能会引入延迟,导致用户获得过时的数据,从而导致错失机会和错误的决策。
这就是 gRPC 流式传输的用武之地。gRPC 是用于应用程序之间通信的高性能框架,并允许服务器和客户端之间持续的数据流(流)。这可以立即通知用户 任何事件、账户更新或交易,一旦它们在链上发生,使用户能够根据应用程序的需要对这些变化做出反应。gRPC 还提供强大的过滤功能,确保你的应用程序仅接收相关数据。在本文中,我们将探讨如何使用 Shyft 的 gRPC 服务实时流式传输 Solana 交易。
我们创建了一些 replit 示例,说明了使用 Shyft 进行 gRPC 流式传输,如果你想继续操作,请随时 fork 它们 —
注意:不要忘记在 secrets 部分添加 gRPC url 和 access token。有关更多详细信息,请参阅“开始之前”部分。
要开始,我们需要一些东西。
身份验证:你的 Shyft API 密钥、gRPC 端点和 gRPC Token
你可以从 Shyft 网站 获取你自己的 Shyft API 密钥(Shyft 使用的身份验证参数)。你还可以在你的 Shyft 仪表板 上找到你所在区域的 gRPC 端点和访问Token。
用于接收 gRPC 数据的服务器端后端(如 NodeJS)
由于 web 浏览器不支持 gRPC 服务,因此你需要一个后端应用程序来接收 gRPC 数据。在本例中,我们使用了 NodeJS,但也可以使用任何其他后端 服务器端语言,如 C#、Go、Java、Kotlin、Python 或 PHP。
虽然这篇博客探讨了 gRPC 流式传输在 Solana 上实时交易的更广泛应用,但让我们以 pump.fun 为例。在这里,我们将演示如何设置 gRPC 流以接收有关 pump.fun 交易的实时更新。但是,它们可以根据你的应用程序要求用于任何程序。
在幕后,Geyser 插件充当 Solana 网络和你的 dApp 之间的数据桥梁。它们捕获所有关键细节,如账户、区块,最重要的是交易。Shyft 的 Geyser 驱动的 gRPC 服务利用这些插件来提供此信息的连续流,确保你始终了解你正在为其设置流的程序上发生的事情。但是,设置流需要几个步骤:
从 Shyft 获取 gRPC url 和访问Token Shyft 的特定区域的 gRPC url 在仪表板中可用,访问Token在其 discord 服务器中可用。有关更多详细信息,请参阅 “开始之前” 部分。
初始化 gRPC 客户端 Shyft 提供 Dragon's Mouth gRPC 节点,最初由 Triton 开发,作为其 yellowstone 项目的一部分。它在各种语言中都有许多预构建的客户端库,可与 gRPC 接口交互。一些支持的语言及其客户端:
对于本文,我们使用了 NodeJS 客户端,但可以根据你的要求选择任何客户端。首先,我们使用以下命令在我们的项目中安装 NodeJS 客户端:
## 对于 npm,
npm install --save @triton-one/yellowstone-grpc
## 或者对于 yarn
yarn add @triton-one/yellowstone-grpc
安装 NodeJS 客户端库后,我们现在可以初始化客户端。我们只需从 @triton-one/yellowstone-grpc
SDK 导入 'Client' 类。Client 类的构造函数接受两个必需的参数,首先是 Shyft gRPC 端点,其次是 gRPC 访问Token,这两个参数已经在上一步中获得。
可以按以下方式初始化 gRPC 客户端:
import Client from "@triton-one/yellowstone-grpc";
const client = new Client(
"<https://grpc.us1.shyft.to>", //Your Region specific Shyft gRPC URL
"hbdj-asjnf-access-token-asdh", //Shyft gRPC Access Token
undefined,
);
初始化客户端后,我们现在可以调用任何客户端方法。请注意,客户端以异步方式运行,因此所有调用都应在异步块或异步函数中执行。
从 gRPC 接收 实时更新 的关键是通过 订阅流。Shyft gRPC 允许你使用 subscribe()
方法订阅特定的数据流。此方法返回一个流对象,该对象会在链上发生更新时立即发出更新。
const stream = await client.subscribe();
创建订阅流后,我们可以向 Shyft 的 gRPC 接口 发送 订阅请求,以便接收流式传输到我们后端服务器的特定更新。Shyft 的 gRPC 服务可以提供各种更新,包括账户更改、交易、新区块,甚至 slot 更新。但为了避免信息过载,你需要指定你实际需要的内容,为此有 不同类型的订阅请求,每种请求都有自己的一组参数。gRPC 上的订阅请求如下所示。
import { CommitmentLevel } from "@triton-one/yellowstone-grpc";
const req: SubscribeRequest = {
accounts: {},
slots: {},
transactions: {},
transactionsStatus: {},
entry: {},
blocks: {},
blocksMeta: {},
accountsDataSlice: [],
ping: undefined,
commitment: CommitmentLevel.CONFIRMED,
};
大多数请求参数都是不言自明的,并且与它们的名称完全一致,
这是一个用于接收交易的示例订阅请求:
const req = {
accounts: {},
slots: {},
transactions: {
pumpFun: {
vote: false,
failed: false,
signature: undefined,
accountInclude: [PUMP_FUN_PROGRAM_ID.toBase58()], //Address 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P
accountExclude: [],
accountRequired: [],
},
},
transactionsStatus: {},
entry: {},
blocks: {},
blocksMeta: {},
accountsDataSlice: [],
ping: undefined,
commitment: CommitmentLevel.CONFIRMED, //for receiving confirmed txn updates
};
为了实时流式传输交易,我们使用 transactions
参数。你还可以使用 pumpFun
设置自定义标记(可以将其视为数据流的昵称)。vote
和 failed
字段允许你选择是仅查看成功的交易 ( vote = true
) 还是包括失败的交易 ( failed = true
)。accountInclude
参数接受一组账户(任何 solana 地址),并向你发送提及账户参与的交易,在我们的例子中是程序地址(特定程序,例如 pump.fun)。最后,commitment
字段定义你希望交易达到的确认级别:processed
(如草稿)、confirmed
(更可靠)或 finalized
(最可靠,但可能需要更长的时间)。请记住,将所有字段留空将广播所有交易。否则,这些字段像逻辑 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(),
); //utility function
const parsedTxn = decodePumpFunTxn(txn);
//decoding the pump.fun transaction with SHYFT txn parser
if (!parsedTxn) return;
console.log(
new Date(),
":",
`New transaction <https://translator.shyft.to/tx/${txn.transaction.signatures[0]}> \\n`,
JSON.stringify(parsedTxn, null, 2) + "\\n",
); //displaying the received transaction
}
});
虽然 gRPC 流式传输提供了一种可靠的方式来接收实时数据,但意外的网络问题仍然可能会中断连接。为了确保无缝体验,你可以在你的应用程序中实现 重新连接机制。以下是一个示例机制,如果 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));
}
}
}
因此,我们已经了解了如何通过利用 Shyft 的 gRPC 服务无缝地实时流式传输 Solana 交易。以下是从 gRPC 接收到的原始交易:
{
filters: [ 'pumpFun' ],
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 用于识别投票交易,message headers 详细说明签名和签名者信息。该响应还表示它是否是 versioned transaction,并且它还为 block、ping 和 entry 提供了规定。
为了将接收到的交易转换为类似于传统 RPC 的交易,我们使用了 Shyft 创建的以下实用程序函数。
import { TransactionFormatter } from "./utils/transaction-formatter";
const TXN_FORMATTER = new TransactionFormatter();
const txn = TXN_FORMATTER.formTransactionFromJson(
data.transaction,
Date.now(),
);
此函数返回 slot、version、blocktime、meta 和 transactions,类似于传统的 Solana getTransaction RPC 调用 . 这也有助于你将流式传输的 gRPC 交易(和 gRPC 服务)集成到你现有的 Solana 应用程序中,而无需太多麻烦。
在后端接收到的原始交易需要进行解析,才能在任何应用程序中有效利用。 对于本示例,我们使用了 Shyft 的 交易解析器 来解析从 gRPC 接收到的交易。
function decodePumpFunTxn(tx: VersionedTransactionResponse) {
if (tx.meta?.err) return;
const paredIxs = PUMP_FUN_IX_PARSER.parseTransactionData(
tx.transaction.message,
tx.meta.loadedAddresses,
);
const pumpFunIxs = paredIxs.filter((ix) =>
ix.programId.equals(PUMP_FUN_PROGRAM_ID),
);
if (pumpFunIxs.length === 0) return;
const events = PUMP_FUN_EVENT_PARSER.parseEvent(tx);
const result = { instructions: pumpFunIxs, events };
bnLayoutFormatter(result);
return result;
}
要了解有关解析交易的更多信息,请参阅我们的博客 如何在 Solana 中解析原始交易。
一个特别有用的场景是 实时跟踪 Pump.fun 交换。以下是已解析的 pump.fun 交换交易的示例,
Solana 上已解析的 Pump.fun 交易
从这个解析的数据中,我们可以获得各种信息,例如 交换中涉及的代币,交换的金额 和 交换者的地址。通过实时流式传输交易,可以立即解析此信息并将其用于任何必要的应用程序中。
我们已经在 replit 上创建了两个示例项目供你参考。只需在 secrets 部分中添加你的 gRPC url 和 access token 即可试用。立即在 Shyft Discord 上申请你的访问Token!
在本文中,我们展示了如何使用 SHYFT 的 gRPC 服务 实时流式传输 Raydium v4 交易。凭借 gRPC 的速度和 SHYFT 的全球网络,设置实时数据流非常快速高效。这种经济实惠的解决方案非常适合自托管 API、运行交易机器人和构建实时数据应用程序。 获取你的 API 密钥,并立即开始流式传输!
如果你喜欢这篇关于 Solana 上 gRPC 流式传输的文章,请查看我们关于 使用 Shyft 和 Jito Bundle 构建 Telegram 交易机器人 或 跟踪实时 orca 事件 的其他文章。我们希望你在使用 SHYFT 构建 dApp 时玩得开心。
- 原文链接: blogs.shyft.to/how-to-st...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!