Skip to main content
当您从Laserstream接收交易数据时,有两个重要的方面需要注意:
  • Message → 用户想要做什么(他们签署的提案)
  • Meta → 实际发生了什么(执行结果)
挑战: 原始交易数据以二进制字节数组形式出现,如<Buffer 00 bf a0 e8...>,而不是可读的地址和签名。 本指南将向您展示如何: 将这些二进制数据解码为人类可读的格式,提取有意义的信息,并理解从提案到执行的完整交易过程。

实时流,无需解码

运行下面的最小客户端。过滤器标志去除投票和失败的交易,accountInclude数组限制结果为涉及Jupiter程序ID的活动。
你的控制台现在显示一个包装器—filters, createdAt加上一个transaction分支,隐藏了两个子项:
  • transaction.transaction.transaction → 签名的消息
  • transaction.transaction.meta → 执行的元数据
所有看起来像Uint8Array的东西目前仍然不透明。 当您运行带有解码功能的脚本时,您将看到实际的嵌套结构和可读的地址:

解码二进制数据

为什么要解码? 原始Laserstream数据包含签名、账户密钥和作为二进制Uint8Array对象的哈希,这些不可读。你需要将这些转换为base58字符串,以便理解交易。 解决方案: Laserstream使用Yellowstone gRPC,它提供了内置的解码工具。我们使用一个递归函数将所有二进制数据转换为人类可读的格式,而不是为每种字段类型编写单独的解码器。
这种方法利用了内置的解码功能,同时处理需要手动转换的二进制字段。交易结构已经被解析 - 你只需要将二进制字段转换为人类可读的格式。

理解交易结构

现在我们可以看到解码后的数据,让我们来探索每个 Laserstream 交易更新的两个主要部分。记住在我们最初的例子中,每个交易包含两个关键对象:
  • 消息(提案)transaction.transaction.transaction → 签名的消息(用户的提案)
  • 元数据(执行)transaction.transaction.meta → 执行元数据(验证者的响应)
这种两部分结构讲述了一个完整的故事:用户请求了什么与实际发生了什么。让我们详细检查每个部分。

提案:消息中的所有内容

用户创建一个消息,指定什么以及何时为止。以下是如何解码每个部分:

交易头

numRequiredSignatures 告诉验证者要验证多少签名,而另外两个 numReadonly* 值标记运行时可以视为只读的账户,从而实现并行执行。

账户密钥字典

accountKeys是一个公共密钥列表,作为查找表。事务中的每个后续整数 - programIdIndex,每个指令中的每个元素accounts数组 - 通过索引指向此列表,从而每条消息节省超过一千字节。

防重放保护

recentBlockhash在它滚动出最后150个区块哈希时过期,大约在主网消耗九十秒。

指令:实际命令

每个指令包含三个关键部分:
  • 程序ID (programIdIndex): 指向accountKeys数组中的地址(例如,索引10 = ComputeBudget111111111111111111111111111111
  • 账户 (accounts): base58编码的字符串,表示该指令接触的账户索引
  • 数据 (data): 实际的指令数据,编码为base58
由于convertBuffers函数,账户显示为base58,但实际上包含账户索引(例如,"3vtmrQMafzDoG2CBz1iqgXPTnC"解码为索引[21, 19, 12, 17, 2, 6, 1, 22]) 这种设计意味着每个指令只需引用查找表中的位置,而不是重复完整的 32 字节地址。

签名:授权证明

signatures包含加密签名,证明所需账户授权了此交易。签名数量必须与header.numRequiredSignatures匹配。

地址表查找

如果versionedtrueaddressTableLookups会出现一个链上表和两个索引列表。查找表可以在保持数据包在1,232字节MTU以下的同时,将地址数量的硬性限制提升到几十个。

如何连接:流程

从基本原理来看,以下是发生的事情:
  1. 构建查找表accountKeys列出此交易将触及的所有地址
  2. 设置规则header指定需要多少个签名和哪些账户是只读的
  3. 创建命令:每个instruction指向:
    • 一个程序(通过programIdIndexaccountKeys[index]
    • 它需要的账户(通过accounts → 多个accountKeys[index]位置)
    • 指令数据(编码在data中)
  4. 添加授权signatures证明所需账户批准了此交易
  5. 设置过期recentBlockhash确保此交易不能在以后重放

执行:meta 中的一切

消息显示用户想要做什么,而 meta 显示验证者执行交易时实际发生的事情。

基本执行信息

成功/失败
  • err: null = 成功
  • err: {...} = 失败并带有错误详情
  • fee = 此交易收取的lamports
余额变动
余额数组根据索引对应到accountKeys数组:
  • 账户0:损失15000 lamports(手续费支付)
  • 账户1:获得1461600 lamports(新账户创建)
  • 账户3:获得2001231920 lamports(程序账户)
计算使用情况
显示使用了多少计算预算(从请求的金额中)。

高级执行细节

内部指令
内部指令是程序在执行期间调用的附加指令。它们不是原始交易的一部分,但由主要指令触发。 日志消息
日志消息提供程序执行的时间顺序跟踪,显示调用了哪些程序以及它们输出的任何自定义日志消息。 代币余额变动
代币余额变动显示 SPL 代币账户的前/后状态,包括具有适当小数处理的人类可读金额。

实用解码模式

以下是从解码交易中提取有用信息的常见模式:

完整示例:Jupiter 交换解码器

这是一个完整的示例,解码 Jupiter 交换交易并提取有意义的信息:
此示例展示了如何结合消息解码与元分析,从复杂的 DeFi 交易中提取与业务相关的信息。

关键要点

  • 双重结构:每个交易都有一个消息(请求内容)和元数据(实际发生情况)
  • 二进制解码:使用bs58.encode()将二进制字段转换为可读的base58字符串
  • 账户键查找:指令通过accountKeys数组中的索引引用账户
  • 余额追踪:比较preBalancespostBalances以查看变化
理解Solana交易的关键在于认识到它们是为效率而设计的:通过使用查找表和索引来最小化交易大小,同时最大化信息密度,而不是重复地址。