@w3lib/evm-batch-call 0.6.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 (248) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +367 -3
  3. package/contracts/TokenProbe.sol +103 -0
  4. package/dist/aggregate.d.ts.map +1 -1
  5. package/dist/aggregate.js +114 -3
  6. package/dist/aggregate.js.map +1 -1
  7. package/dist/approvals.d.ts +169 -0
  8. package/dist/approvals.d.ts.map +1 -0
  9. package/dist/approvals.js +310 -0
  10. package/dist/approvals.js.map +1 -0
  11. package/dist/chains.d.ts.map +1 -1
  12. package/dist/chains.js +4 -0
  13. package/dist/chains.js.map +1 -1
  14. package/dist/dividends.d.ts +135 -0
  15. package/dist/dividends.d.ts.map +1 -0
  16. package/dist/dividends.js +253 -0
  17. package/dist/dividends.js.map +1 -0
  18. package/dist/fees.d.ts +97 -0
  19. package/dist/fees.d.ts.map +1 -0
  20. package/dist/fees.js +115 -0
  21. package/dist/fees.js.map +1 -0
  22. package/dist/history.d.ts +154 -0
  23. package/dist/history.d.ts.map +1 -0
  24. package/dist/history.js +247 -0
  25. package/dist/history.js.map +1 -0
  26. package/dist/historySources.d.ts +32 -0
  27. package/dist/historySources.d.ts.map +1 -0
  28. package/dist/historySources.js +319 -0
  29. package/dist/historySources.js.map +1 -0
  30. package/dist/http.d.ts +10 -0
  31. package/dist/http.d.ts.map +1 -0
  32. package/dist/http.js +30 -0
  33. package/dist/http.js.map +1 -0
  34. package/dist/liquidity/v3.d.ts +2 -0
  35. package/dist/liquidity/v3.d.ts.map +1 -1
  36. package/dist/liquidity/v3.js +1 -1
  37. package/dist/liquidity/v3.js.map +1 -1
  38. package/dist/liquidity/v4.d.ts +21 -0
  39. package/dist/liquidity/v4.d.ts.map +1 -1
  40. package/dist/liquidity/v4.js +4 -3
  41. package/dist/liquidity/v4.js.map +1 -1
  42. package/dist/lp.d.ts +10 -1
  43. package/dist/lp.d.ts.map +1 -1
  44. package/dist/lp.js +19 -2
  45. package/dist/lp.js.map +1 -1
  46. package/dist/lpV4.d.ts +4 -0
  47. package/dist/lpV4.d.ts.map +1 -1
  48. package/dist/lpV4.js +27 -3
  49. package/dist/lpV4.js.map +1 -1
  50. package/dist/multicall.d.ts.map +1 -1
  51. package/dist/multicall.js +3 -0
  52. package/dist/multicall.js.map +1 -1
  53. package/dist/names.d.ts +55 -0
  54. package/dist/names.d.ts.map +1 -0
  55. package/dist/names.js +93 -0
  56. package/dist/names.js.map +1 -0
  57. package/dist/owner.d.ts +4 -26
  58. package/dist/owner.d.ts.map +1 -1
  59. package/dist/owner.js +7 -112
  60. package/dist/owner.js.map +1 -1
  61. package/dist/permit.d.ts +137 -0
  62. package/dist/permit.d.ts.map +1 -0
  63. package/dist/permit.js +355 -0
  64. package/dist/permit.js.map +1 -0
  65. package/dist/portfolio.d.ts +56 -0
  66. package/dist/portfolio.d.ts.map +1 -0
  67. package/dist/portfolio.js +54 -0
  68. package/dist/portfolio.js.map +1 -0
  69. package/dist/price.d.ts +95 -0
  70. package/dist/price.d.ts.map +1 -0
  71. package/dist/price.js +329 -0
  72. package/dist/price.js.map +1 -0
  73. package/dist/rewards/farms.d.ts +26 -0
  74. package/dist/rewards/farms.d.ts.map +1 -0
  75. package/dist/rewards/farms.js +210 -0
  76. package/dist/rewards/farms.js.map +1 -0
  77. package/dist/rewards/index.d.ts +42 -0
  78. package/dist/rewards/index.d.ts.map +1 -0
  79. package/dist/rewards/index.js +382 -0
  80. package/dist/rewards/index.js.map +1 -0
  81. package/dist/rewards/types.d.ts +170 -0
  82. package/dist/rewards/types.d.ts.map +1 -0
  83. package/dist/rewards/types.js +2 -0
  84. package/dist/rewards/types.js.map +1 -0
  85. package/dist/risk.d.ts +42 -0
  86. package/dist/risk.d.ts.map +1 -0
  87. package/dist/risk.js +134 -0
  88. package/dist/risk.js.map +1 -0
  89. package/dist/rpcNodes.d.ts.map +1 -1
  90. package/dist/rpcNodes.js +18 -0
  91. package/dist/rpcNodes.js.map +1 -1
  92. package/dist/simulate.d.ts +186 -0
  93. package/dist/simulate.d.ts.map +1 -0
  94. package/dist/simulate.js +559 -0
  95. package/dist/simulate.js.map +1 -0
  96. package/dist/simulateDecode.d.ts +33 -0
  97. package/dist/simulateDecode.d.ts.map +1 -0
  98. package/dist/simulateDecode.js +321 -0
  99. package/dist/simulateDecode.js.map +1 -0
  100. package/dist/smartAccount/account.d.ts +31 -0
  101. package/dist/smartAccount/account.d.ts.map +1 -0
  102. package/dist/smartAccount/account.js +33 -0
  103. package/dist/smartAccount/account.js.map +1 -0
  104. package/dist/smartAccount/clients.d.ts +77 -0
  105. package/dist/smartAccount/clients.d.ts.map +1 -0
  106. package/dist/smartAccount/clients.js +122 -0
  107. package/dist/smartAccount/clients.js.map +1 -0
  108. package/dist/smartAccount/eip5792.d.ts +77 -0
  109. package/dist/smartAccount/eip5792.d.ts.map +1 -0
  110. package/dist/smartAccount/eip5792.js +152 -0
  111. package/dist/smartAccount/eip5792.js.map +1 -0
  112. package/dist/smartAccount/send.d.ts +40 -0
  113. package/dist/smartAccount/send.d.ts.map +1 -0
  114. package/dist/smartAccount/send.js +271 -0
  115. package/dist/smartAccount/send.js.map +1 -0
  116. package/dist/smartAccount/userOp.d.ts +91 -0
  117. package/dist/smartAccount/userOp.d.ts.map +1 -0
  118. package/dist/smartAccount/userOp.js +124 -0
  119. package/dist/smartAccount/userOp.js.map +1 -0
  120. package/dist/subpaths/approvals.d.ts +26 -0
  121. package/dist/subpaths/approvals.d.ts.map +1 -0
  122. package/dist/subpaths/approvals.js +27 -0
  123. package/dist/subpaths/approvals.js.map +1 -0
  124. package/dist/subpaths/dividends.d.ts +28 -0
  125. package/dist/subpaths/dividends.d.ts.map +1 -0
  126. package/dist/subpaths/dividends.js +29 -0
  127. package/dist/subpaths/dividends.js.map +1 -0
  128. package/dist/subpaths/fees.d.ts +23 -0
  129. package/dist/subpaths/fees.d.ts.map +1 -0
  130. package/dist/subpaths/fees.js +25 -0
  131. package/dist/subpaths/fees.js.map +1 -0
  132. package/dist/subpaths/history.d.ts +22 -0
  133. package/dist/subpaths/history.d.ts.map +1 -0
  134. package/dist/subpaths/history.js +23 -0
  135. package/dist/subpaths/history.js.map +1 -0
  136. package/dist/subpaths/names.d.ts +35 -0
  137. package/dist/subpaths/names.d.ts.map +1 -0
  138. package/dist/subpaths/names.js +39 -0
  139. package/dist/subpaths/names.js.map +1 -0
  140. package/dist/subpaths/permit.d.ts +14 -0
  141. package/dist/subpaths/permit.d.ts.map +1 -0
  142. package/dist/subpaths/permit.js +14 -0
  143. package/dist/subpaths/permit.js.map +1 -0
  144. package/dist/subpaths/price.d.ts +38 -0
  145. package/dist/subpaths/price.d.ts.map +1 -0
  146. package/dist/subpaths/price.js +40 -0
  147. package/dist/subpaths/price.js.map +1 -0
  148. package/dist/subpaths/rewards.d.ts +27 -0
  149. package/dist/subpaths/rewards.d.ts.map +1 -0
  150. package/dist/subpaths/rewards.js +25 -0
  151. package/dist/subpaths/rewards.js.map +1 -0
  152. package/dist/subpaths/risk.d.ts +19 -0
  153. package/dist/subpaths/risk.d.ts.map +1 -0
  154. package/dist/subpaths/risk.js +20 -0
  155. package/dist/subpaths/risk.js.map +1 -0
  156. package/dist/subpaths/simulate.d.ts +25 -0
  157. package/dist/subpaths/simulate.d.ts.map +1 -0
  158. package/dist/subpaths/simulate.js +28 -0
  159. package/dist/subpaths/simulate.js.map +1 -0
  160. package/dist/subpaths/smart-account.d.ts +24 -0
  161. package/dist/subpaths/smart-account.d.ts.map +1 -0
  162. package/dist/subpaths/smart-account.js +24 -0
  163. package/dist/subpaths/smart-account.js.map +1 -0
  164. package/dist/subpaths/swap.d.ts +19 -0
  165. package/dist/subpaths/swap.d.ts.map +1 -0
  166. package/dist/subpaths/swap.js +20 -0
  167. package/dist/subpaths/swap.js.map +1 -0
  168. package/dist/swap.d.ts +168 -0
  169. package/dist/swap.d.ts.map +1 -0
  170. package/dist/swap.js +387 -0
  171. package/dist/swap.js.map +1 -0
  172. package/dist/tokenProbe.d.ts +3 -0
  173. package/dist/tokenProbe.d.ts.map +1 -0
  174. package/dist/tokenProbe.js +4 -0
  175. package/dist/tokenProbe.js.map +1 -0
  176. package/dist/write/batch.d.ts +34 -2
  177. package/dist/write/batch.d.ts.map +1 -1
  178. package/dist/write/batch.js +22 -3
  179. package/dist/write/batch.js.map +1 -1
  180. package/dist/write/collect.d.ts +9 -0
  181. package/dist/write/collect.d.ts.map +1 -1
  182. package/dist/write/collect.js +41 -1
  183. package/dist/write/collect.js.map +1 -1
  184. package/dist/write/send.d.ts +4 -0
  185. package/dist/write/send.d.ts.map +1 -1
  186. package/dist/write/send.js.map +1 -1
  187. package/dist/write/signer.d.ts +5 -1
  188. package/dist/write/signer.d.ts.map +1 -1
  189. package/dist/write/signer.js.map +1 -1
  190. package/dist/write/transfer.d.ts +5 -3
  191. package/dist/write/transfer.d.ts.map +1 -1
  192. package/dist/write/transfer.js +12 -6
  193. package/dist/write/transfer.js.map +1 -1
  194. package/dist/write/tron.d.ts +25 -0
  195. package/dist/write/tron.d.ts.map +1 -1
  196. package/dist/write/tron.js +26 -0
  197. package/dist/write/tron.js.map +1 -1
  198. package/package.json +73 -16
  199. package/src/aggregate.ts +110 -3
  200. package/src/approvals.ts +462 -0
  201. package/src/chains.ts +4 -0
  202. package/src/dividends.ts +360 -0
  203. package/src/fees.ts +201 -0
  204. package/src/history.ts +367 -0
  205. package/src/historySources.ts +360 -0
  206. package/src/http.ts +34 -0
  207. package/src/liquidity/v3.ts +1 -1
  208. package/src/liquidity/v4.ts +4 -3
  209. package/src/lp.ts +21 -2
  210. package/src/lpV4.ts +32 -2
  211. package/src/multicall.ts +3 -0
  212. package/src/names.ts +141 -0
  213. package/src/owner.ts +7 -138
  214. package/src/permit.ts +457 -0
  215. package/src/portfolio.ts +98 -0
  216. package/src/price.ts +415 -0
  217. package/src/rewards/farms.ts +229 -0
  218. package/src/rewards/index.ts +431 -0
  219. package/src/rewards/types.ts +179 -0
  220. package/src/risk.ts +166 -0
  221. package/src/rpcNodes.ts +18 -0
  222. package/src/simulate.ts +727 -0
  223. package/src/simulateDecode.ts +352 -0
  224. package/src/smartAccount/account.ts +63 -0
  225. package/src/smartAccount/clients.ts +180 -0
  226. package/src/smartAccount/eip5792.ts +187 -0
  227. package/src/smartAccount/send.ts +341 -0
  228. package/src/smartAccount/userOp.ts +180 -0
  229. package/src/subpaths/approvals.ts +48 -0
  230. package/src/subpaths/dividends.ts +47 -0
  231. package/src/subpaths/fees.ts +43 -0
  232. package/src/subpaths/history.ts +41 -0
  233. package/src/subpaths/names.ts +43 -0
  234. package/src/subpaths/permit.ts +39 -0
  235. package/src/subpaths/price.ts +60 -0
  236. package/src/subpaths/rewards.ts +46 -0
  237. package/src/subpaths/risk.ts +23 -0
  238. package/src/subpaths/simulate.ts +51 -0
  239. package/src/subpaths/smart-account.ts +40 -0
  240. package/src/subpaths/swap.ts +36 -0
  241. package/src/swap.ts +534 -0
  242. package/src/tokenProbe.ts +5 -0
  243. package/src/write/batch.ts +44 -5
  244. package/src/write/collect.ts +60 -1
  245. package/src/write/send.ts +4 -0
  246. package/src/write/signer.ts +5 -1
  247. package/src/write/transfer.ts +17 -10
  248. package/src/write/tron.ts +43 -0
@@ -6,6 +6,8 @@ import type { Provider } from '../provider.js'
6
6
  import { isTronChain } from '../tron.js'
7
7
  import { WriteRpc } from './rpc.js'
8
8
  import { emptyOutcome, sendJobs, toWriteResult, type SendOptions, type TronSendOptions, type TxJob, type TxOutcome, type WriteResult } from './send.js'
9
+ import type { Eip1193Like } from '../smartAccount/eip5792.js'
10
+ import type { SmartAccountOptions } from '../smartAccount/send.js'
9
11
  import { resolveSigner, type ResolvedSigner, type SignerInput } from './signer.js'
10
12
 
11
13
  /**
@@ -38,13 +40,17 @@ export interface WriteCall {
38
40
  gasLimit?: bigint
39
41
  }
40
42
 
41
- export type BatchCallMode = 'tx' | '7702'
43
+ export type BatchCallMode = 'tx' | '7702' | '4337' | '5792'
42
44
 
43
45
  export interface BatchCallOptions extends SendOptions, TronSendOptions {
44
46
  /**
45
47
  * - 'tx'(默认):每个调用一笔交易,所有链都支持
46
48
  * - '7702':钱包通过 EIP-7702 委托给批量执行合约,同一钱包的调用合成一笔交易(按 batchSize 拆分),一笔里的调用要么全部成功要么全部失败。
47
- * 需要链支持 EIP-7702,钱包需要能本地签名(私钥 / ethers Wallet)。委托会一直保留,可用 revokeDelegation 撤销
49
+ * 需要链支持 EIP-7702,钱包需要能本地签名(私钥 / ethers Wallet)。委托会一直保留,可用 revokeDelegation 撤销。
50
+ * 传 sponsor 时由 sponsor 钱包发交易并付 gas(用户只签授权和 UserOperation,不需要有主币),见 sponsor
51
+ * - '4337':ERC-4337 智能账户(smartAccount 选项:bundler / paymaster / selfBundle),一个 UserOperation 原子执行同一钱包的调用,可由 paymaster 代付 gas。
52
+ * 默认账户是 owner 对应的 SimpleAccount(首次使用时部署,资产要放在账户地址里)。子路径 /smart-account
53
+ * - '5792':浏览器钱包的 EIP-5792(wallet_sendCalls):钱包支持原子批量时一次确认原子执行,不支持时退回逐笔发送
48
54
  */
49
55
  mode?: BatchCallMode
50
56
  /**
@@ -58,6 +64,20 @@ export interface BatchCallOptions extends SendOptions, TronSendOptions {
58
64
  batchSize?: number
59
65
  /** 7702:钱包已委托给其他合约时改为委托给 delegate。默认 false(报错,不动已有的委托) */
60
66
  redelegate?: boolean
67
+ /**
68
+ * '7702' 代付 gas:用这个钱包(私钥 / Signer)发 EntryPoint.handleOps 交易,付 gas 的是它,用户钱包不需要主币。
69
+ * 用户签 EIP-7702 授权(未委托时)+ UserOperation;UserOperation 的 gas 价格为 0,sponsor 拿不到补偿,也不能改调用内容。
70
+ * 用 EntryPoint v0.8 + Simple7702Account
71
+ */
72
+ sponsor?: SignerInput
73
+ /** '4337' 的智能账户选项 */
74
+ smartAccount?: SmartAccountOptions
75
+ /** '5792':钱包的 provider(EIP-1193 / BrowserProvider)。默认用 from 的 JsonRpcSigner 背后的 provider */
76
+ eip1193?: Eip1193Like
77
+ /** '5792':要求原子执行,钱包不能原子批量时不退回逐笔发送,直接失败。默认 false */
78
+ atomicRequired?: boolean
79
+ /** '5792':传给 wallet_sendCalls 的 capabilities(如 `{ paymasterService: { url } }`) */
80
+ capabilities?: Record<string, unknown>
61
81
  }
62
82
 
63
83
  /** 归一化后的调用 */
@@ -111,7 +131,26 @@ export async function batchCall(provider: Provider, calls: readonly WriteCall[],
111
131
  }
112
132
  const rpc = await WriteRpc.from(provider)
113
133
  const normalized = await normalizeCalls(calls)
114
- return (options.mode ?? 'tx') === '7702' ? send7702(rpc, normalized, options) : sendEach(rpc, normalized, options)
134
+ return sendByMode(rpc, normalized, options)
135
+ }
136
+
137
+ /** 按 mode 发送;智能账户 / 5792 的代码按需加载,不用时 /write 的体积不变 */
138
+ export async function sendByMode(rpc: WriteRpc, calls: readonly NormalizedCall[], options: BatchCallOptions): Promise<WriteResult[]> {
139
+ const mode = options.mode ?? 'tx'
140
+ if (mode === '7702' && options.sponsor) {
141
+ const { sendSmartBatch } = await import('../smartAccount/send.js')
142
+ return sendSmartBatch(rpc, calls, { ...options, smartAccount: { ...options.smartAccount, account: 'simple7702', entryPoint: '0.8', selfBundle: options.sponsor, bundler: undefined } })
143
+ }
144
+ if (mode === '7702') return send7702(rpc, calls, options)
145
+ if (mode === '4337') {
146
+ const { sendSmartBatch } = await import('../smartAccount/send.js')
147
+ return sendSmartBatch(rpc, calls, options)
148
+ }
149
+ if (mode === '5792') {
150
+ const { send5792 } = await import('../smartAccount/eip5792.js')
151
+ return send5792(rpc, calls, options)
152
+ }
153
+ return sendEach(rpc, calls, options)
115
154
  }
116
155
 
117
156
  /** Tron 链上用 Tron 的实现(按需加载:只用 EVM 时打包不会带上) */
@@ -137,7 +176,7 @@ export async function sendEach(rpc: WriteRpc, calls: readonly NormalizedCall[],
137
176
  }
138
177
 
139
178
  /** 钱包当前的委托状态 */
140
- async function delegationOf(rpc: WriteRpc, address: string): Promise<{ delegated: boolean; to: string | null }> {
179
+ export async function delegationOf(rpc: WriteRpc, address: string): Promise<{ delegated: boolean; to: string | null }> {
141
180
  const code = (await rpc.code(address)).toLowerCase()
142
181
  if (code === '0x' || code === '') {
143
182
  return { delegated: false, to: null }
@@ -251,7 +290,7 @@ export async function send7702(rpc: WriteRpc, calls: readonly NormalizedCall[],
251
290
  }
252
291
 
253
292
  /** 解析 executeBatch 的 ExecuteError(index, error);只有一个调用时合约直接透传 revert 数据 */
254
- function failedIndex(data: string, size: number): { index: number; reason: string } | null {
293
+ export function failedIndex(data: string, size: number): { index: number; reason: string } | null {
255
294
  if (size === 1) {
256
295
  const reason = decodeRevertReason(data)
257
296
  return { index: 0, reason: reason ? `execution reverted: ${reason}` : 'execution reverted' }
@@ -6,7 +6,7 @@ import { isExecutionError } from '../errors.js'
6
6
  import { WriteRpc, failureText, l1Fee } from './rpc.js'
7
7
  import { FeeSource, applyGasMultiplier, emptyOutcome, mapPool, sendJobs, toWriteResult, type SendOptions, type TronSendOptions, type TxFees, type TxJob, type TxOutcome, type WriteResult } from './send.js'
8
8
  import { resolveSigner, type ResolvedSigner, type SignerInput } from './signer.js'
9
- import { tronModule } from './batch.js'
9
+ import { sendByMode, tronModule, type NormalizedCall } from './batch.js'
10
10
  import { batchTransfer, type TransferResult } from './transfer.js'
11
11
 
12
12
  const erc20Interface = new Interface(ERC20_ABI as unknown as string[])
@@ -31,6 +31,15 @@ export interface BatchCollectOptions extends SendOptions, TronSendOptions {
31
31
  gasBuffer?: number
32
32
  /** 各代币的最小归集数量(最小单位),余额低于它的不归集:`{ [USDT]: 10n ** 6n }` */
33
33
  minAmount?: Readonly<Record<string, BigNumberish>>
34
+ /**
35
+ * 代付 gas 归集:用这个钱包发交易(EIP-7702 + EntryPoint v0.8,见 BatchCallOptions.sponsor),子钱包不需要主币、不用补 gas,
36
+ * 每个子钱包的所有代币一个 UserOperation 原子归集;包含主币时全部主币一起转走(没有手续费要留)。
37
+ * 子钱包会被委托给 delegate(默认 Simple7702Account,委托保留,可用 revokeDelegation 撤销)
38
+ */
39
+ sponsor?: SignerInput
40
+ /** sponsor 模式:委托的合约、是否替换已有委托,见 BatchCallOptions */
41
+ delegate?: string
42
+ redelegate?: boolean
34
43
  }
35
44
 
36
45
  /** 单个钱包单个代币的归集结果;from 是子钱包,to 是归集地址 */
@@ -136,6 +145,11 @@ export async function batchCollect(provider: Provider, wallets: readonly SignerI
136
145
  }
137
146
  const active = (item: Item) => item.result.error === null
138
147
 
148
+ if (options.sponsor) {
149
+ await collectSponsored(rpc, items, target, options, balances, nativeAt, results)
150
+ return { results, topUps: [] }
151
+ }
152
+
139
153
  // 预估每个钱包的代币归集 gas,算出需要补多少主币。
140
154
  // 手续费价格只取这一次,代币归集也按它发:补 gas 等确认期间节点报价上涨,也不会超出补的数量、因余额不足被拒
141
155
  const fee = await new FeeSource(rpc, options).get()
@@ -291,6 +305,51 @@ export async function batchCollect(provider: Provider, wallets: readonly SignerI
291
305
  return { results, topUps }
292
306
  }
293
307
 
308
+ /** 代付 gas 归集:每个子钱包一批(所有代币 + 主币全额)原子转到归集地址 */
309
+ async function collectSponsored(
310
+ rpc: WriteRpc,
311
+ items: readonly Item[],
312
+ target: string,
313
+ options: BatchCollectOptions,
314
+ balances: TokenBalance[][],
315
+ nativeAt: number,
316
+ results: CollectItemResult[],
317
+ ): Promise<void> {
318
+ const list = items.filter((i) => i.result.error === null)
319
+ for (const item of list) {
320
+ if (item.native) {
321
+ // 没有手续费要留:全部主币
322
+ const balance = balances[Math.floor(item.index / options.tokens.length)]?.[nativeAt] as TokenBalance
323
+ item.amount = balance.success ? BigInt(balance.balance) : 0n
324
+ }
325
+ }
326
+ const sendable = list.filter((i) => i.amount > 0n)
327
+ list.filter((i) => i.amount === 0n).forEach((i) => (results[i.index] = toCollectResult(i, target, emptyOutcome('skipped', 'skipped: zero balance'))))
328
+ const calls: NormalizedCall[] = sendable.map((i) => ({
329
+ signer: i.signer,
330
+ to: i.native ? target : i.token,
331
+ data: i.native ? '0x' : erc20Interface.encodeFunctionData('transfer', [target, i.amount]),
332
+ value: i.native ? i.amount : 0n,
333
+ }))
334
+ const written = await sendByMode(rpc, calls, {
335
+ ...sendOptions(options),
336
+ mode: '7702',
337
+ sponsor: options.sponsor,
338
+ delegate: options.delegate,
339
+ redelegate: options.redelegate,
340
+ continueOnError: true,
341
+ onUpdate: (r) => {
342
+ const item = sendable[r.index] as Item
343
+ results[item.index] = { ...toCollectResult(item, target, emptyOutcome('skipped')), ...r, index: item.index, to: target }
344
+ options.onUpdate?.(results[item.index] as CollectItemResult)
345
+ },
346
+ })
347
+ written.forEach((r, k) => {
348
+ const item = sendable[k] as Item
349
+ results[item.index] = { ...toCollectResult(item, target, emptyOutcome('skipped')), ...r, index: item.index, to: target }
350
+ })
351
+ }
352
+
294
353
  /** 只保留发送参数(补 gas 时不带 tokens / onUpdate 等归集参数) */
295
354
  function sendOptions(options: BatchCollectOptions): SendOptions {
296
355
  const { tokens: _tokens, gasWallet: _gasWallet, gasMode: _gasMode, gasBuffer: _gasBuffer, minAmount: _minAmount, onUpdate: _onUpdate, ...rest } = options
package/src/write/send.ts CHANGED
@@ -69,6 +69,10 @@ export interface WriteResult {
69
69
  fee: string | null
70
70
  blockNumber: number | null
71
71
  error: string | null
72
+ /** 智能账户模式('4337' / sponsored '7702'):UserOperation 哈希;此时 from 是账户地址,gasUsed / fee 是 UserOperation 的 */
73
+ userOpHash?: string | null
74
+ /** 智能账户 / EIP-5792 模式:同一批的调用是否在一笔里原子执行(5792 钱包不支持而退回逐笔发送时为 false) */
75
+ atomic?: boolean
72
76
  }
73
77
 
74
78
  /** 交易层面的结果(一笔交易可能对应多个调用的结果) */
@@ -1,4 +1,4 @@
1
- import { BaseWallet, JsonRpcSigner, SigningKey, getAddress, type Authorization, type AuthorizationRequest, type TransactionRequest } from 'ethers'
1
+ import { BaseWallet, JsonRpcSigner, SigningKey, getAddress, type Authorization, type AuthorizationRequest, type TransactionRequest, type TypedDataDomain, type TypedDataField } from 'ethers'
2
2
 
3
3
  /**
4
4
  * 发交易的钱包,两种都可以:
@@ -35,6 +35,10 @@ export interface WriteSigner {
35
35
  sendTransaction?(tx: TransactionRequest): Promise<{ hash: string }>
36
36
  /** EIP-7702 授权签名(ethers Wallet 支持;'7702' 模式需要) */
37
37
  authorize?(auth: AuthorizationRequest): Promise<Authorization>
38
+ /** EIP-712 签名(Wallet / 浏览器钱包都支持):4337 v0.8 的 UserOperation、permit */
39
+ signTypedData?(domain: TypedDataDomain, types: Record<string, TypedDataField[]>, value: Record<string, unknown>): Promise<string>
40
+ /** EIP-191 个人签名:4337 v0.7 的 UserOperation */
41
+ signMessage?(message: string | Uint8Array): Promise<string>
38
42
  }
39
43
 
40
44
  /** 归一化后的钱包 */
@@ -2,7 +2,7 @@ import { Interface, getAddress, getBigInt, parseUnits, type BigNumberish } from
2
2
 
3
3
  import { ERC20_ABI, NATIVE_TOKEN, formatUnits } from '../erc20.js'
4
4
  import type { Provider } from '../provider.js'
5
- import { send7702, sendEach, tronModule, type NormalizedCall } from './batch.js'
5
+ import { sendByMode, tronModule, type BatchCallOptions, type NormalizedCall } from './batch.js'
6
6
  import { WriteRpc } from './rpc.js'
7
7
  import { emptyOutcome, sendJobs, toWriteResult, type SendOptions, type TronSendOptions, type TxJob, type TxOutcome, type WriteResult } from './send.js'
8
8
  import { resolveSigner, type ResolvedSigner, type SignerInput } from './signer.js'
@@ -34,11 +34,12 @@ export interface TransferItem {
34
34
  * - 'disperse'(默认):通过 Disperse 合约,每种代币每 batchSize 个收款人一笔交易。以下情况自动改为逐笔发送:
35
35
  * 链上没有 Disperse 合约;某种代币只有一个收款人;某一批预估 revert(收款地址拒收主币、转账收税的代币等,逐笔发送能把出问题的那一个单独标成失败)
36
36
  * - 'tx':每个收款人一笔交易
37
- * - '7702':EIP-7702 批量执行,每 batchSize 个收款人一笔(见 BatchCallOptions.mode)
37
+ * - '7702':EIP-7702 批量执行,每 batchSize 个收款人一笔(见 BatchCallOptions.mode);传 sponsor 时由 sponsor 代付 gas
38
+ * - '4337' / '5792':智能账户 UserOperation / 浏览器钱包 wallet_sendCalls,每 batchSize 个收款人一笔、原子执行(见 BatchCallOptions.mode)
38
39
  */
39
- export type TransferMode = 'disperse' | 'tx' | '7702'
40
+ export type TransferMode = 'disperse' | 'tx' | '7702' | '4337' | '5792'
40
41
 
41
- export interface BatchTransferOptions extends SendOptions, TronSendOptions {
42
+ export interface BatchTransferOptions extends SendOptions, TronSendOptions, Pick<BatchCallOptions, 'sponsor' | 'smartAccount' | 'eip1193' | 'atomicRequired' | 'capabilities'> {
42
43
  /** 默认转的代币,默认主币(NATIVE_TOKEN);每项也可以单独指定 token */
43
44
  token?: string
44
45
  mode?: TransferMode
@@ -130,9 +131,15 @@ export async function batchTransfer(provider: Provider, from: SignerInput, recip
130
131
  }
131
132
  const rpc = await WriteRpc.from(provider)
132
133
  const signer = await resolveSigner(from)
133
- const planned = await plan(provider, signer.address, recipients, options)
134
134
  const mode = options.mode ?? 'disperse'
135
- if (mode === 'tx' || mode === '7702') {
135
+ // 4337 的 SimpleAccount:资产在账户地址里,不是 owner 的地址
136
+ let holder = signer.address
137
+ if (mode === '4337' && (options.smartAccount?.account ?? 'simple') === 'simple') {
138
+ const { getSimpleAccountAddress } = await import('../smartAccount/account.js')
139
+ holder = await getSimpleAccountAddress(rpc, signer.address, options.smartAccount?.entryPoint ?? '0.7', options.smartAccount?.salt, options.smartAccount?.factory)
140
+ }
141
+ const planned = await plan(provider, holder, recipients, options)
142
+ if (mode !== 'disperse') {
136
143
  return sendDirect(rpc, signer, planned, mode, options)
137
144
  }
138
145
  return sendDisperse(rpc, provider, signer, planned, options)
@@ -203,8 +210,8 @@ function toTransferResult(p: Planned, result: WriteResult, via: TransferMode): T
203
210
  }
204
211
  }
205
212
 
206
- /** 'tx' / '7702':直接转账 */
207
- async function sendDirect(rpc: WriteRpc, signer: ResolvedSigner, planned: readonly Planned[], mode: 'tx' | '7702', options: BatchTransferOptions): Promise<TransferResult[]> {
213
+ /** 'tx' / '7702' / '4337' / '5792':直接转账 */
214
+ async function sendDirect(rpc: WriteRpc, signer: ResolvedSigner, planned: readonly Planned[], mode: Exclude<TransferMode, 'disperse'>, options: BatchTransferOptions): Promise<TransferResult[]> {
208
215
  const calls = planned.map((p) => transferCall(signer, p))
209
216
  const callOptions = {
210
217
  ...options,
@@ -212,7 +219,7 @@ async function sendDirect(rpc: WriteRpc, signer: ResolvedSigner, planned: readon
212
219
  continueOnError: true,
213
220
  onUpdate: options.onUpdate && ((r: WriteResult) => options.onUpdate?.(toTransferResult(planned[r.index] as Planned, r, mode))),
214
221
  }
215
- const results = mode === '7702' ? await send7702(rpc, calls, callOptions) : await sendEach(rpc, calls, callOptions)
222
+ const results = await sendByMode(rpc, calls, callOptions)
216
223
  return results.map((r, i) => toTransferResult(planned[i] as Planned, r, mode))
217
224
  }
218
225
 
@@ -318,7 +325,7 @@ async function sendDisperse(rpc: WriteRpc, provider: Provider, signer: ResolvedS
318
325
  if (direct.length && !options.signal?.aborted) {
319
326
  direct.sort((a, b) => a.index - b.index)
320
327
  const calls = direct.map((p) => transferCall(signer, p))
321
- await sendEach(rpc, calls, {
328
+ await sendByMode(rpc, calls, {
322
329
  ...options,
323
330
  mode: 'tx',
324
331
  continueOnError: true,
package/src/write/tron.ts CHANGED
@@ -744,6 +744,49 @@ function canonical(address: string): string {
744
744
  return toTronAddress(toEvmAddress(address))
745
745
  }
746
746
 
747
+ /** 一笔交易的手续费估算(子路径 /fees 的 estimateCost 在 Tron 上用) */
748
+ export interface TronCostEstimate {
749
+ /** 合约调用消耗的能量(模拟得到);转 TRX 为 0 */
750
+ energy: number
751
+ /** 交易的字节数(带宽) */
752
+ bandwidth: number
753
+ /** 账户剩余的能量 / 带宽不够时要烧的 TRX(sun) */
754
+ energyBurn: bigint
755
+ bandwidthBurn: bigint
756
+ /** 转 TRX 给没激活的地址:激活费(含创建账户的带宽),算在 bandwidthBurn 里 */
757
+ activates: boolean
758
+ /** 每单位能量、每字节带宽的价格(sun) */
759
+ energyPrice: bigint
760
+ bandwidthPrice: bigint
761
+ }
762
+
763
+ /**
764
+ * 估算一笔交易要烧多少 TRX:合约调用模拟出能量,按发送者剩余的质押能量 / 免费和质押带宽抵扣,不够的部分按链参数的价格烧 TRX。
765
+ * 能量按模拟值(不加余量),实际执行可能略有出入
766
+ */
767
+ export async function tronEstimateCost(provider: Provider, call: { from: string; to: string; data?: string; value?: bigint }): Promise<TronCostEstimate> {
768
+ const rpc = await tronRpc(provider, {})
769
+ const from = canonical(call.from)
770
+ const to = canonical(call.to)
771
+ const value = call.value ?? 0n
772
+ const data = call.data && call.data !== '0x' ? call.data : null
773
+ const [params, resources, isContract] = await Promise.all([rpc.params(), rpc.resources(from), data ? Promise.resolve(true) : rpc.isContract(to)])
774
+ const contract: TronContract = isContract ? { type: 'trigger', owner: from, contract: to, data: data ?? '0x', callValue: value } : { type: 'transfer', owner: from, to, amount: value }
775
+ const energy = contract.type === 'trigger' ? await rpc.simulate(contract) : 0
776
+ const feeLimit = contract.type === 'trigger' ? BigInt(Math.ceil(energy * 1.5)) * params.energyFee || 1_000_000n : 0n
777
+ const bytes = transactionBytes(contract, feeLimit)
778
+ const activates = contract.type === 'transfer' && !(await rpc.accountExists(to))
779
+ return {
780
+ energy,
781
+ bandwidth: bytes,
782
+ energyBurn: BigInt(Math.max(0, energy - resources.energy)) * params.energyFee,
783
+ bandwidthBurn: contract.type === 'transfer' ? transferBurn(resources, bytes, params, activates) : bandwidthBurn(resources, bytes, params.bandwidthFee),
784
+ activates,
785
+ energyPrice: params.energyFee,
786
+ bandwidthPrice: params.bandwidthFee,
787
+ }
788
+ }
789
+
747
790
  /** 转 TRX:普通地址用 TransferContract;合约地址用合约调用(空 calldata 带 call_value,主网禁止 TransferContract 转给合约) */
748
791
  async function trxContract(rpc: TronRpc, owner: string, to: string, amount: bigint): Promise<TronContract> {
749
792
  return (await rpc.isContract(to)) ? { type: 'trigger', owner, contract: to, data: '0x', callValue: amount } : { type: 'transfer', owner, to, amount }