ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

手写 eth_call 解析 Kuru 订单簿:jev-trader book.ts 逐行剖析,比官方 SDK 少一半往返

手写 eth_call 解析 Kuru 订单簿:jev-trader book.ts 逐行剖析,比官方 SDK 少一半往返 手写 eth_call 解析 Kuru 订单簿jev-trader book.ts 逐行剖析比官方 SDK 少一半往返【免费下载链接】jev-traderOne AI trade decision every Monad block. Jev on Kuru MON-USDC.项目地址: https://gitcode.com/gh_mirrors/je/jev-traderjev-trader 是一个每 300 毫秒Monad 一个区块让 AI 做一次真实买/卖决策的链上交易机器人盯的是 Kuru 交易所上的 MON-USDC 订单簿。它的核心模块 src/book.ts 用一次手写的eth_call请求就拿到完整订单簿而官方 Kuru SDK 需要两次串行 RPC 调用——少掉的正是那半程网络往返。本文带你逐段读懂这份不到 230 行的源码。为什么手写 eth_call先看懂 300 毫秒预算 ⏱️机器人每块必须完成读订单簿 → AI 决策 → 挂单。README 给出的实测数据是一次订单簿读取约 18 ms公共 RPC整圈循环 p50 约 100 ms见 README.md 的 The 300 ms budget 一节。问题出在官方 SDK 上。Kuru SDK 的getFormattedL2OrderBook内部做了两次串行eth_callgetL2Book()—— 拿 L2 订单簿getVaultParams()—— 拿 AMM 金库参数用于合并 AMM 挂单层级但在 MON-USDC 市场上Kuru 的 AMM 金库当前vaultBidOrderSize 0第二次调用返回的是全零数据——纯粹浪费一次网络往返。于是 book.ts 的取舍是默认只发一次调用只有当金库真正上线才把两次调用合并进同一个 HTTP 请求。设计原则热路径上绝不出现可能没用的 RPC。主入口 readBook一次 HTTP 打包两个调用 readBook 是对外唯一入口逻辑可以拆成四步const l2Call ethCall(1, market, SEL_GET_L2_BOOK, tag); const json await rpcPost(url, opts.vault ? [l2Call, ethCall(2, market, SEL_GET_VAULT_PARAMS, tag)] : l2Call, opts.timeoutMs);选择器selector函数头两个常量0x46fdfbb1getL2Book()和0x88bb4f60getVaultParams()是函数签名的 Keccak 哈希前 4 字节项目里用 scripts/abi-selectors.ts 从 SDK 的 ABI 自动打印出来避免手抄错。JSON-RPC 批处理opts.vault为真时请求体是一个数组——两个eth_call走同一个 HTTP 连接只占一个往返。响应同样按id用 Map 对齐回来互不阻塞。超时rpcPost用AbortSignal.timeout给每次请求兜底L59-L68防止公共 RPC 挂起拖垮整个 300 ms 循环。这里有个小技巧provider参数可以传ethers的 JsonRpcProvider也可以直接传一个 URL 字符串urlOf负责归一化。机器人实际用的是裸 URL——热路径上完全绕开了 ethers 的 provider 层连它的重试、批处理调度都不经过。解码 ABI 字节把 hex 拆成价格档位 getL2Book()的返回值是一个 ABI 编码的bytes动态类型。解码分两层第一层剥掉 bytes 外壳。abiBytesPayload 处理动态类型的经典布局第一个 32 字节是 offset指向数据起点第二个是 length字节长度之后才是真正的内容const offset Number(BigInt(0x hex.slice(0, 64))) * 2; const length Number(BigInt(0x hex.slice(offset, offset 64))) * 2; return 0x hex.slice(offset 64, offset 64 length);第二层顺序读档位。decodeL2Book 把 payload 按固定格式切开前 32 字节块号订单簿快照来自哪个块用来对齐决策之后是买卖两侧交替的(price, size)对每档两个 32 字节价格读到 0 即结束零值哨兵价格/数量在链上是按pricePrecision、sizePrecision放大的定点数toFloatL149-L156把 bigint 还原成浮点。注意它的实现是手工拼小数串再Number()而不是 ethers 的formatUnits——这是刻意的为了和 SDK 的解析路径位级一致避免两种写法在边界值上出现 1e-16 级别的差异。AMM 金库阶梯vault 上线后怎么办 如果金库激活vaultBidOrderSize ! 0且金库地址非零地址见 vaultActive还需要把 AMM 的虚拟挂单合并进订单簿。ammLevels 是 SDK 内部getAmmPricesFromVaultParams的逐行移植以vaultBestBid / vaultBestAsk为起点按spread的 1/2000 为步长向外最多推 300 档首档数量扣掉bidPartiallyFilledSize已部分成交的部分之后每档数量按固定比例递增全部用mulDivRound四舍五入的整数乘除完成和链上/SDK 的整数运算语义一致调用侧的联动在 src/market.ts每 200 个块在热路径之外执行一次readVaultParams发现金库激活才把useVault置真之后readBook自动切到一次 HTTP、两个调用的批处理模式。和 SDK 逐位对齐formatLevels 的三个步骤 模型看到的不是原始档位而是加工后的Bookbest bid/ask、中间价、点差 bps、±1% 深度不平衡、10/25/50 bps 深度带。formatLevels 严格复刻 SDK 的三步分组求和把 L2 档位和 AMM 档位按价格合并group用 Map 保序对齐 tick买单价格向下取整到最小变动价卖单向上取整floorTick/ceilTick小数位由pricePrecision / tickSize推出两侧都按价格降序排序——注意 SDK 连卖单也是降序输出的这个反直觉细节照搬后bid/ask/mid/imbalance 才能和 SDK 完全相等buildBook 在此基础上算出spreadBps、imbalance±1% 区间内的买卖深度差/和取值 -1 到 1并做空簿保护任何一侧为空直接抛错让上层日志暴露问题而不是喂给模型脏数据。怎么证明少一半往返还不走样项目自带验证脚本 scripts/bench-read.ts跑三组对比实时交替读数手写版1 次eth_callvs SDK2 次串行落在同一区块时逐项比对报告延迟分布确定性对比喂给两个解码器同一份原始字节离线验证 bit-for-bit 相等0 次 RPC合成活跃金库构造一个数量非零的假VaultParams验证 AMM 阶梯合并逻辑与 SDK 一致运行方式公共 RPC 限速 50 req/s脚本已做限速bun run scripts/bench-read.ts小结热路径的每一毫秒都花在刀刃上 ✅做法效果裸fetch发 JSON-RPC绕开 provider 层零依赖、零调度开销默认 1 次eth_call金库激活才升级常态比 SDK 少一半往返金库激活时两调用同包批处理需要时也只花 1 个 HTTP 往返解码/取整/排序逐位复刻 SDK与官方输出可逐项断言相等对新手来说这份 book.ts 还是一份很好的 ABI 解码教材动态bytes的 offset/length 布局、零值哨兵、定点数还原、按 id 对齐的 RPC 批处理——每一段都不长但每一段都对应一个真实的坑。配合 src/trader.ts 的区块主循环和 SPEC.md 的产品设计可以看到每块一个决策这句话在工程上是如何被 300 毫秒的硬预算倒逼出来的。【免费下载链接】jev-traderOne AI trade decision every Monad block. Jev on Kuru MON-USDC.项目地址: https://gitcode.com/gh_mirrors/je/jev-trader创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表