@w3lib/evm-batch-call 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +206 -1
  3. package/dist/dividends.d.ts +135 -0
  4. package/dist/dividends.d.ts.map +1 -0
  5. package/dist/dividends.js +253 -0
  6. package/dist/dividends.js.map +1 -0
  7. package/dist/history.d.ts +154 -0
  8. package/dist/history.d.ts.map +1 -0
  9. package/dist/history.js +247 -0
  10. package/dist/history.js.map +1 -0
  11. package/dist/historySources.d.ts +32 -0
  12. package/dist/historySources.d.ts.map +1 -0
  13. package/dist/historySources.js +319 -0
  14. package/dist/historySources.js.map +1 -0
  15. package/dist/liquidity/v3.d.ts +2 -0
  16. package/dist/liquidity/v3.d.ts.map +1 -1
  17. package/dist/liquidity/v3.js +1 -1
  18. package/dist/liquidity/v3.js.map +1 -1
  19. package/dist/liquidity/v4.d.ts +21 -0
  20. package/dist/liquidity/v4.d.ts.map +1 -1
  21. package/dist/liquidity/v4.js +4 -3
  22. package/dist/liquidity/v4.js.map +1 -1
  23. package/dist/lp.d.ts +8 -0
  24. package/dist/lp.d.ts.map +1 -1
  25. package/dist/lp.js +17 -1
  26. package/dist/lp.js.map +1 -1
  27. package/dist/lpV4.d.ts +4 -0
  28. package/dist/lpV4.d.ts.map +1 -1
  29. package/dist/lpV4.js +27 -3
  30. package/dist/lpV4.js.map +1 -1
  31. package/dist/permit.d.ts +137 -0
  32. package/dist/permit.d.ts.map +1 -0
  33. package/dist/permit.js +355 -0
  34. package/dist/permit.js.map +1 -0
  35. package/dist/portfolio.d.ts +2 -0
  36. package/dist/portfolio.d.ts.map +1 -1
  37. package/dist/portfolio.js +9 -4
  38. package/dist/portfolio.js.map +1 -1
  39. package/dist/rewards/farms.d.ts +26 -0
  40. package/dist/rewards/farms.d.ts.map +1 -0
  41. package/dist/rewards/farms.js +210 -0
  42. package/dist/rewards/farms.js.map +1 -0
  43. package/dist/rewards/index.d.ts +42 -0
  44. package/dist/rewards/index.d.ts.map +1 -0
  45. package/dist/rewards/index.js +382 -0
  46. package/dist/rewards/index.js.map +1 -0
  47. package/dist/rewards/types.d.ts +170 -0
  48. package/dist/rewards/types.d.ts.map +1 -0
  49. package/dist/rewards/types.js +2 -0
  50. package/dist/rewards/types.js.map +1 -0
  51. package/dist/simulate.d.ts +186 -0
  52. package/dist/simulate.d.ts.map +1 -0
  53. package/dist/simulate.js +559 -0
  54. package/dist/simulate.js.map +1 -0
  55. package/dist/simulateDecode.d.ts +33 -0
  56. package/dist/simulateDecode.d.ts.map +1 -0
  57. package/dist/simulateDecode.js +321 -0
  58. package/dist/simulateDecode.js.map +1 -0
  59. package/dist/smartAccount/account.d.ts +31 -0
  60. package/dist/smartAccount/account.d.ts.map +1 -0
  61. package/dist/smartAccount/account.js +33 -0
  62. package/dist/smartAccount/account.js.map +1 -0
  63. package/dist/smartAccount/clients.d.ts +77 -0
  64. package/dist/smartAccount/clients.d.ts.map +1 -0
  65. package/dist/smartAccount/clients.js +122 -0
  66. package/dist/smartAccount/clients.js.map +1 -0
  67. package/dist/smartAccount/eip5792.d.ts +77 -0
  68. package/dist/smartAccount/eip5792.d.ts.map +1 -0
  69. package/dist/smartAccount/eip5792.js +152 -0
  70. package/dist/smartAccount/eip5792.js.map +1 -0
  71. package/dist/smartAccount/send.d.ts +40 -0
  72. package/dist/smartAccount/send.d.ts.map +1 -0
  73. package/dist/smartAccount/send.js +271 -0
  74. package/dist/smartAccount/send.js.map +1 -0
  75. package/dist/smartAccount/userOp.d.ts +91 -0
  76. package/dist/smartAccount/userOp.d.ts.map +1 -0
  77. package/dist/smartAccount/userOp.js +124 -0
  78. package/dist/smartAccount/userOp.js.map +1 -0
  79. package/dist/subpaths/dividends.d.ts +28 -0
  80. package/dist/subpaths/dividends.d.ts.map +1 -0
  81. package/dist/subpaths/dividends.js +29 -0
  82. package/dist/subpaths/dividends.js.map +1 -0
  83. package/dist/subpaths/history.d.ts +22 -0
  84. package/dist/subpaths/history.d.ts.map +1 -0
  85. package/dist/subpaths/history.js +23 -0
  86. package/dist/subpaths/history.js.map +1 -0
  87. package/dist/subpaths/permit.d.ts +14 -0
  88. package/dist/subpaths/permit.d.ts.map +1 -0
  89. package/dist/subpaths/permit.js +14 -0
  90. package/dist/subpaths/permit.js.map +1 -0
  91. package/dist/subpaths/price.js +2 -2
  92. package/dist/subpaths/price.js.map +1 -1
  93. package/dist/subpaths/rewards.d.ts +27 -0
  94. package/dist/subpaths/rewards.d.ts.map +1 -0
  95. package/dist/subpaths/rewards.js +25 -0
  96. package/dist/subpaths/rewards.js.map +1 -0
  97. package/dist/subpaths/simulate.d.ts +25 -0
  98. package/dist/subpaths/simulate.d.ts.map +1 -0
  99. package/dist/subpaths/simulate.js +28 -0
  100. package/dist/subpaths/simulate.js.map +1 -0
  101. package/dist/subpaths/smart-account.d.ts +24 -0
  102. package/dist/subpaths/smart-account.d.ts.map +1 -0
  103. package/dist/subpaths/smart-account.js +24 -0
  104. package/dist/subpaths/smart-account.js.map +1 -0
  105. package/dist/swap.d.ts +45 -5
  106. package/dist/swap.d.ts.map +1 -1
  107. package/dist/swap.js +108 -12
  108. package/dist/swap.js.map +1 -1
  109. package/dist/write/batch.d.ts +34 -2
  110. package/dist/write/batch.d.ts.map +1 -1
  111. package/dist/write/batch.js +22 -3
  112. package/dist/write/batch.js.map +1 -1
  113. package/dist/write/collect.d.ts +9 -0
  114. package/dist/write/collect.d.ts.map +1 -1
  115. package/dist/write/collect.js +41 -1
  116. package/dist/write/collect.js.map +1 -1
  117. package/dist/write/send.d.ts +4 -0
  118. package/dist/write/send.d.ts.map +1 -1
  119. package/dist/write/send.js.map +1 -1
  120. package/dist/write/signer.d.ts +5 -1
  121. package/dist/write/signer.d.ts.map +1 -1
  122. package/dist/write/signer.js.map +1 -1
  123. package/dist/write/transfer.d.ts +5 -3
  124. package/dist/write/transfer.d.ts.map +1 -1
  125. package/dist/write/transfer.js +12 -6
  126. package/dist/write/transfer.js.map +1 -1
  127. package/package.json +35 -1
  128. package/src/dividends.ts +360 -0
  129. package/src/history.ts +367 -0
  130. package/src/historySources.ts +360 -0
  131. package/src/liquidity/v3.ts +1 -1
  132. package/src/liquidity/v4.ts +4 -3
  133. package/src/lp.ts +18 -1
  134. package/src/lpV4.ts +32 -2
  135. package/src/permit.ts +457 -0
  136. package/src/portfolio.ts +11 -4
  137. package/src/rewards/farms.ts +229 -0
  138. package/src/rewards/index.ts +431 -0
  139. package/src/rewards/types.ts +179 -0
  140. package/src/simulate.ts +727 -0
  141. package/src/simulateDecode.ts +352 -0
  142. package/src/smartAccount/account.ts +63 -0
  143. package/src/smartAccount/clients.ts +180 -0
  144. package/src/smartAccount/eip5792.ts +187 -0
  145. package/src/smartAccount/send.ts +341 -0
  146. package/src/smartAccount/userOp.ts +180 -0
  147. package/src/subpaths/dividends.ts +47 -0
  148. package/src/subpaths/history.ts +41 -0
  149. package/src/subpaths/permit.ts +39 -0
  150. package/src/subpaths/price.ts +2 -2
  151. package/src/subpaths/rewards.ts +46 -0
  152. package/src/subpaths/simulate.ts +51 -0
  153. package/src/subpaths/smart-account.ts +40 -0
  154. package/src/swap.ts +139 -18
  155. package/src/write/batch.ts +44 -5
  156. package/src/write/collect.ts +60 -1
  157. package/src/write/send.ts +4 -0
  158. package/src/write/signer.ts +5 -1
  159. package/src/write/transfer.ts +17 -10
package/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.0
4
+
5
+ ### 新功能
6
+
7
+ - Flap 分红,子路径 `@w3lib/evm-batch-call/dividends`(只支持 BSC):`getDividends` / `dividends`(持有人在 Flap 税收代币上的余额、可领取 / 已领取数量、分红资产(WBNB 按 BNB 领取)和美元价值;`tokens` 或 `discover`(扫描收到过的 Transfer,只收 `7777` / `8888` 后缀的地址,卖光后累计的分红也能发现))、`buildClaimDividends`、`claimDividends`(每个代币一笔 `withdrawDividendsFor(owner)`,任何人可代领、款项直接到持有人;支持 `mode: '7702' | '4337' | '5792'` 原子一笔,`simulate` 默认 true,先模拟剔除会 revert 的项;发送前在链上核对 `dividendContract`;分红资产精度读不到时标 `decimalsUnknown`、不算美元价值)
8
+ - 合约来自 docs.flap.sh 和链上核实(Portal、TaxTokenHelper、每个代币的 Dividend 合约);Dividend 合约实现没有验证源码,函数靠文档 + 选择器 + 链上调用核对。已在 BSC 主网分叉上对真实持有人核实 Tax Token V3:分红资产为 ERC20 和 WBNB 两种,到账等于预览,与 `TaxTokenHelper.getDividendInfo` 一致;Tax Token V2 没有找到链上实例,识别不了的合约放进 `unsupported`,不猜函数
9
+ - 一键领取手续费和挖矿奖励,子路径 `@w3lib/evm-batch-call/rewards`:`getClaimable` / `claimable`(每个仓位能领什么:V3 / V4 手续费、PancakeSwap V3 MasterChef 的 CAKE 和质押仓位的手续费、Aerodrome / Velodrome Slipstream gauge 的奖励,数量 + 美元价值)、`buildClaimAll`(合并成尽量少的交易:V3 `multicall`、V4 一次 `modifyLiquidities`、MasterChef `multicall`,先用 `/simulate` 模拟、剔除会失败的项)、`claimAll`(签名发送,支持 `mode: '7702' | '4337' | '5792'` 原子一笔;每笔独立,`continueOnError` 默认 true)
10
+ - 质押在 MasterChef 里的仓位也能读到(NFT 在合约里):CAKE 用所有者身份预执行 `harvest` 得到真正会发的数量(`pendingCake` 偏少)
11
+ - 模拟只有合并后的那一笔失败(单项都通过)时对半拆开重新模拟;取消(`signal`)时抛出;`claimAll({ dryRun: true })` 返回 `status: 'simulated'`、`claimed: []`、预估通过的项在 `simulated`;`minUsd` 不跳过有币没价格的项;harvest 预执行分批发出,失败退回 `pendingCake` 的项标 `approximate: true`;gauge 列表缓存不再受第一个调用方的 signal / blockTag 影响
12
+ - `/lp` 新增内部函数 `lpPositionsByIds`(按 tokenId 读 V3 仓位,不看 NFT 现在在谁手里)
13
+ - 交易模拟,子路径 `@w3lib/evm-batch-call/simulate`:`getSimulation` / `simulate`(一笔)、`getBatchSimulation` / `simulateBatch`(一串依赖的交易,如 approve + swap,同一个区块里依次执行、状态延续),可以直接传 `buildSwap` / `buildRevokes` / 流动性构建的交易
14
+ - EVM 用 `eth_simulateV1`(`traceTransfers`),按日志算资产变化(ERC20 / 721 / 1155、WETH Deposit / Withdrawal、主币)和授权变化(Approval、ApprovalForAll、Permit2);不支持的节点退回 `eth_call` + `estimateGas`(只有成败 / gas / revert 原因);支持 `stateOverrides`、`watch`、`usd`(美元价值)、`risk`(收到的代币做风险检查,按需加载)
15
+ - calldata 解码(ERC20 / 721 / 1155、WETH、Uniswap V2 / V3 Router、V3 仓位合约、Universal Router、Permit2、Multicall3、Disperse,Router 按内置协议表标协议名,multicall 解出子调用;`decodeCalldata`)、revert 原因解码(Error / Panic / 常见自定义错误;`decodeRevert`)
16
+ - `warnings`:无限授权、被授权方不在 `knownSpenders()`、授权给普通地址、调用没有代码的地址、转出大部分余额、代币风险;`assertSimulation` 一行守卫
17
+ - Tron:`triggerconstantcontract`(能量、TRC20 日志),TRX 只有 `call_value`
18
+ - 历史记录,子路径 `@w3lib/evm-batch-call/history`:`getHistory` / `history` 返回统一的 `Activity`(类型 transfer / swap / approve / liquidity / mint / contract / other、方向、对方、描述、资产变化、手续费、协议 / 方法),`nextCursor` 分页(按区块,各来源通用),`types` / `before` / `after` 过滤
19
+ - 可替换、可故障切换的数据来源(`source: [a, b]`):Blockscout(免 Key,各链实例表,用其 Etherscan 兼容接口)、Etherscan v2(`apiKey`,一个 Key 覆盖多条链)、纯 RPC 日志(ERC20 / 721 / 1155 Transfer,没有主币转账)、Tron 的 TronGrid;也可以自己实现适配器(`blockscoutSource` / `etherscanSource` / `rpcSource` / `trongridSource` / `explorerSource`)
20
+ - 智能账户批量,子路径 `@w3lib/evm-batch-call/smart-account`,并接入 `/write` 的 `batchCall` / `batchTransfer`(`batchCollect`、`swap` 同样支持代付 / 原子模式)的新 `mode`:
21
+ - `'4337'`:ERC-4337(EntryPoint v0.7 / v0.8)UserOperation 的手写打包 / 哈希 / 签名;账户 `SimpleAccount`(工厂 initCode,部署前即可算出地址)和 `Simple7702Account`;bundler 客户端(`BundlerClient`)、ERC-7677 paymaster 客户端(`PaymasterClient`,也可传自定义函数);`selfBundle` 自己发 `EntryPoint.handleOps`,不需要 bundler
22
+ - `'7702'` + `sponsor`:代付 gas 的 7702,用户只签授权和 UserOperation,不需要主币
23
+ - `'5792'`:浏览器钱包 EIP-5792(`wallet_getCapabilities` / `wallet_sendCalls` / `wallet_getCallsStatus`),不支持原子批量时退回逐笔发送
24
+ - `batchCollect` 新增 `sponsor`:代付 gas 归集,子钱包不用补 gas,主币全额转走
25
+ - `WriteResult` 新增可选的 `userOpHash`、`atomic`
26
+ - 这些代码按需加载,不用时 `/write` 的体积不变
27
+ - 免 gas 授权,子路径 `@w3lib/evm-batch-call/permit`:EIP-2612 permit(含 DAI 风格、USDC v2)、Permit2 的 PermitSingle / PermitBatch / permitTransferFrom;`detectPermit`、`buildPermit*`、`signPermit`(签名前核对,签完本地验签)、`verifyPermitSignature`、calldata 编码;默认精确额度、30 分钟有效期
28
+ - `swap` / `buildSwap` 的 `approve` 增加 `'permit'` / `'permit2'` / `'auto'`(V3 路线用 `selfPermitIfNecessary` 一笔交易完成;V2 路线在原子批量里才用 permit;`'permit2'` 内置 Router 不支持,退回普通授权),`plan.approval` / `result.approval` 说明实际方式和退回原因;`swap` 新增 `mode: '7702' | '4337' | '5792'`,授权 / permit 和兑换放进同一笔原子批量
29
+ - `WriteSigner` 新增可选的 `signTypedData` / `signMessage`
30
+ - `portfolio` / `getPortfolio`(`/price`)支持 `signal`:余额、LP 的节点请求随之中止,查价格的步骤立即以 `signal.reason` 失败(之前 `getPortfolio` 的 `signal` 被丢掉,不起作用)
31
+ - `/lp` V4 扫描(`v4.scan`)先查仓位合约的 `balanceOf`:地址没有持有仓位 NFT 时不扫描(`V4ScanProgress.skipped`),公共节点上不再白扫几千万个区块
32
+
3
33
  ## 0.7.0
4
34
 
5
35
  ### 新功能
package/README.md CHANGED
@@ -284,7 +284,7 @@ await getLpPositions(user, { chainId: 1, v4: { scan: { storage } } }) //
284
284
  | --- | --- | --- |
285
285
  | **V3**(仓位 NFT) | 能 | 仓位合约可以按地址列出 NFT:`balanceOf` → `tokenOfOwnerByIndex` → `positions`,再读池子的价格和手续费增长。仓位很多的地址只核对按持有顺序最后的 `maxPositions` 个(默认 500,大致是最近的) |
286
286
  | **V2**(LP 代币) | **尽量** | 链上没有“地址持有哪些代币”的索引。默认用计价币之间的组合(WETH/USDT、WBNB/USDT…)在各工厂里找池子;`tokens` 传入更多代币(如资产列表里的)一起组合;`pools` 直接传池子地址;`source: alchemy({ apiKey })` 用代币来源列出地址持有的全部 ERC20(包括 LP 代币,见[资产列表](#资产列表))。找到的 LP 代币按 `token0` / `token1` / `factory` 识别池子和协议,工厂不在协议表里的显示为 `Unknown V2` |
287
- | **V4**(仓位 NFT) | **需要配置** | V4 的仓位合约不能按地址列出 NFT。`v4.tokenIds` 传已知的仓位;`v4.scan` 扫描仓位合约发给该地址的 NFT(`Transfer` 事件),扫到的再核对是否仍归这个地址(已转走 / 销毁的从进度里去掉)。两个都不传时不查 V4;候选太多时只核对 tokenId 最大的 `maxPositions` 个 |
287
+ | **V4**(仓位 NFT) | **需要配置** | V4 的仓位合约不能按地址列出 NFT。`v4.tokenIds` 传已知的仓位;`v4.scan` 扫描仓位合约发给该地址的 NFT(`Transfer` 事件),扫到的再核对是否仍归这个地址(已转走 / 销毁的从进度里去掉)。扫描前先查 `balanceOf`:地址一个仓位 NFT 都没有时直接跳过扫描(`onProgress` 收到 `skipped: true`)。两个都不传时不查 V4;候选太多时只核对 tokenId 最大的 `maxPositions` 个 |
288
288
 
289
289
  **V4 扫描**(`v4: { scan: true }` 或传选项):每次调用从上次的位置往后扫,进度默认在内存里,`storage: keyValueLpScanStorage(localStorage)` 可以跨页面刷新接着扫。`fromBlock`:`'deployment'`(默认,从仓位合约部署的区块开始扫全部历史;自定义协议没传 `deployBlock` 时等同于 `'latest'`)、`'latest'`(只发现之后的新仓位)或区块号。只查仓位合约的事件,单次范围默认 10000 个区块,节点有上限时自动缩小,限频 / 超时自动退避(与[增量扫描](#增量扫描可选)相同)。扫全部历史在公共节点上很慢(BSC 公共节点每次只能查几百个区块),每次调用只推进 `maxRequests` 个请求 / `timeBudget` 时间,建议用自己的节点或把进度存起来。**没扫到最新区块之前 V4 的结果不完整**(从旧往新扫,最近铸造的仓位最后才扫到):`onProgress: ({ cursor, target, done, pausedUntil }) => …` 可以拿到进度,没扫完时提示用户或再调用几次(节点不开放 getLogs 时会暂停一段时间:触发暂停的那次带 `pausedUntil`,暂停期间的调用 `target` 为 null;拿不到最新区块时 `target` 也为 null)。`confirmations`(默认 5)不要设为 0:刚出的区块可能被重组。已转走 / 销毁的仓位只在节点跟上扫描进度时才从进度里去掉,节点落后时照常返回结果、这次不清理。改 `fromBlock` 只对还没有进度的地址生效(进度按链 + 仓位合约 + 地址保存)。
290
290
 
@@ -371,6 +371,74 @@ await collectFees(multi, privateKey, { position })
371
371
  打包:在已经用到 `Provider` 的基础上,只用 `build*` 约增加 40KB(minify,gzip 约 13KB),加上发送约 77KB(gzip 约 26KB,其中大部分是 `/write` 的发送代码),均不含 ethers。
372
372
 
373
373
 
374
+ ## 领取手续费和挖矿奖励
375
+
376
+ 子路径 `@w3lib/evm-batch-call/rewards`:一键领取 V3 / V4 仓位未领取的手续费,以及质押在农场里的挖矿奖励。
377
+
378
+ - `getClaimable(owner, { chainId })` / `claimable(provider, owner)`:只读,列出每个仓位能领什么(数量按精度换算,`prices: true` 时带美元价值,按价值排序),来源出错只记进 `errors`
379
+ - `buildClaimAll(provider, owner, options)`:合并成尽量少的交易,先模拟、剔除会失败的项,只读取、不签名
380
+ - `claimAll(provider, from, options)`:构建后签名发送,`from` 与 `/write` 的 `batchCall` 相同;`mode: '7702' | '4337' | '5792'` 合成一笔原子交易
381
+
382
+ ```ts
383
+ import { claimAll, getClaimable } from '@w3lib/evm-batch-call/rewards'
384
+
385
+ const c = await getClaimable(user, { chainId: 56, prices: true })
386
+ // c.items: [{ id: 'farm:pancakeswap-v3:7682136', kind: 'farm', staked: true, amounts: [{ token: CAKE, formatted: '0.046', usd: 0.03 }], usd: 0.03 },
387
+ // { id: 'fees:pancakeswap-v3:7682136', kind: 'fees', staked: true, amounts: [token0, token1], … }, …]
388
+ // c.totals / c.usd:按代币汇总 / 合计
389
+
390
+ const { status, claimed, failed, plan } = await claimAll(multi, privateKey, { minUsd: 1 }) // 价值低于 1 美元的项跳过
391
+ await claimAll(multi, privateKey, { mode: '7702' }) // 一笔交易领完(要求钱包能本地签名)
392
+ await claimAll(multi, privateKey, { items: c.items.filter((i) => i.kind === 'fees') }) // 只领挑出来的
393
+ ```
394
+
395
+ | 来源 | 链 | 读取 | 领取 |
396
+ | --- | --- | --- | --- |
397
+ | V3 手续费(Uniswap V3、PancakeSwap V3、SunSwap V3) | 内置协议表 | 与 `/lp` 相同(仓位 NFT + 池子的手续费增长) | 仓位合约 `multicall([collect…])`,一笔合并多个仓位 |
398
+ | V4 手续费(Uniswap V4) | 内置协议表 | `v4: { tokenIds }` 或 `v4: { scan: true }`(同 `/lp`,V4 不能按地址列出 NFT) | 一次 `modifyLiquidities`:每个仓位 `DECREASE_LIQUIDITY(0)`,每个币 `TAKE` 一次 |
399
+ | PancakeSwap V3 农场(CAKE + 质押仓位的手续费) | BSC、Ethereum、Base、Arbitrum(MasterChefV3) | `balanceOf` / `tokenOfOwnerByIndex` 列出质押的仓位,以所有者身份预执行 `harvest` 得到真正会发的 CAKE(`pendingCake` 比实际少,只作备用) | MasterChef `multicall([harvest, collectTo…])` |
400
+ | Aerodrome / Velodrome Slipstream gauge | Base、Optimism | 经 Voter 枚举 gauge(缓存 1 小时,也可以传 `gauges`),`stakedValues` + `earned` | 每个仓位一笔 `getReward(tokenId)`(合约只能领一个) |
401
+
402
+ - `protocols`:只查某些协议(LP 协议 id 或挖矿协议 id;`'pancakeswap-v3'` 同时含它的农场);`fees` / `rewards`:只查手续费或只查奖励;`farms`:自己的 `FarmProtocol`;`maxPositions`:每个协议最多核对多少个仓位(默认 500)
403
+ - `recipient`:领到的代币发给哪个地址(默认 owner);gauge 的奖励只能发给 owner,传了别的地址时这些项被跳过
404
+ - `simulate`(默认 true,用 `/simulate`,按需加载):某一笔失败时逐项再试,失败的项放进 `plan.skipped`(带原因),其余照常;每笔交易带 `simulation`(资产变化就是实际会收到的数量)。原子模式下一个仓位失败会让整笔回滚,所以先剔除
405
+ - `minUsd`:低于这个美元价值的项跳过(有任何一个币没有价格的项算价值未知,不跳过);`batchSize`:一笔最多合并多少个仓位,默认 20
406
+ - `claimAll` 的 `continueOnError` 默认 true(每笔独立,不像 `batchCall` 默认遇错停);结果的 `claimed` / `failed` 是已确认 / 未确认的项
407
+
408
+ 核对:BSC 主网分叉(anvil)上,以真实质押用户的身份核对手续费与 `MasterChef.collectTo` 的预执行结果逐位一致、CAKE 与 `harvest` 一致,`claimAll` 的实际到账等于预览;再用随机钱包持有从真实持有者转来的 PancakeSwap V3 / Uniswap V3 / Uniswap V4 仓位并质押一个到 MasterChef,分别用逐笔和 `'7702'`(一笔原子交易)领取,每个代币到账数量与预览逐位一致,转走一个仓位后它被模拟剔除、其余照常。Base 分叉上以真实用户的身份领取 Aerodrome gauge 奖励(到账与预览一致,只多出几秒的累计);Base 主网(公共节点)枚举 Voter 的全部 gauge 读到质押的仓位和 AERO(带美元价值),Tron 主网读 SunSwap V3 真实用户的手续费并用 `triggerconstantcontract` 预执行领取交易。
409
+
410
+ **局限**:手续费 / 奖励随时间增加,预览是查询那一刻的数量;V4 要给 `tokenIds` 或开启扫描;Uniswap V3 的 UniswapV3Staker 激励按 `IncentiveKey` 领取、不能按用户列出,没有支持;Slipstream 的未质押仓位用了不同的 NonfungiblePositionManager(`tickSpacing` 代替 `fee`),不在 `/lp` 表里,手续费不查(质押后手续费归投票人,质押人只有奖励);V3 手续费收到的是代币本身(包装币不自动换成主币)。
411
+
412
+ ## Flap 分红
413
+
414
+ 子路径 `@w3lib/evm-batch-call/dividends`:查并领取 [Flap](https://flap.sh)(BNB Chain 发币平台)税收代币的分红。只支持 BSC(chainId 56)。
415
+
416
+ ```ts
417
+ import { claimDividends, getDividends } from '@w3lib/evm-batch-call/dividends'
418
+
419
+ const { items, unsupported } = await getDividends(user, { chainId: 56, tokens: [flapToken], discover: true })
420
+ // items: [{ token, symbol, balance, claimable: '194002826213118', claimableFormatted, claimed, asset: 'native', assetSymbol: 'BNB', usd: 0.12, accruing, dividendContract, … }]
421
+ await claimDividends(multi, privateKey, items) // 每个代币一笔
422
+ await claimDividends(multi, privateKey, items, { mode: '7702' }) // 一笔原子交易,默认先模拟、剔除会失败的项
423
+ ```
424
+
425
+ - `tokens`:要查的 Flap 代币;`discover`:从持有人收到过的 `Transfer` 事件里发现(只收地址后缀 `7777` / `8888` 的代币,再逐个核对;卖光的代币上已累计的分红仍可领取,所以不能只看当前余额)。传对象可配置 `lookbackBlocks`(默认 5000)、`fromBlock`、`progress`(结果 `scan.progress`,下次接着扫)、`blockRange`、`maxRequests`、`timeBudget`。`eth_getLogs` 不带地址时,公共 BSC 节点只允许很小的区块范围(25 / 50 个区块)或直接拒绝(publicnode 要求带地址),所以 `discover` 在公共节点上只能扫最近几百个区块;要完整发现请用自己的节点,或把已知的代币传给 `tokens`
426
+ - `prices`(默认 true):分红资产的美元价值;`includeZero`:也返回没有可领取分红的代币。一次最多检查 200 个代币
427
+ - `claimDividends`:每个代币一笔 `withdrawDividendsFor(owner)` 发给它的 Dividend 合约,`from` 与 `batchCall` 相同,`mode: '7702' | '4337' | '5792'` 合成一笔原子交易;`simulate`(默认 true,用 `/simulate`,按需加载)先模拟每一笔,会 revert 的不发;发送前还会在链上核对每项的 `dividendContract` 是代币自己的 Dividend 合约,不一致的跳过(`status: 'skipped'`,`error` 是原因);`dryRun` 只预估 gas。`buildClaimDividends(items)` 只构建、不签名
428
+ - **任何人都可以替持有人领取,款项直接到持有人地址**,所以 `from` 可以是别的钱包(如代付 gas),一批里也可以混多个持有人的项
429
+
430
+ ### 合约和核实范围
431
+
432
+ 资料来源:docs.flap.sh(Tax Token V2 / V3、Inspect A Tax Token、已部署地址)和链上调用。BSC 主网:Portal `0xe2cE6ab80874Fa9Fa2aAE65D277Dd6B8e65C9De0`、TaxTokenHelper `0x53841c73217735F37BC1775538b03b23feFD8346`、SwapRegistry `0x644A8f560138418bAD4EdEFC7c17878a3c2fBEB6`。
433
+
434
+ - 每个税收代币有自己的 Dividend 合约(EIP-1167 克隆,实现 `0xd67447C29908Cb901374b658cfD3CB261a375905`,**Sourcify / BscScan 上没有验证源码**;函数名靠官方文档、实现字节码里的函数选择器和链上调用核对)。代币的 `dividendContract()` 给出它;读 `dividendToken()`、`weth()`、`minimumShareBalance()`、`withdrawableDividends(user)`、`withdrawnDividends(user)`;领取 `withdrawDividendsFor(user)`
435
+ - 分红资产就是 Dividend 合约里的 `dividendToken()`:可以是 WBNB(领取时解包成 **BNB**,结果里 `asset: 'native'`)、代币自己、或任意 ERC20(BTCB、DOGE、FXIon 等,按 Flap 的 SwapRegistry 换成)。持有人要持有至少 `minimumShareBalance`(实测 10000 枚)才继续累计新分红(`accruing`);已累计的不受影响,卖光后仍可领取
436
+ - Flap 的触发机器人也会调 `distributeDividend` 自动给大额持有人发放,所以 `claimable` 为 0 不代表没领过(看 `claimed`)
437
+ - 只有一种合约形态,所以只有一个适配器(`adapter: 'flap'`):Tax Token V3(`TOKEN_TAXED_V3`,主网上目前绝大多数)已在主网和分叉上核实。Tax Token V2(`dividendBps > 0` 时才部署 Dividend 合约,文档说明函数相同)**没有找到链上实例核实**,能读到 `dividendContract()` 且函数齐全才会识别,否则放进 `unsupported`(带原因);V1 没有分红。不认识的合约不会猜函数
438
+ - 核对:BSC 主网分叉(anvil,Prague)上用随机钱包代领真实持有人的分红:分红资产为 ERC20(FXIon)和 WBNB(解包成 BNB)两种,持有人到账数量等于预览的 `claimable`,与官方 `TaxTokenHelper.getDividendInfo` 的 `(claimed, pending)` 一致,领完后 `claimable` 为 0、`claimed` 增加;`mode: '7702'` 把两个持有人的领取合成一笔原子交易;`simulate: true` 剔除会 revert 的项。另有连主网的读取 / 发现测试(`LIVE=1`)
439
+
440
+ **局限**:只有 BSC(Flap 在 Base、X Layer 等链上也有部署,没有核实);`claimable` 是查询那一刻的数量,之后还会增加;`usd` 依赖价格服务,没有价格时为 null;代币地址不是 `7777` / `8888` 后缀的不会被 `discover` 发现(传 `tokens` 即可);官方文档中 `MAGIC_DIVIDEND_COMPUTED` 的 LP / 篮子代币(如 Stocks Vault)作为分红资产时,领取到的是篮子代币解包后的底层资产,`assetSymbol` / `usd` 反映的是篮子代币,不一定等于实际收到的资产。
441
+
374
442
  ## 兑换
375
443
 
376
444
  子路径 `@w3lib/evm-batch-call/swap`:在内置协议表的 V2 / V3 池子里找最好的路线,报价并构建 / 发送兑换交易(Tron 上是 SunSwap V2)。
@@ -423,6 +491,60 @@ const [risk] = await getTokenRisks([token], { chainId: 56 })
423
491
 
424
492
  打包:探测合约约 3KB 字节码;在已经用到 `Provider` 的基础上 `getTokenRisks` 约增加 19KB(minify,gzip 约 8KB)。
425
493
 
494
+ ## 交易模拟
495
+
496
+ 子路径 `@w3lib/evm-batch-call/simulate`:发送前先看一笔(或一串依赖的)交易会发生什么,可以直接传 `buildSwap` / `buildRevokes` / 流动性构建的交易。
497
+
498
+ ```ts
499
+ import { getSimulation, getBatchSimulation } from '@w3lib/evm-batch-call/simulate'
500
+
501
+ const sim = await getSimulation({ to: router, data, value }, { chainId: 1, from: user, usd: true })
502
+ // { success: true, gasUsed: '152345', fee: '…',
503
+ // balanceChanges: [{ address, asset: 'native', symbol: 'ETH', delta: '-1000…', formatted: '-1', usd: -2400 }, { asset: '0xA0b8…', symbol: 'USDC', formatted: '2395.1', usd: 2395 }],
504
+ // approvalChanges: [], decoded: { type: 'swap', protocol: 'Uniswap SwapRouter02', method: 'multicall', summary: 'Swap 1 ETH for 2395.1 USDC on Uniswap SwapRouter02' },
505
+ // warnings: [], transfers: […], mode: 'simulateV1' }
506
+
507
+ const plan = await getSwapQuote(user, { tokenIn: USDT, tokenOut: NATIVE_TOKEN, amountInFormatted: '1000' }, { chainId: 1 })
508
+ const flow = await getBatchSimulation(plan, { chainId: 1, from: user }) // approve + swap 在同一个区块里依次执行,状态延续
509
+ if (!flow.success) throw new Error(flow.results[flow.failedIndex!].error)
510
+ ```
511
+
512
+ - **EVM**:`eth_simulateV1`(实测 publicnode、drpc、mainnet.base.org、bsc-dataseed 都支持,anvil 也支持;`traceTransfers` 把主币转账变成日志)。资产变化和授权变化从日志里算:ERC20 / 721 / 1155 的 Transfer、WETH 的 Deposit / Withdrawal(WETH9 没有 Transfer 事件)、Approval、ApprovalForAll、Permit2 的 Approval / Permit / Lockdown。没有用 deployless 探测合约:公共节点上日志方案够用,也不需要字节码
513
+ - 不支持 `eth_simulateV1` 的节点自动退回 `eth_call` + `eth_estimateGas`(`mode: 'eth_call'`):只有成败、gas、revert 原因,资产变化按 calldata 估(主币 value、ERC20 `transfer`),多笔交易之间状态不延续(结果里有 `no-state` 提示)。`debug_traceCall` 没有用(公共节点基本都关了)。`gas` 默认 min(1000 万, 2500 万 / 笔数):公共节点限制一次模拟的总 gas
514
+ - 选项:`from`、`watch`(另外统计资产变化的地址)、`blockTag`、`stateOverrides`(如给地址加余额)、`usd`(给变化带美元价值,用 `/price`,按需加载)、`risk`(对收到的代币做 `/risk` 检查,按需加载)、`fee`
515
+ - **calldata 解码**(不查网络):ERC20 / 721 / 1155、WETH、Uniswap V2 / V3 Router、V3 仓位合约、Universal Router(命令列表)、Permit2、Multicall3、Disperse、智能账户的 `executeBatch`;Router 的地址按内置协议表标出协议名;multicall 解出子调用;认不出的选择器是 `type: 'unknown'` 带原始选择器。`decodeCalldata` / `decodeRevert` 也可以单独用
516
+ - **revert 原因**:`Error(string)`、`Panic`(带含义)、常见自定义错误(Universal Router、Permit2、OpenZeppelin;`ExecutionFailed` 里的错误一并解开),认不出的是 `custom error 0x选择器`
517
+ - **warnings**(`{ code, severity: 'info' | 'warn' | 'danger', message, address? }`):`unlimited-approval`、`unlimited-nft-approval`、`unknown-spender`(不在 `knownSpenders()` 里)、`approve-to-eoa`(被授权方没有合约代码)、`no-code`(带 calldata 调用没有代码的地址)、`large-value`(主币转出超过余额的 90%)、`token-risk`(`risk: true`)、`unknown-method`、`reverted`
518
+ - `assertSimulation(simulate(…))`:失败或有 danger 提示时抛错,一行守卫“模拟通过才发送”
519
+ - **Tron**:`triggerconstantcontract`(能量记在 `gasUsed`、日志里的 TRC20 Transfer / Approval),TRX 的变化只有 `call_value`(内部转账查不到),没有手续费估算(用 `/fees` 的 `getCostEstimate`),每笔按当前状态单独模拟
520
+
521
+ 核对:以太坊主网用公共节点模拟 USDT 转账、revert、无限授权、WETH 存入、`buildSwap` 的 approve + swap(`usd` 价值与报价一致);在主网分叉(anvil)上先模拟、再真的发出去,USDT 和 ETH 的变化与模拟逐位一致。
522
+
523
+ ## 历史记录
524
+
525
+ 子路径 `@w3lib/evm-batch-call/history`:一个地址的转账 / 兑换 / 授权 / 流动性 / 铸造记录,统一成 `Activity`。
526
+
527
+ ```ts
528
+ import { getHistory } from '@w3lib/evm-batch-call/history'
529
+
530
+ const page = await getHistory(user, { chainId: 1, limit: 20 }) // 默认 Blockscout,失败换 RPC 日志
531
+ // { items: [{ hash, time, block, status: 'success', type: 'swap', label: 'Swapped 1 ETH for 2395 USDC on Uniswap V2 Router',
532
+ // changes: [{ asset: 'native', symbol: 'ETH', delta: '-1000…', formatted: '-1' }, …], fee: '…', protocol, method }, …],
533
+ // nextCursor: 'p:20123456', source: 'blockscout' }
534
+ const more = await getHistory(user, { chainId: 1, cursor: page.nextCursor, types: ['swap'] })
535
+ await getHistory(user, { chainId: 56, source: ['etherscan', 'rpc'], apiKey }) // Etherscan v2:一个 Key 覆盖多条链
536
+ await getHistory(tronAddress, { chainId: 728126428 }) // TronGrid
537
+ ```
538
+
539
+ - **来源(适配器)**:`'blockscout'`(免 Key,内置各链实例表 `BLOCKSCOUT_APIS`,可用 `blockscoutSource({ [chainId]: url })` 补充)、`'etherscan'`(Etherscan v2,`apiKey`)、`'rpc'`(`eth_getLogs`)、`'trongrid'`;传数组按顺序故障切换(某个来源出错 / 被限流就换下一个,全部失败抛 `HistorySourceError`,带每个来源的错误);也可以传自己实现的 `{ name, fetch(query) → RawPage }`。默认:有 Blockscout 实例的链 `[blockscout, rpc]`,其他链 `[rpc]`,传了 `apiKey` 时 etherscan 排最前;Tron 是 trongrid
540
+ - **Blockscout 用的是它的 Etherscan 兼容接口**(`/api?module=account&action=txlist | txlistinternal | tokentx | tokennfttx | token1155tx`),没用 v2 REST:数据相同,但 v2 一页 50 条带大量标签约 500KB;这个接口只有必要字段,并且与 Etherscan 系完全相同。公共实例按请求数限流(一页 3~5 个请求,被限流时自动退避重试,仍失败就换下一个来源);`nft: false` 少 2 个请求
541
+ - **分类**:`approve`(calldata 是 approve / permit / setApprovalForAll…)、`liquidity`(加 / 减流动性、领取手续费)、`mint`(收到来自零地址的 NFT / 代币)、`swap`(calldata 是兑换,或同一笔交易里不同资产一进一出)、`transfer`(其余有资产变化的、或普通转账)、`contract`、`other`;`direction` / `counterparty` / `label` 一并给出;`fee` 只在这个地址是发送者时有(最小单位的主币,Tron 是 sun);失败的交易 `status: 'failed'`、没有资产变化
542
+ - **分页**:`nextCursor` 是不透明字符串(按区块号 / Tron 的毫秒时间戳),各来源通用,可以中途换来源。`limit` 是“至少返回多少条”:按区块整块取,可能略多;`types` / `before` / `after` 过滤后不够 `limit` 会继续翻页(`maxPages`,默认 5)
543
+ - **rpc 来源的局限**:只有 ERC20 / 721 / 1155 的 Transfer 日志(按 topic 里的地址查),**没有主币转账和内部交易**(顶层 value 只在带代币转账的交易里有);按区块窗口(`rpcSource({ window, maxWindows })`,默认 2000 块 × 10)往回扫,稀疏的地址一次调用可能一条都没有,拿 `nextCursor` 继续;节点需要支持无合约地址过滤的 `eth_getLogs`(publicnode 的以太坊节点不支持,drpc / 1rpc 可以)
544
+ - **Tron**:TronGrid 的 `/v1/accounts/{address}/transactions` + `/transactions/trc20`(没有 Key 限频较严,传 `apiKey`);内部的 TRX 转账查不到
545
+
546
+ 核对:以太坊主网上 Blockscout 取一个活跃地址的两页(时间递减、分页不重复),RPC 日志来源取代币转账、识别出 Universal Router 的兑换并分页,TronGrid 取 Binance 热钱包的 TRX / USDT 转账(Blockscout 公共实例限流很严,连续跑测试会被 429 一段时间);用假的 Etherscan 响应单测了分类、分页不丢不重和故障切换。
547
+
426
548
  ## 手续费估算
427
549
 
428
550
  子路径 `@w3lib/evm-batch-call/fees`:当前的手续费建议,和一笔交易大概要付多少手续费。
@@ -499,6 +621,45 @@ await revokeApprovals(provider, key, list.filter((a) => a.type !== 'nft' && a.un
499
621
 
500
622
  打包:在已经用到 `Provider` 的基础上,`getApprovals` 约增加 11KB(minify,gzip 约 5KB),`scanApprovals` 很小(约 4KB gzip,不含 Provider);发送的代码按需加载,只查授权时不会打包。
501
623
 
624
+ ## 免 gas 授权(permit)
625
+
626
+ 子路径 `@w3lib/evm-batch-call/permit`:用签名代替 `approve` 交易。EIP-2612 permit(含 DAI 风格)、Permit2 的 `PermitSingle` / `PermitBatch`(AllowanceTransfer)和 `permitTransferFrom`(SignatureTransfer)。构建 EIP-712 数据 → 用 ethers Signer 签名(私钥 Wallet、浏览器钱包的 `JsonRpcSigner` 都行)→ 本地验证 → 生成上链 calldata。
627
+
628
+ ```ts
629
+ import { buildPermit, signPermit, permitCalldata, verifyPermitSignature, buildPermit2Single, permit2Calldata } from '@w3lib/evm-batch-call/permit'
630
+
631
+ const permit = await buildPermit(provider, { token: USDC, owner, spender: router, value: 100_000_000n }) // 精确额度、30 分钟有效
632
+ const signed = await signPermit(signer, permit) // 签名前核对,签完本地验签
633
+ verifyPermitSignature(permit, signed.signature) // true
634
+ permitCalldata(signed) // token.permit(...) 的 calldata:谁都可以提交
635
+
636
+ const p2 = await buildPermit2Single(provider, { token: USDC, owner, spender: router, amount: 100_000_000n })
637
+ permit2Calldata(await signPermit(signer, p2)) // Permit2.permit(...)
638
+ ```
639
+
640
+ - **检测**:`detectPermit(provider, token)` 读 `nonces` / `DOMAIN_SEPARATOR` / `name` / `version` / `eip712Domain`(EIP-5267),按链 ID 和地址重新算 domain,**与代币的 `DOMAIN_SEPARATOR` 逐位一致**才算支持(USDC v2 的 `version` 是 `'2'`;没有 `version()` 时试 `'1'` / `'2'`);`PERMIT_TYPEHASH` 识别 DAI 风格。不一致时返回原因,不签
641
+ - **安全默认**:精确额度(`MaxUint256` / `MaxUint160` 要 `allowUnlimited: true`;DAI 风格的 permit 只能无限额度,也要显式允许);有效期默认 30 分钟,超过 24 小时要 `allowLongDeadline: true`;数量为 0 的空白 permit、零地址 spender 直接拒绝;`signPermit` 签名前核对签名者 = owner、`chainId`、`verifyingContract`(代币本身 / Permit2)、typed data 里的 spender / 数量 / 有效期与 `permit` 摘要一致(防被替换),钱包返回的签名不是 owner 的也报错
642
+ - **Permit2 的 `permitTransferFrom`**:nonce 默认随机(无序位图,不会冲突);只有签名里的 `spender` 能调用;`permitTransferFromCalldata` 的 `requestedAmount` 不能超过签名的数量
643
+ - 合约账户(ERC-1271)的签名无法本地验证(`verifyPermitSignature` 返回 false),也不能签 EIP-2612(链上要求 EOA 签名);撤销 Permit2 额度用 `/approvals` 的 `buildRevokes`(lockdown)
644
+
645
+ **在 `swap` 里用**(`approve: 'permit' | 'permit2' | 'auto'`,实际采用的方式在 `result.approval`):
646
+
647
+ | 路线 | 能否用签名代替授权 |
648
+ | --- | --- |
649
+ | V3(Uniswap SwapRouter02 / PancakeSwap SmartRouter) | 能:`selfPermitIfNecessary` 放进同一笔 `multicall`,一次签名、一笔交易;代币要支持 EIP-2612 |
650
+ | V2 Router | Router 没有 `selfPermit`:单独发 `permit()` 交易省不了什么,所以默认退回普通授权;用 `mode: '7702' / '4337' / '5792'` 把 permit 和兑换放进同一笔原子批量时才用 permit |
651
+ | Permit2 | 内置的 V2 / V3 Router 都不读 Permit2(直接 `transferFrom`),`'permit2'` 会退回普通的精确授权,`result.approval.reason` 写明;Universal Router 暂不支持 |
652
+ | DAI 风格 / 不支持 permit 的代币 / Tron / 4337 的合约账户 | 退回普通授权,`reason` 里有原因 |
653
+
654
+ ```ts
655
+ const r = await swap(provider, signer, { tokenIn: USDC, tokenOut: NATIVE_TOKEN, amountInFormatted: '20' }, { approve: 'auto' })
656
+ r.approval // { requested: 'auto', method: 'permit', via: 'selfPermit' } 或 { method: 'approve', reason: '…' }
657
+ ```
658
+
659
+ permit 的有效期与兑换的 `deadline` 相同,额度是精确的卖出数量;用户拒绝签名时整个 `swap` 中止,不会悄悄改成发授权交易。
660
+
661
+ 主网核对(Base 分叉,anvil Prague):USDC 的 permit 提交后额度正好是签的数量、重放失败;Permit2 的 PermitSingle 链上验签通过,`permitTransferFrom` 由指定 spender 转走、超额 / 重放 / 他人调用都失败;`swap` 的 `'permit'` 在 Uniswap V3、PancakeSwap V3 上一笔交易完成(额度用完归零),V2 路线 + 7702 代付的 permit + 兑换原子完成。
662
+
502
663
  ## 价格
503
664
 
504
665
  子路径 `@w3lib/evm-batch-call/price`:代币的美元价格和地址的资产总值。
@@ -755,6 +916,50 @@ await window.tronLink.request({ method: 'tron_requestAccounts' }) // 先连接
755
916
  await batchTransfer(new Provider(window.tronWeb), window.tronWeb, recipients)
756
917
  ```
757
918
 
919
+ ## 智能账户批量(原子执行、代付 gas)
920
+
921
+ `batchCall` / `batchTransfer`(以及 `batchCollect`、`swap`)的 `mode` 增加三种,同一钱包的调用合成**一笔原子执行**的操作,gas 可以由别人付。底层积木在子路径 `@w3lib/evm-batch-call/smart-account`;这些模式的代码按需加载,不用时 `/write` 的体积不变。
922
+
923
+ | mode | 谁签什么 | 谁付 gas | 适用钱包 |
924
+ | --- | --- | --- | --- |
925
+ | `'7702'` + `sponsor` | 用户签 EIP-7702 授权(未委托时)+ UserOperation(EIP-712) | `sponsor` 钱包(用户不需要主币) | 能本地签名的钱包(私钥 / Wallet) |
926
+ | `'4337'` | owner 签 UserOperation(v0.7 用 personal_sign,v0.8 用 EIP-712,浏览器钱包也行) | 账户自己 / paymaster / 自己打包的钱包 | 任何能签名的钱包 |
927
+ | `'5792'` | 钱包弹一次确认 | 钱包决定 | 支持 EIP-5792 的浏览器钱包 |
928
+
929
+ ```ts
930
+ // 1) 代付 gas 的 7702:用户没有主币,sponsor 付 gas;一个 UserOperation 里的转账原子执行
931
+ await batchTransfer(multi, userKey, recipients, { token: USDT, mode: '7702', sponsor: sponsorKey })
932
+ await batchCollect(multi, [kid1, kid2], hot, { tokens: [USDT, NATIVE_TOKEN], sponsor: sponsorKey }) // 子钱包不用补 gas,主币全额转走
933
+
934
+ // 2) ERC-4337:owner 对应的 SimpleAccount(首次使用时部署,资产放在账户地址里)
935
+ import { getSimpleAccount } from '@w3lib/evm-batch-call/smart-account'
936
+ const { address } = await getSimpleAccount(multi, owner) // 部署前就能算出
937
+ await batchTransfer(multi, ownerKey, recipients, {
938
+ mode: '4337',
939
+ smartAccount: {
940
+ bundler: 'https://public.pimlico.io/v2/8453/rpc', // 或 BundlerClient
941
+ paymaster: new PaymasterClient(pmUrl, { sponsorshipPolicyId }), // ERC-7677;或 (op, ctx) => ({ paymaster, paymasterData, … })
942
+ // account: 'simple7702' 让 UserOperation 的 sender 就是钱包自己(EntryPoint v0.8);entryPoint: '0.7' | '0.8'
943
+ // selfBundle: key | true 不用 bundler:自己发 EntryPoint.handleOps 交易
944
+ },
945
+ })
946
+
947
+ // 3) 浏览器钱包 EIP-5792:支持原子批量时一次确认,不支持时退回逐笔发送(result.atomic 为 false)
948
+ const signer = await new BrowserProvider(window.ethereum).getSigner()
949
+ await batchCall(multi, calls.map((c) => ({ from: signer, ...c })), { mode: '5792' }) // atomicRequired: true 时不退回、直接失败
950
+ ```
951
+
952
+ - **EntryPoint v0.7 / v0.8**:UserOperation 的打包、哈希(v0.8 是 EIP-712,`userOpTypedData`;7702 账户按 EntryPoint 的规则把 initCode 换成委托合约地址)、签名都是手写的(只依赖 ethers)。账户:`'simple'`(eth-infinitism SimpleAccount,工厂地址在 `SIMPLE_ACCOUNT_FACTORY_V07/V08`)和 `'simple7702'`(`SIMPLE_7702_ACCOUNT`,只能用 v0.8)
953
+ - **代付 7702 的原理**:Simple7702Account 的 `executeBatch` 只允许账户自己或 EntryPoint 调用,sponsor 不能直接调;所以由 sponsor 发 `EntryPoint.handleOps` 交易(带用户的 7702 授权),UserOperation 的 `maxFeePerGas` 为 0,用户不付 gas,sponsor 也拿不到补偿。用户签的 UserOperation 固定了调用内容,sponsor 改不了;用户的 nonce 防重放。委托会保留,可用 `revokeDelegation` 撤销
954
+ - **bundler / paymaster**:`BundlerClient`(`eth_sendUserOperation` / `eth_estimateUserOperationGas` / `eth_getUserOperationReceipt` / `eth_supportedEntryPoints`,轮询 `waitForUserOperation`)、`PaymasterClient`(ERC-7677 的 `pm_getPaymasterStubData` / `pm_getPaymasterData`,URL 和 Key 由应用提供,报错信息不带 URL);`paymaster` 也可以是自定义函数
955
+ - **结果**:仍是 `WriteResult`,`from` 是账户(sender)地址,多了 `userOpHash` 和 `atomic`;`gasUsed` / `fee` 是 UserOperation 的 `actualGasUsed` / `actualGasCost`(账户或 paymaster 付的,代付时为 0),不含打包交易的其余部分;UserOperation 执行失败(打包交易成功)为 `reverted`,EntryPoint 拒绝(如 `AA24 signature error`)为 `failed`
956
+ - 自己打包时 gas 是本地估算(整批能执行时估整批,否则逐个调用相加 + 余量;可用 `smartAccount.gas` 覆盖);用 bundler 时由 bundler 估算
957
+ - 同一钱包的多批(超过 `batchSize`)按顺序发,上一批确认后再发下一批;`sponsor` / 自己打包用同一个钱包发时,不同子钱包也按顺序发(避免抢 nonce)
958
+
959
+ 主网核对(Base 分叉,anvil Prague):`getUserOpHash` 与 `EntryPoint.getUserOpHash` 逐位一致(v0.7、v0.8、含 7702 的 initCode 替换);未部署的 SimpleAccount(v0.7 / v0.8)首次部署 + 主币 / USDC 批量转账、一个调用 revert 时整批回滚;7702 代付(用户余额为 0)、已委托后再来一批不带授权、代付归集、签名错误被 EntryPoint 拒绝(`AA24`)。公共 bundler(Pimlico 免费端点):`eth_supportedEntryPoints`、`eth_estimateUserOperationGas` 只读调用通过。EIP-5792 用模拟钱包的单元测试覆盖(没有在真实钱包里试过)。
960
+
961
+ 局限:4337 的账户 `'simple'` 的资产在账户地址里(不是 owner 的地址),合约账户不能签 EIP-2612;7702 的授权需要本地签名,浏览器钱包只能用 `'4337'` 的 SimpleAccount 或 `'5792'`;bundler 路径中带 7702 授权(`eip7702Auth`)的调用只做了请求格式层面的核对,没有在真实 bundler 上提交;Tron 不支持这些模式。
962
+
758
963
  ## 主币余额与 deployless
759
964
 
760
965
  `getEthBalance`(以及 `balances` 里的主币)默认在两种模式下都和合约调用在同一次 `eth_call` 里:
@@ -0,0 +1,135 @@
1
+ import type { Provider } from './provider.js';
2
+ import { type LogScanProgress } from './scan.js';
3
+ import type { PriceSource } from './price.js';
4
+ import type { BatchCallOptions } from './write/batch.js';
5
+ import type { WriteResult } from './write/send.js';
6
+ import type { SignerInput } from './write/signer.js';
7
+ /** 支持的链:目前只有 BSC(Flap 在 Base、X Layer 等链上也有部署,但没有核实过,不开放) */
8
+ export declare const FLAP_DIVIDEND_CHAINS: Readonly<Record<number, {
9
+ portal: string;
10
+ taxTokenHelper: string;
11
+ swapRegistry: string;
12
+ }>>;
13
+ /** Flap 代币地址的靓号后缀(Portal 创建时用):税收代币是 7777,普通代币是 8888(实测也有 8888 的代币带分红合约);发现持仓时只收这类地址 */
14
+ export declare const FLAP_TOKEN_SUFFIXES: readonly string[];
15
+ export interface DividendItem {
16
+ /** 持有人 */
17
+ owner: string;
18
+ /** 带分红的 Flap 代币 */
19
+ token: string;
20
+ symbol: string | null;
21
+ decimals: number;
22
+ /** 持有人当前的代币余额(可以是 0:卖掉后之前累计的分红仍可领取) */
23
+ balance: string;
24
+ /** 这个代币的 Dividend 合约(领取的目标) */
25
+ dividendContract: string;
26
+ /** Dividend 合约里配置的分红代币地址(WBNB 也是这个地址) */
27
+ dividendToken: string;
28
+ /** 实际收到的资产:'native'(分红代币是 WBNB 时领取会解包成 BNB)或分红代币地址 */
29
+ asset: 'native' | string;
30
+ assetSymbol: string | null;
31
+ assetDecimals: number;
32
+ /** 分红资产的 decimals 读不到(节点出错等):assetDecimals 按 18 估,claimableFormatted 可能不准,usd 固定为 null */
33
+ decimalsUnknown?: boolean;
34
+ /** 当前可领取的数量(asset 的最小单位),领取时就是收到的数量 */
35
+ claimable: string;
36
+ claimableFormatted: string;
37
+ /** 已经领取过的累计数量 */
38
+ claimed: string;
39
+ claimedFormatted: string;
40
+ /** 可领取部分的美元价值;没开价格或没有价格时为 null */
41
+ usd: number | null;
42
+ /** 持有人要持有至少这么多代币才会继续累计新的分红(已累计的不受影响) */
43
+ minimumShareBalance: string;
44
+ /** 当前余额是否达到 minimumShareBalance(false 时新的分红不再累计,但 claimable 仍可领取) */
45
+ accruing: boolean;
46
+ /** 适配器名称 */
47
+ adapter: 'flap';
48
+ }
49
+ export interface DividendUnsupported {
50
+ token: string;
51
+ reason: string;
52
+ }
53
+ export interface DividendScanOptions {
54
+ /** 收哪些后缀的代币地址,默认 FLAP_TOKEN_SUFFIXES */
55
+ suffixes?: readonly string[];
56
+ /** 起点区块(含);不传时往回扫 lookbackBlocks 个区块 */
57
+ fromBlock?: number;
58
+ /** 不传 fromBlock 时往回扫多少个区块,默认 5000 */
59
+ lookbackBlocks?: number;
60
+ /** 上次的进度(结果的 scan.progress),传了就接着扫 */
61
+ progress?: LogScanProgress;
62
+ /** 单次 getLogs 的最大区块范围,默认 1000;节点有上限时自动缩小 */
63
+ blockRange?: number;
64
+ /** 这次最多发多少个 getLogs,默认 10 */
65
+ maxRequests?: number;
66
+ /** 这次最多花多少毫秒,默认 10000 */
67
+ timeBudget?: number;
68
+ signal?: AbortSignal;
69
+ }
70
+ export interface DividendsOptions {
71
+ /** 要检查的代币(Flap 代币地址)。可以和 discover 一起用 */
72
+ tokens?: readonly string[];
73
+ /** 从持有人收到过的 Transfer 事件里发现 Flap 代币(只收靓号后缀 7777 / 8888 的地址,再逐个核对;发现的代币里没有分红合约的不报告),true 用默认值 */
74
+ discover?: boolean | DividendScanOptions;
75
+ /** 查分红资产的美元价格:true(默认)用 DefiLlama + 链上池子,也可以传自己的 PriceSource,false 不查 */
76
+ prices?: boolean | PriceSource;
77
+ fetch?: typeof fetch;
78
+ /** 包含没有可领取分红的代币(默认 false,只返回 claimable > 0 的) */
79
+ includeZero?: boolean;
80
+ }
81
+ export interface DividendsResult {
82
+ items: DividendItem[];
83
+ /** 检查过但识别不了的代币(不是税收代币、没有分红合约、合约函数不符) */
84
+ unsupported: DividendUnsupported[];
85
+ /** discover 的扫描状态:cursor 是已扫到的区块,progress 可以存下来下次接着扫 */
86
+ scan?: {
87
+ cursor: number;
88
+ progress: LogScanProgress | null;
89
+ tokens: string[];
90
+ };
91
+ /** 查价格出错时的信息(这时 usd 都是 null) */
92
+ priceError?: string;
93
+ }
94
+ /** 发现持有人收到过的 Flap 税收代币(只发现,不核对有没有分红:把 tokens 交给 dividends) */
95
+ export declare function scanDividendTokens(provider: Provider, owner: string, options?: DividendScanOptions): Promise<{
96
+ tokens: string[];
97
+ cursor: number;
98
+ progress: LogScanProgress | null;
99
+ }>;
100
+ /**
101
+ * 查持有人在 Flap 代币上的分红:余额、可领取数量、已领取数量、分红资产和美元价值。
102
+ * 代币来自 tokens 和 discover(扫描收到过的 Transfer),每个代币都在链上核对(dividendContract() + Dividend 合约的函数),
103
+ * 核对不过的放进 unsupported。不发送交易
104
+ */
105
+ export declare function dividends(provider: Provider, owner: string, options?: DividendsOptions): Promise<DividendsResult>;
106
+ export interface ClaimDividendTx {
107
+ to: string;
108
+ data: string;
109
+ value: '0';
110
+ item: DividendItem;
111
+ }
112
+ /**
113
+ * 领取分红要发的交易(不签名):每个代币一笔 `withdrawDividendsFor(owner)`,发给它的 Dividend 合约,款项直接到 owner。
114
+ * 只包含 claimable > 0 的项。只校验 owner / dividendContract 是合法地址,不连链:items 来自调用方,
115
+ * 要确认 dividendContract 确实是代币自己的 Dividend 合约,用 claimDividends(发送前会在链上核对)
116
+ */
117
+ export declare function buildClaimDividends(items: readonly DividendItem[]): ClaimDividendTx[];
118
+ export interface ClaimDividendsOptions extends BatchCallOptions {
119
+ /** 发送前先用 /simulate 模拟每一笔(claimable 只是查询时的快照,可能已经变了),会 revert 的不发(结果 status 为 'skipped',error 是原因)。默认 true,false 不模拟;dryRun 只预估 gas */
120
+ simulate?: boolean;
121
+ }
122
+ export interface ClaimDividendResult extends WriteResult {
123
+ item: DividendItem;
124
+ }
125
+ /**
126
+ * 领取分红:构建(同 buildClaimDividends)后用 from 签名发送,钱包和选项同 batchCall
127
+ * (mode: '7702' / '4337' / '5792' 时合成一笔原子交易)。任何人都可以替持有人领取,款项直接到 item.owner,所以 from 不必是持有人(如代付 gas)。
128
+ *
129
+ * ```ts
130
+ * const { items } = await getDividends(user, { chainId: 56, discover: true })
131
+ * await claimDividends(provider, key, items, { mode: '7702' })
132
+ * ```
133
+ */
134
+ export declare function claimDividends(provider: Provider, from: SignerInput, items: readonly DividendItem[], options?: ClaimDividendsOptions): Promise<ClaimDividendResult[]>;
135
+ //# sourceMappingURL=dividends.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dividends.d.ts","sourceRoot":"","sources":["../src/dividends.ts"],"names":[],"mappings":"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"}