Nonce 与 API 钱包
背景
去中心化 L1 必须防止重放攻击。当用户签署一笔 USDC 划转交易后,接收方不能将其多次广播以掏空发送者的钱包。为此,Ethereum 为每个地址存储一个 "nonce",它是一个从 0 开始的数字。每笔交易必须恰好使用 "nonce + 1" 才能被打包。
API 钱包
在文档中也称为 agent wallets。主账户可以授权 API 钱包代表主账户或其任何子账户进行签名。
请注意,API 钱包仅用于签名。要查询与主账户或子账户关联的账户数据,必须传入该账户的实际地址。一个常见的错误是使用代理钱包地址查询,这会导致结果为空。
API 钱包的清理
在以下情况下,API 钱包及其关联的 nonce 状态可能会被清理:
- 钱包被注销。当发送 ApproveAgent 操作注册新的未命名 API 钱包时,现有的未命名 API 钱包会被注销。当发送带有相同名称的 ApproveAgent 操作时,现有的已命名 API 钱包也会被注销。
- 钱包过期。
- 注册该代理的账户不再有资金。
重要提示: 对于以编程方式使用 API 钱包的用户,强烈建议不要复用其地址。一旦代理被注销,其已使用的 nonce 状态可能会被清理。为避免意外行为,请在后续使用时生成新的代理钱包。例如,一旦 nonce 集合被清理,之前签署的操作可能会被重放。
Hyperliquid 的 nonce 机制
Ethereum 的设计不适用于链上订单簿。做市策略每秒可能发送数千个订单和撤单。要求在区块链上精确排序打包会破坏任何此类策略。
在 Hyperliquid 上,每个地址存储最高的 100 个 nonce。每笔新交易的 nonce 必须大于该集合中最小的 nonce,且此前从未被使用过。nonce 按签名者跟踪:如果使用地址私钥签名,则为用户地址;如果使用 API 钱包签名,则为代理地址。
Nonce 必须在 (T - 2 days, T + 1 day) 范围内,其中 T 是交易所在区块的 unix 毫秒时间戳。
以下步骤可以帮助将自动化策略从中心化交易所迁移过来:
- 每个交易进程使用一个独立的 API 钱包。请注意,nonce 按签名者(即私钥)存储,因此由同一 API 钱包签名的不同子账户会共享该 API 钱包的 nonce 跟踪器。建议为不同的子账户使用不同的 API 钱包。
- 在每个交易进程中,设置一个任务,每 0.1 秒定期批量处理订单和撤单请求。建议将 IOC 和 GTC 订单与 ALO 订单分开批量提交,因为验证者会优先处理仅含 ALO 订单的批次。
- 交易逻辑任务将订单和撤单发送给批量处理任务。
- 对于每批订单或撤单,获取并递增一个原子计数器,确保该地址的 nonce 唯一。如有需要,原子计数器可以快进到当前的 unix 毫秒时间戳。
此结构能够容忍 2 秒内的乱序交易,对于地理位置靠近 API 服务器的自动化策略应该足够了。
对子账户和金库用户的建议
请注意,nonce 按签名者存储,即用于签署交易的私钥对应的地址。因此,建议每个交易进程或前端会话使用独立的私钥进行签名。特别是,单个 API 钱包为用户、金库或子账户签名时,都共享同一个 nonce 集合。
如果用户想并行使用多个子账户,更简单的做法是在主账户下生成两个独立的 API 钱包,每个子账户各使用一个 API 钱包。这样可以避免各子账户使用的 nonce 集合之间发生冲突。