Documentation Index
Fetch the complete documentation index at: https://docs.stable.xyz/llms.txt
Use this file to discover all available pages before exploring further.
摘要
系统交易为 Stable 协议提供了一种为 Stable SDK 操作发出 EVM 事件的方式。当质押事件(如解绑完成)在 SDK 层发生时,协议会自动生成发出相应事件的 EVM 交易,使这些操作对 EVM 工具和应用程序完全可见。动机
Stable 上的 EVM 用户和应用程序期望通过标准 EVM 接口(如eth_getLogs)监控区块链事件。但关键操作发生在 Stable SDK 模块中,这些模块不会自然地发出 EVM 事件。这造成了可见性差距:EVM dapps 无法轻松跟踪用户的代币何时完成解绑。
系统交易弥合了这一差距。当质押模块完成解绑操作时,Stable 的 x/stable 模块会检测到该事件并生成一个调用 StableSystem 预编译合约(0x0000000000000000000000000000000000009999)的系统交易。然后,预编译合约会发出任何 dapp 都可以订阅的适当 EVM 事件。系统交易使用特殊的发送者地址(0x0000000000000000000000000000000000000001)运行,只有协议才能使用该地址。这可以防止任何人伪造协议事件,同时保持事件发出在链上的无需信任和可验证性。
规范
系统交易通过三个主要组件工作:x/stable 模块的 EndBlocker、PrepareProposal 处理程序和 StableSystem 预编译合约。架构概述

StableSystem 预编译合约
StableSystem 预编译合约位于0x0000000000000000000000000000000000009999,处理需要发出 EVM 事件的协议级操作。目前它支持解绑完成通知。
系统交易发送者
系统交易使用0x0000000000000000000000000000000000000001 作为发送者地址。该地址:
- 不需要签名验证
- 只能由 PrepareProposal 中创建的交易使用
- 用户或合约无法伪造
- 通过 SystemTxDecorator ante 处理程序跳过费用扣除
msg.sender == 0x1 来识别系统交易。预编译合约可以使用此功能来限制仅协议操作。
事件驱动流程
当用户的解绑期完成时,会发生以下情况:- Stable SDK 层: 质押模块的 EndBlocker 完成解绑并发出 EventTypeCompleteUnbonding,包含委托者地址、验证者地址和金额。
- 检测: x/stable 模块的 EndBlocker 在质押之后运行,并扫描区块事件日志中的解绑事件。每当有代币完成解绑,该模块都将在队列中添加一条记录,包含委托者地址、验证者地址、金额和区块高度。
- 系统交易生成:在下一个区块的 PrepareProposal 中,应用程序查询所有排队的完成。如果存在任何完成,它会创建一个调用 StableSystem.notifyUnbondingCompletions(blockHeight) 的系统交易,使用当前区块高度。此交易放在区块前面,在任何用户交易之前。
- 执行: 在区块执行期间,系统交易首先运行。预编译合约查询该区块高度排队的完成状态,为每个完成发出一个 UnbondingCompleted 事件(最多 100 个),并从队列中删除它们。
- EVM 可见性: 事件出现在交易收据和日志中,对 eth_getLogs 查询、区块浏览器和任何监控 StableSystem 预编译合约的应用程序可见。
批处理
为防止区块变得过大,系统每个区块最多处理 100 个解绑完成。如果队列中存在 150 条记录:- 区块 N:创建处理完成 0-99 的系统交易
- 区块 N+1:创建处理完成 100-149 的系统交易
使用示例
最常见的用例是需要在解绑期完成时通知用户的质押仪表板。以下是如何设置解绑完成监听器。过滤特定用户的事件
要仅接收特定委托者地址的事件,请使用索引事件参数创建过滤器:查询历史事件
如果您的 dApp 需要显示过去解绑完成的历史记录,您可以使用带有区块范围的事件过滤器查询历史事件:集成指南
步骤 1:添加 Stable System 合约接口
首先,将 StableSystem 预编译合约接口添加到您的项目中。如果您使用 Foundry 或 Hardhat,请创建一个新的接口文件:步骤 2:设置事件监听器
初始化您的 ethers.js provider 并创建指向 StableSystem 预编译合约地址的合约实例。预编译合约始终部署在 Stable 测试网和主网的0x00000000000....0000009999。
注意:预编译合约尚未部署在 Stable 主网上,将在 v1.2.0 升级后提供。
步骤 3:在应用程序逻辑中处理事件
订阅事件并相应地更新应用程序状态。常见模式包括:- 余额更新:当解绑完成时,刷新用户的代币余额
- 通知系统:在用户的解绑完成时显示 toast 通知
- 仪表板统计:实时更新质押指标和图表
- 交易历史:将已完成的解绑添加到用户的活动源
步骤 4:处理连接问题
由于事件订阅依赖于持久的 websocket 连接,因此为生产 dApp 实现重新连接逻辑:为什么采用这种方法?
与自定义索引器相比
以前,Stable SDK 要求 dApp 开发人员运行自定义索引器,监视 SDK 事件并将它们存储在数据库中。这增加了操作开销并引入了潜在的故障点。 使用系统交易,无需单独的索引器基础设施。EVM 的日志系统原生支持这类事件,每个 RPC 节点都已经索引和提供。任何标准 web3 库都可以订阅这些事件,无需额外工具。与轮询 SDK 端点相比
没有系统交易,EVM dApps 需要定期调用 Stable SDK REST 端点来检查解绑期是否已完成。这会产生几个问题:- 延迟增加:5-10 秒的轮询间隔意味着用户可能需要等待那么长时间才能看到更新
- 更高的负载:每个 dApp 实例轮询端点都会增加 RPC 基础设施的负载
- 复杂性:dApps 需要同时处理 web3 提供程序(用于 EVM 交互)和 Stable SDK REST 客户端(用于 SDK 查询)
- 无实时更新:轮询本质上无法提供即时通知
安全保证
无需信任的事件发出
系统交易在PrepareProposal ABCI 阶段创建,只有验证者才能执行。用户提交的交易无法伪造系统发送者地址(0x1),因为 EVM 的状态转换逻辑强制只有到 StableSystem 预编译合约地址的交易才能跳过签名验证。
这意味着:
- 用户无法伪造解绑完成事件
- 用户无法从自己的交易中调用
notifyUnbondingCompletions - 发出
UnbondingCompleted事件的唯一方法是在 Stable SDK 质押模块中实际完成解绑
无额外信任假设
系统交易不会引入超出区块链共识已经需要的新安全假设。如果您相信验证者正确执行区块,您就可以相信系统交易事件准确反映了 Stable SDK 状态变化。 事件发出过程是确定性的:给定EndBlock 中相同的 SDK 事件,所有诚实的验证者将在 PrepareProposal 期间产生相同的系统交易。共识机制确保验证者就包含哪些系统交易达成一致。
区块最终性
Stable 区块链通过 StableBFT 的共识机制使用快速最终性。一旦提交了一个区块,它就会立即最终化,无法重组。这意味着一旦您收到UnbondingCompleted 事件,您就可以相信它是永久的。
不需要像在概率最终性链上那样等待多个确认。dApps 可以在收到事件后立即更新用户余额并显示通知。
性能和限制
批处理大小约束
每个区块通过系统交易最多处理 100 个解绑完成。此限制存在是为了防止在解绑活动高峰期间区块大小无限制。 在实践中,假设平均区块时间为 0.7 秒,每个区块 100 个完成提供了约 9000 个完成/分钟的吞吐量。正常的质押活动很少达到此限制。在特殊情况下,完成可能会在完全处理之前排队几个区块。Gas 消耗
系统交易在执行期间消耗 gas,这在区块的 gas 限制中计算。gas 成本与正在处理的完成数量成线性比例:- 基本函数调用:约 21,000 gas
- 每个事件发出:约 3,000 gas
- 读取状态:每个完成约 2,000 gas
通知延迟
当解绑期在区块 N 期间完成时:- Stable 模块的
EndBlock在区块 N 的状态中排队完成 - 区块 N+1 的
PrepareProposal创建系统交易 - 系统交易在区块 N+1 期间执行,发出事件
高负载场景
如果解绑完成的到达速度快于每个区块 100 个,它们会在队列中累积。队列按 FIFO 顺序处理,因此最旧的完成始终首先通知。 在持续的高负载期间,队列可能会暂时增长。但是,一旦高峰消退,完成较少的后续区块将逐渐排空队列。该系统旨在处理突发而不丢失事件。未来扩展
系统交易机制为将任何 Stable SDK 操作桥接到 EVM 事件空间提供了通用模式。虽然目前仅用于解绑完成,但该架构可以扩展以涵盖其他用例:质押操作
除了解绑之外,其他质押事件可以发出 EVM 通知:- 验证者的佣金率变化
- 验证者入狱和出狱

