@w3lib/evm-batch-call 0.5.0 → 0.7.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 (160) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +240 -4
  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/fees.d.ts +97 -0
  15. package/dist/fees.d.ts.map +1 -0
  16. package/dist/fees.js +115 -0
  17. package/dist/fees.js.map +1 -0
  18. package/dist/http.d.ts +10 -0
  19. package/dist/http.d.ts.map +1 -0
  20. package/dist/http.js +30 -0
  21. package/dist/http.js.map +1 -0
  22. package/dist/liquidity/common.d.ts +72 -0
  23. package/dist/liquidity/common.d.ts.map +1 -0
  24. package/dist/liquidity/common.js +271 -0
  25. package/dist/liquidity/common.js.map +1 -0
  26. package/dist/liquidity/concentrated.d.ts +19 -0
  27. package/dist/liquidity/concentrated.d.ts.map +1 -0
  28. package/dist/liquidity/concentrated.js +106 -0
  29. package/dist/liquidity/concentrated.js.map +1 -0
  30. package/dist/liquidity/index.d.ts +42 -0
  31. package/dist/liquidity/index.d.ts.map +1 -0
  32. package/dist/liquidity/index.js +143 -0
  33. package/dist/liquidity/index.js.map +1 -0
  34. package/dist/liquidity/types.d.ts +182 -0
  35. package/dist/liquidity/types.d.ts.map +1 -0
  36. package/dist/liquidity/types.js +2 -0
  37. package/dist/liquidity/types.js.map +1 -0
  38. package/dist/liquidity/v2.d.ts +5 -0
  39. package/dist/liquidity/v2.d.ts.map +1 -0
  40. package/dist/liquidity/v2.js +342 -0
  41. package/dist/liquidity/v2.js.map +1 -0
  42. package/dist/liquidity/v3.d.ts +6 -0
  43. package/dist/liquidity/v3.d.ts.map +1 -0
  44. package/dist/liquidity/v3.js +310 -0
  45. package/dist/liquidity/v3.js.map +1 -0
  46. package/dist/liquidity/v4.d.ts +5 -0
  47. package/dist/liquidity/v4.d.ts.map +1 -0
  48. package/dist/liquidity/v4.js +279 -0
  49. package/dist/liquidity/v4.js.map +1 -0
  50. package/dist/lp.d.ts +10 -2
  51. package/dist/lp.d.ts.map +1 -1
  52. package/dist/lp.js +31 -19
  53. package/dist/lp.js.map +1 -1
  54. package/dist/lpMath.d.ts +55 -0
  55. package/dist/lpMath.d.ts.map +1 -1
  56. package/dist/lpMath.js +209 -0
  57. package/dist/lpMath.js.map +1 -1
  58. package/dist/multicall.d.ts.map +1 -1
  59. package/dist/multicall.js +3 -0
  60. package/dist/multicall.js.map +1 -1
  61. package/dist/names.d.ts +55 -0
  62. package/dist/names.d.ts.map +1 -0
  63. package/dist/names.js +93 -0
  64. package/dist/names.js.map +1 -0
  65. package/dist/owner.d.ts +4 -26
  66. package/dist/owner.d.ts.map +1 -1
  67. package/dist/owner.js +7 -112
  68. package/dist/owner.js.map +1 -1
  69. package/dist/portfolio.d.ts +54 -0
  70. package/dist/portfolio.d.ts.map +1 -0
  71. package/dist/portfolio.js +49 -0
  72. package/dist/portfolio.js.map +1 -0
  73. package/dist/price.d.ts +95 -0
  74. package/dist/price.d.ts.map +1 -0
  75. package/dist/price.js +329 -0
  76. package/dist/price.js.map +1 -0
  77. package/dist/risk.d.ts +42 -0
  78. package/dist/risk.d.ts.map +1 -0
  79. package/dist/risk.js +134 -0
  80. package/dist/risk.js.map +1 -0
  81. package/dist/rpcNodes.d.ts.map +1 -1
  82. package/dist/rpcNodes.js +18 -0
  83. package/dist/rpcNodes.js.map +1 -1
  84. package/dist/subpaths/approvals.d.ts +26 -0
  85. package/dist/subpaths/approvals.d.ts.map +1 -0
  86. package/dist/subpaths/approvals.js +27 -0
  87. package/dist/subpaths/approvals.js.map +1 -0
  88. package/dist/subpaths/fees.d.ts +23 -0
  89. package/dist/subpaths/fees.d.ts.map +1 -0
  90. package/dist/subpaths/fees.js +25 -0
  91. package/dist/subpaths/fees.js.map +1 -0
  92. package/dist/subpaths/liquidity.d.ts +24 -0
  93. package/dist/subpaths/liquidity.d.ts.map +1 -0
  94. package/dist/subpaths/liquidity.js +21 -0
  95. package/dist/subpaths/liquidity.js.map +1 -0
  96. package/dist/subpaths/lp.d.ts +1 -1
  97. package/dist/subpaths/lp.d.ts.map +1 -1
  98. package/dist/subpaths/lp.js +1 -1
  99. package/dist/subpaths/lp.js.map +1 -1
  100. package/dist/subpaths/names.d.ts +35 -0
  101. package/dist/subpaths/names.d.ts.map +1 -0
  102. package/dist/subpaths/names.js +39 -0
  103. package/dist/subpaths/names.js.map +1 -0
  104. package/dist/subpaths/price.d.ts +38 -0
  105. package/dist/subpaths/price.d.ts.map +1 -0
  106. package/dist/subpaths/price.js +40 -0
  107. package/dist/subpaths/price.js.map +1 -0
  108. package/dist/subpaths/risk.d.ts +19 -0
  109. package/dist/subpaths/risk.d.ts.map +1 -0
  110. package/dist/subpaths/risk.js +20 -0
  111. package/dist/subpaths/risk.js.map +1 -0
  112. package/dist/subpaths/swap.d.ts +19 -0
  113. package/dist/subpaths/swap.d.ts.map +1 -0
  114. package/dist/subpaths/swap.js +20 -0
  115. package/dist/subpaths/swap.js.map +1 -0
  116. package/dist/swap.d.ts +128 -0
  117. package/dist/swap.d.ts.map +1 -0
  118. package/dist/swap.js +291 -0
  119. package/dist/swap.js.map +1 -0
  120. package/dist/tokenProbe.d.ts +3 -0
  121. package/dist/tokenProbe.d.ts.map +1 -0
  122. package/dist/tokenProbe.js +4 -0
  123. package/dist/tokenProbe.js.map +1 -0
  124. package/dist/write/tron.d.ts +27 -0
  125. package/dist/write/tron.d.ts.map +1 -1
  126. package/dist/write/tron.js +30 -0
  127. package/dist/write/tron.js.map +1 -1
  128. package/package.json +32 -1
  129. package/src/aggregate.ts +110 -3
  130. package/src/approvals.ts +462 -0
  131. package/src/chains.ts +4 -0
  132. package/src/fees.ts +201 -0
  133. package/src/http.ts +34 -0
  134. package/src/liquidity/common.ts +314 -0
  135. package/src/liquidity/concentrated.ts +121 -0
  136. package/src/liquidity/index.ts +171 -0
  137. package/src/liquidity/types.ts +194 -0
  138. package/src/liquidity/v2.ts +371 -0
  139. package/src/liquidity/v3.ts +382 -0
  140. package/src/liquidity/v4.ts +348 -0
  141. package/src/lp.ts +39 -19
  142. package/src/lpMath.ts +223 -0
  143. package/src/multicall.ts +3 -0
  144. package/src/names.ts +141 -0
  145. package/src/owner.ts +7 -138
  146. package/src/portfolio.ts +91 -0
  147. package/src/price.ts +415 -0
  148. package/src/risk.ts +166 -0
  149. package/src/rpcNodes.ts +18 -0
  150. package/src/subpaths/approvals.ts +48 -0
  151. package/src/subpaths/fees.ts +43 -0
  152. package/src/subpaths/liquidity.ts +52 -0
  153. package/src/subpaths/lp.ts +20 -1
  154. package/src/subpaths/names.ts +43 -0
  155. package/src/subpaths/price.ts +60 -0
  156. package/src/subpaths/risk.ts +23 -0
  157. package/src/subpaths/swap.ts +36 -0
  158. package/src/swap.ts +413 -0
  159. package/src/tokenProbe.ts +5 -0
  160. package/src/write/tron.ts +48 -0
package/src/swap.ts ADDED
@@ -0,0 +1,413 @@
1
+ /**
2
+ * 兑换(子路径 `@w3lib/evm-batch-call/swap`):在内置协议表的 V2 / V3 池子里找路线(直接兑换,或经过包装主币 / 稳定币的两跳),
3
+ * 批量报价(V2 用 Router 的 getAmountsOut,V3 用 QuoterV2,按 gas 分成几次并发的 eth_call),选输出最多的;按滑点算最少收到的数量,
4
+ * 构建授权 + 兑换的交易(V2 走 Router 的 swapExact…SupportingFeeOnTransferTokens,V3 走 SwapRouter02 / PancakeSwap SmartRouter 的 multicall)
5
+ */
6
+ import { Interface, type BigNumberish } from 'ethers'
7
+
8
+ import { approvals, createCtx, evm, fmt, isNative, minus, out, parseAmount, same, tokenInfo, type Ctx } from './liquidity/common.js'
9
+ import type { LiquidityTxResult } from './liquidity/types.js'
10
+ import { LP_PROTOCOLS, LP_QUOTE_TOKENS, type LpProtocol, type LpToken } from './lp.js'
11
+ import type { BlockTag } from './aggregate.js'
12
+ import type { Provider } from './provider.js'
13
+ import type { BatchCallOptions } from './write/batch.js'
14
+ import type { SignerInput } from './write/signer.js'
15
+
16
+ /** V3 协议的报价合约(QuoterV2)和兑换合约(SwapRouter02 接口),按 协议 id → chainId。2026-10 在链上核对过(Router 的 WETH9 / deployer,并用 eth_call 实际执行过兑换) */
17
+ export const V3_SWAP_CONTRACTS: Readonly<Record<string, Readonly<Record<number, { quoter: string; router: string }>>>> = {
18
+ 'uniswap-v3': {
19
+ 1: { quoter: '0x61fFE014bA17989E743c5F6cB21bF9697530B21e', router: '0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45' },
20
+ 10: { quoter: '0x61fFE014bA17989E743c5F6cB21bF9697530B21e', router: '0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45' },
21
+ 56: { quoter: '0x78D78E420Da98ad378D7799bE8f4AF69033EB077', router: '0xB971eF87ede563556b2ED4b1C0b0019111Dd85d2' },
22
+ 137: { quoter: '0x61fFE014bA17989E743c5F6cB21bF9697530B21e', router: '0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45' },
23
+ 8453: { quoter: '0x3d4e44Eb1374240CE5F1B871ab261CD16335B76a', router: '0x2626664c2603336E57B271c5C0b26F421741e481' },
24
+ 42161: { quoter: '0x61fFE014bA17989E743c5F6cB21bF9697530B21e', router: '0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45' },
25
+ 43114: { quoter: '0xbe0F5544EC67e9B3b2D979aaA43f18Fd87E6257F', router: '0xbb00FF08d01D300023C629E8fFfFcb65A5a578cE' },
26
+ },
27
+ 'pancakeswap-v3': {
28
+ 1: { quoter: '0xB048Bbc1Ee6b733FFfCFb9e9CeF7375518e25997', router: '0x13f4EA83D0bd40E75C8222255bc855a974568Dd4' },
29
+ 56: { quoter: '0xB048Bbc1Ee6b733FFfCFb9e9CeF7375518e25997', router: '0x13f4EA83D0bd40E75C8222255bc855a974568Dd4' },
30
+ // Base 上的 SmartRouter 地址与 Ethereum / BSC 不同
31
+ 8453: { quoter: '0xB048Bbc1Ee6b733FFfCFb9e9CeF7375518e25997', router: '0x678Aa4bF4E210cf2166753e054d5b7c31cc7fa86' },
32
+ },
33
+ }
34
+
35
+ const V3_FEES = [100, 500, 2500, 3000, 10000] as const
36
+ /** SwapRouter02 的特殊收款地址:留在 Router 里(之后 unwrapWETH9 换成主币) */
37
+ const ADDRESS_THIS = '0x0000000000000000000000000000000000000002'
38
+
39
+ const v2RouterInterface = new Interface([
40
+ 'function getAmountsOut(uint256 amountIn, address[] path) view returns (uint256[] amounts)',
41
+ 'function swapExactTokensForTokensSupportingFeeOnTransferTokens(uint256 amountIn, uint256 amountOutMin, address[] path, address to, uint256 deadline)',
42
+ 'function swapExactETHForTokensSupportingFeeOnTransferTokens(uint256 amountOutMin, address[] path, address to, uint256 deadline) payable',
43
+ 'function swapExactTokensForETHSupportingFeeOnTransferTokens(uint256 amountIn, uint256 amountOutMin, address[] path, address to, uint256 deadline)',
44
+ ])
45
+ const v3FactoryInterface = new Interface(['function getPool(address tokenA, address tokenB, uint24 fee) view returns (address)'])
46
+ const v3PoolInterface = new Interface(['function liquidity() view returns (uint128)'])
47
+ /** QuoterV2 很费 gas(池子很薄时一次可达上千万):每次 eth_call 只放几个报价,多个 eth_call 并发,避免超过节点的 gas 上限后被悄悄截掉 */
48
+ const QUOTES_PER_CALL = 3
49
+ /** 两跳时每一跳最多用几个手续费档位(按当前流动性取最大的) */
50
+ const TIERS_PER_LEG = 2
51
+ const quoterInterface = new Interface([
52
+ 'function quoteExactInputSingle((address tokenIn, address tokenOut, uint256 amountIn, uint24 fee, uint160 sqrtPriceLimitX96) params) returns (uint256 amountOut, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed, uint256 gasEstimate)',
53
+ 'function quoteExactInput(bytes path, uint256 amountIn) returns (uint256 amountOut, uint160[] sqrtPriceX96AfterList, uint32[] initializedTicksCrossedList, uint256 gasEstimate)',
54
+ ])
55
+ const swapRouterInterface = new Interface([
56
+ 'function exactInputSingle((address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 amountIn, uint256 amountOutMinimum, uint160 sqrtPriceLimitX96) params) payable returns (uint256 amountOut)',
57
+ 'function exactInput((bytes path, address recipient, uint256 amountIn, uint256 amountOutMinimum) params) payable returns (uint256 amountOut)',
58
+ 'function unwrapWETH9(uint256 amountMinimum, address recipient) payable',
59
+ 'function multicall(uint256 deadline, bytes[] data) payable returns (bytes[] results)',
60
+ ])
61
+
62
+ export interface SwapParams {
63
+ /** 卖出 / 买入的代币;主币用 NATIVE_TOKEN 或零地址(池子里按包装主币,交易里自动包装 / 解包) */
64
+ tokenIn: string
65
+ tokenOut: string
66
+ /** 卖出的数量:amountIn(最小单位)和 amountInFormatted(按精度换算,如 '1.5')二选一 */
67
+ amountIn?: BigNumberish
68
+ amountInFormatted?: string
69
+ }
70
+
71
+ export interface SwapOptions {
72
+ /** 价格滑点(比例,0.005 = 0.5%),最少收到 = 报价 × (1 − slippage),默认 0.005 */
73
+ slippage?: number
74
+ /** 交易的有效期(秒),默认 1200 */
75
+ deadline?: number
76
+ /** 收到代币的地址,默认 owner */
77
+ recipient?: string
78
+ /** 授权额度:'exact'(默认,本次卖出的数量)或 'max' */
79
+ approve?: 'exact' | 'max'
80
+ /**
81
+ * 代币的转账税(比例,0.05 = 5%),默认 0。V2 报价(getAmountsOut)不含转账税,带税的代币
82
+ * 实际收到的比报价少,最少收到按 (1 − slippage) × (1 − tax) 算,否则会因 INSUFFICIENT_OUTPUT_AMOUNT 回滚。
83
+ * 税率可用 /risk 的 getTokenRisks 查;只对 V2 路线生效(V3 不支持带税的代币)。plan.amountOut 仍是不含税的报价
84
+ */
85
+ tax?: number
86
+ /** 只用这些协议(id,如 ['pancakeswap-v3']),默认这条链内置表里能兑换的 V2 / V3 协议 */
87
+ protocols?: readonly string[]
88
+ /** 两跳的中间代币,默认 LP_QUOTE_TOKENS 里这条链的(包装主币和稳定币);传 [] 只找直接兑换 */
89
+ intermediates?: readonly string[]
90
+ /** 按这个区块的状态报价(默认最新):同一个区块里报价可以复现 */
91
+ blockTag?: BlockTag
92
+ /** 指定路线(如之前报价结果 routes 里的某一条):只用这条路线报价和兑换,不选最好的 */
93
+ route?: Pick<SwapRoute, 'protocol' | 'path' | 'fees'>
94
+ }
95
+
96
+ export interface SwapRoute {
97
+ protocol: string
98
+ protocolName: string
99
+ version: 'v2' | 'v3'
100
+ /** 经过的代币(池子里的地址,主币是包装主币) */
101
+ path: string[]
102
+ /** V3:每一跳池子的手续费档位;V2 为空 */
103
+ fees: number[]
104
+ /** 这条路线的报价(最小单位) */
105
+ amountOut: string
106
+ }
107
+
108
+ export interface SwapTx {
109
+ /** approve:卖出的代币授权给 Router;swap:兑换 */
110
+ kind: 'approve' | 'swap'
111
+ to: string
112
+ data: string
113
+ /** 附带的主币(最小单位):卖出主币时为卖出的数量 */
114
+ value: string
115
+ token?: string
116
+ spender?: string
117
+ amount?: string
118
+ }
119
+
120
+ export interface SwapPlan {
121
+ tokenIn: LpToken
122
+ tokenOut: LpToken
123
+ amountIn: string
124
+ amountInFormatted: string | null
125
+ /** 报价(最小单位)和最少收到的数量(按滑点) */
126
+ amountOut: string
127
+ amountOutFormatted: string | null
128
+ amountOutMin: string
129
+ amountOutMinFormatted: string | null
130
+ /** 1 个 tokenIn 换多少 tokenOut(按这笔的数量) */
131
+ price: number | null
132
+ /** 价格影响(0~1):这笔的价格比极小数量的价格差多少;算不出时为 null */
133
+ priceImpact: number | null
134
+ route: SwapRoute
135
+ /** 其他候选路线的报价,从高到低(含选中的) */
136
+ routes: SwapRoute[]
137
+ /** 按顺序发送的交易(授权在前) */
138
+ transactions: SwapTx[]
139
+ }
140
+
141
+ interface Candidate {
142
+ protocol: LpProtocol
143
+ version: 'v2' | 'v3'
144
+ path: string[]
145
+ fees: number[]
146
+ /** V2:Router;V3:报价 / 兑换合约 */
147
+ router: string
148
+ quoter?: string
149
+ }
150
+
151
+ /** 这条链上能兑换的协议:V2 要有 Router,V3 要有报价和兑换合约(V3_SWAP_CONTRACTS) */
152
+ export function swapProtocols(chainId: number): LpProtocol[] {
153
+ return LP_PROTOCOLS.filter((p) => p.chainId === chainId && ((p.version === 'v2' && !!p.router) || (p.version === 'v3' && !!V3_SWAP_CONTRACTS[p.id]?.[chainId])))
154
+ }
155
+
156
+ /** V3 的路径编码:token ‖ fee(uint24) ‖ token ‖ … */
157
+ function encodePath(path: readonly string[], fees: readonly number[]): string {
158
+ let hex = path[0]?.slice(2).toLowerCase() ?? ''
159
+ fees.forEach((fee, i) => {
160
+ hex += fee.toString(16).padStart(6, '0') + (path[i + 1] as string).slice(2).toLowerCase()
161
+ })
162
+ return `0x${hex}`
163
+ }
164
+
165
+ /** 列出候选路线(V3 先查哪些池子存在)并一次 multicall 报价,按输出从高到低 */
166
+ async function quoteRoutes(ctx: Ctx, tokenIn: string, tokenOut: string, amountIn: bigint, options: SwapOptions): Promise<Array<Candidate & { amountOut: bigint }>> {
167
+ const all = swapProtocols(ctx.chainId)
168
+ const protocols = options.protocols ? all.filter((p) => options.protocols?.includes(p.id)) : all
169
+ if (!protocols.length) throw new Error(`No swap protocol on chain ${ctx.chainId}${options.protocols ? ` among ${options.protocols.join(', ')}` : ''}`)
170
+ const mids = (options.intermediates ?? LP_QUOTE_TOKENS[ctx.chainId] ?? []).map(evm).filter((m) => !same(m, tokenIn) && !same(m, tokenOut))
171
+
172
+ // V3:先查池子是否存在(每一跳的每个手续费档位)
173
+ const v3 = protocols.filter((p) => p.version === 'v3')
174
+ const legs: Array<{ p: LpProtocol; a: string; b: string; fee: number }> = []
175
+ for (const p of v3) {
176
+ for (const fee of V3_FEES) {
177
+ legs.push({ p, a: tokenIn, b: tokenOut, fee })
178
+ for (const m of mids) legs.push({ p, a: tokenIn, b: m, fee }, { p, a: m, b: tokenOut, fee })
179
+ }
180
+ }
181
+ const overrides = options.blockTag === undefined ? undefined : { blockTag: options.blockTag }
182
+ const pools = legs.length ? await ctx.provider.tryAll<string>(legs.map((l) => ctx.provider.contract(l.p.factory as string, v3FactoryInterface).getPool(l.a, l.b, l.fee)), overrides) : []
183
+ // 再读存在的池子的当前流动性:没有流动性的不报价;两跳的每一跳只留流动性最大的几个档位(同一对代币的流动性可以直接比)
184
+ const found = legs.flatMap((l, i) => (pools[i] && BigInt(pools[i] as string) !== 0n ? [{ ...l, pool: pools[i] as string }] : []))
185
+ const liquidity = found.length ? await ctx.provider.tryAll<bigint>(found.map((l) => ctx.provider.contract(l.pool, v3PoolInterface).liquidity()), overrides) : []
186
+ const live = found.map((l, i) => ({ ...l, liquidity: liquidity[i] ?? 0n })).filter((l) => l.liquidity > 0n)
187
+ const legKey = (l: { p: LpProtocol; a: string; b: string }) => `${l.p.id}:${l.a}:${l.b}`
188
+ const byLeg = new Map<string, typeof live>()
189
+ for (const l of live) byLeg.set(legKey(l), [...(byLeg.get(legKey(l)) ?? []), l])
190
+ const exists = new Set<string>()
191
+ for (const [key, list] of byLeg) {
192
+ const direct = key.endsWith(`:${tokenIn}:${tokenOut}`)
193
+ const kept = direct ? list : [...list].sort((x, y) => (x.liquidity < y.liquidity ? 1 : x.liquidity > y.liquidity ? -1 : 0)).slice(0, TIERS_PER_LEG)
194
+ for (const l of kept) exists.add(`${l.p.id}:${l.a}:${l.b}:${l.fee}`)
195
+ }
196
+ const has = (p: LpProtocol, a: string, b: string, fee: number) => exists.has(`${p.id}:${a}:${b}:${fee}`)
197
+
198
+ const candidates: Candidate[] = []
199
+ for (const p of protocols) {
200
+ if (p.version === 'v2') {
201
+ const router = p.router as string
202
+ candidates.push({ protocol: p, version: 'v2', path: [tokenIn, tokenOut], fees: [], router })
203
+ for (const m of mids) candidates.push({ protocol: p, version: 'v2', path: [tokenIn, m, tokenOut], fees: [], router })
204
+ continue
205
+ }
206
+ const c = V3_SWAP_CONTRACTS[p.id]?.[ctx.chainId] as { quoter: string; router: string }
207
+ for (const fee of V3_FEES) {
208
+ if (has(p, tokenIn, tokenOut, fee)) candidates.push({ protocol: p, version: 'v3', path: [tokenIn, tokenOut], fees: [fee], router: c.router, quoter: c.quoter })
209
+ }
210
+ for (const m of mids) {
211
+ for (const f1 of V3_FEES) {
212
+ if (!has(p, tokenIn, m, f1)) continue
213
+ for (const f2 of V3_FEES) {
214
+ if (has(p, m, tokenOut, f2)) candidates.push({ protocol: p, version: 'v3', path: [tokenIn, m, tokenOut], fees: [f1, f2], router: c.router, quoter: c.quoter })
215
+ }
216
+ }
217
+ }
218
+ }
219
+ // V2 的 getAmountsOut 很便宜,一次报完;V3 每 QUOTES_PER_CALL 个一组并发
220
+ const v2Index = candidates.flatMap((c, i) => (c.version === 'v2' ? [i] : []))
221
+ const v3Index = candidates.flatMap((c, i) => (c.version === 'v3' ? [i] : []))
222
+ const groups = [v2Index, ...Array.from({ length: Math.ceil(v3Index.length / QUOTES_PER_CALL) }, (_, k) => v3Index.slice(k * QUOTES_PER_CALL, (k + 1) * QUOTES_PER_CALL))].filter((g) => g.length)
223
+ const quotes: unknown[] = new Array(candidates.length).fill(null)
224
+ await Promise.all(
225
+ groups.map(async (group) => {
226
+ const results = await ctx.provider.tryAll(
227
+ group.map((i) => quoteCall(ctx, candidates[i] as Candidate, amountIn)),
228
+ overrides,
229
+ )
230
+ group.forEach((i, k) => (quotes[i] = results[k]))
231
+ }),
232
+ )
233
+ return candidates
234
+ .map((c, i) => ({ ...c, amountOut: amountOf(c, quotes[i]) }))
235
+ .filter((c) => c.amountOut > 0n)
236
+ .sort((a, b) => (a.amountOut < b.amountOut ? 1 : a.amountOut > b.amountOut ? -1 : 0))
237
+ }
238
+
239
+ function quoteCall(ctx: Ctx, c: Candidate, amountIn: bigint) {
240
+ if (c.version === 'v2') return ctx.provider.contract(c.router, v2RouterInterface).getAmountsOut(amountIn, c.path)
241
+ const quoter = ctx.provider.contract(c.quoter as string, quoterInterface)
242
+ return c.path.length === 2
243
+ ? quoter.quoteExactInputSingle({ tokenIn: c.path[0], tokenOut: c.path[1], amountIn, fee: c.fees[0], sqrtPriceLimitX96: 0n })
244
+ : quoter.quoteExactInput(encodePath(c.path, c.fees), amountIn)
245
+ }
246
+
247
+ function amountOf(c: Candidate, result: unknown): bigint {
248
+ if (result === null || result === undefined) return 0n
249
+ if (c.version === 'v2') {
250
+ const amounts = result as readonly bigint[]
251
+ return amounts.length === c.path.length ? (amounts[amounts.length - 1] as bigint) : 0n
252
+ }
253
+ return (result as { 0: bigint })[0] ?? 0n
254
+ }
255
+
256
+ /**
257
+ * 报价并构建兑换的交易(不签名):选输出最多的路线,最少收到 = 报价 × (1 − slippage);卖出代币时额度不够先授权给 Router。
258
+ *
259
+ * ```ts
260
+ * const plan = await buildSwap(provider, user, { tokenIn: NATIVE_TOKEN, tokenOut: USDT, amountInFormatted: '0.1' })
261
+ * plan.amountOutFormatted // '61.2'
262
+ * plan.route // { protocol: 'pancakeswap-v3', path: [WBNB, USDT], fees: [100], … }
263
+ * ```
264
+ */
265
+ export async function buildSwap(provider: Provider, owner: string, params: SwapParams, options: SwapOptions = {}): Promise<SwapPlan> {
266
+ const { slippage, deadline, recipient, approve } = options
267
+ const ctx = await createCtx(provider, owner, { slippage, deadline, recipient, approve })
268
+ const wrapped = LP_QUOTE_TOKENS[ctx.chainId]?.[0]
269
+ const nativeIn = isNative(ctx, params.tokenIn)
270
+ const nativeOut = isNative(ctx, params.tokenOut)
271
+ if ((nativeIn || nativeOut) && !wrapped) throw new Error(`No wrapped native token known on chain ${ctx.chainId}`)
272
+ const tokenIn = nativeIn ? evm(wrapped as string) : evm(params.tokenIn)
273
+ const tokenOut = nativeOut ? evm(wrapped as string) : evm(params.tokenOut)
274
+ if (same(tokenIn, tokenOut)) throw new Error(nativeIn || nativeOut ? 'Wrapping / unwrapping the native token is not a swap; use the wrapped token contract' : 'tokenIn and tokenOut are the same')
275
+ const [infoIn, infoOut] = await tokenInfo(ctx, [nativeIn ? params.tokenIn : tokenIn, nativeOut ? params.tokenOut : tokenOut])
276
+ const tIn = infoIn as LpToken
277
+ const tOut = infoOut as LpToken
278
+ const amountIn = parseAmount(params.amountIn, params.amountInFormatted, tIn, 'amountIn')
279
+ if (amountIn === undefined || amountIn <= 0n) throw new Error('amountIn must be greater than 0')
280
+
281
+ const all = await quoteRoutes(ctx, tokenIn, tokenOut, amountIn, options)
282
+ const wanted = options.route
283
+ const routes = wanted
284
+ ? all.filter((c) => c.protocol.id === wanted.protocol && c.path.length === wanted.path.length && c.path.every((a, i) => same(a, wanted.path[i] as string)) && c.fees.join() === wanted.fees.join())
285
+ : all
286
+ const best = routes[0]
287
+ if (!best) throw new Error(wanted ? 'The given route has no quote (pool missing or no liquidity)' : 'No route found: no pool for this pair (directly or through the intermediate tokens) in the built-in protocols')
288
+ // 价格影响:同一路线卖出极小的数量(1 / 10000)的价格
289
+ const small = amountIn / 10_000n
290
+ const smallOut = small > 0n ? amountOf(best, await provider.tryAll([quoteCall(ctx, best, small)], options.blockTag === undefined ? undefined : { blockTag: options.blockTag }).then((r) => r[0])) : 0n
291
+ // 参考交易的输出太小时取整误差太大(几个最小单位就差好几个百分点),算不准,返回 null
292
+ const priceImpact = smallOut >= 10_000n ? Math.max(0, 1 - Number(best.amountOut) / Number(amountIn) / (Number(smallOut) / Number(small))) : null
293
+ const tax = options.tax ?? 0
294
+ if (!(tax >= 0 && tax < 1)) throw new RangeError(`tax must be in [0, 1): ${tax}`)
295
+ const keep = best.version === 'v2' ? 1 - tax : 1
296
+ const amountOutMin = minus((best.amountOut * BigInt(Math.round(keep * 1_000_000))) / 1_000_000n, ctx.slippage)
297
+
298
+ // approvals 只会返回 kind 为 'approve' 的交易
299
+ // Router 在 Tron 上是 T 地址:授权的 ABI 参数要用 0x 格式
300
+ const transactions = (nativeIn ? [] : await approvals(ctx, [{ token: tokenIn, spender: evm(best.router), amount: amountIn }])) as SwapTx[]
301
+ transactions.push(swapTx(ctx, best, amountIn, amountOutMin, nativeIn, nativeOut))
302
+
303
+ const route = (c: Candidate & { amountOut: bigint }): SwapRoute => ({
304
+ protocol: c.protocol.id,
305
+ protocolName: c.protocol.name,
306
+ version: c.version,
307
+ path: c.path.map((a) => out(ctx, a)),
308
+ fees: c.fees,
309
+ amountOut: c.amountOut.toString(),
310
+ })
311
+ const decimals = (t: LpToken) => t.decimals ?? 0
312
+ return {
313
+ tokenIn: tIn,
314
+ tokenOut: tOut,
315
+ amountIn: amountIn.toString(),
316
+ amountInFormatted: fmt(amountIn, tIn),
317
+ amountOut: best.amountOut.toString(),
318
+ amountOutFormatted: fmt(best.amountOut, tOut),
319
+ amountOutMin: amountOutMin.toString(),
320
+ amountOutMinFormatted: fmt(amountOutMin, tOut),
321
+ price: tIn.decimals === null || tOut.decimals === null ? null : Number(best.amountOut) / 10 ** decimals(tOut) / (Number(amountIn) / 10 ** decimals(tIn)),
322
+ priceImpact,
323
+ route: route(best),
324
+ routes: routes.map(route),
325
+ transactions,
326
+ }
327
+ }
328
+
329
+ function swapTx(ctx: Ctx, c: Candidate, amountIn: bigint, amountOutMin: bigint, nativeIn: boolean, nativeOut: boolean): SwapTx {
330
+ const value = nativeIn ? amountIn.toString() : '0'
331
+ if (c.version === 'v2') {
332
+ const data = nativeIn
333
+ ? v2RouterInterface.encodeFunctionData('swapExactETHForTokensSupportingFeeOnTransferTokens', [amountOutMin, c.path, ctx.recipient, ctx.deadline])
334
+ : nativeOut
335
+ ? v2RouterInterface.encodeFunctionData('swapExactTokensForETHSupportingFeeOnTransferTokens', [amountIn, amountOutMin, c.path, ctx.recipient, ctx.deadline])
336
+ : v2RouterInterface.encodeFunctionData('swapExactTokensForTokensSupportingFeeOnTransferTokens', [amountIn, amountOutMin, c.path, ctx.recipient, ctx.deadline])
337
+ return { kind: 'swap', to: out(ctx, c.router), data, value }
338
+ }
339
+ // V3:卖出主币时附带 value(Router 自动包装);买入主币时先收到 Router,再 unwrapWETH9 给收款地址
340
+ const recipient = nativeOut ? ADDRESS_THIS : ctx.recipient
341
+ const swap =
342
+ c.path.length === 2
343
+ ? swapRouterInterface.encodeFunctionData('exactInputSingle', [
344
+ { tokenIn: c.path[0], tokenOut: c.path[1], fee: c.fees[0], recipient, amountIn, amountOutMinimum: amountOutMin, sqrtPriceLimitX96: 0n },
345
+ ])
346
+ : swapRouterInterface.encodeFunctionData('exactInput', [{ path: encodePath(c.path, c.fees), recipient, amountIn, amountOutMinimum: amountOutMin }])
347
+ const calls = nativeOut ? [swap, swapRouterInterface.encodeFunctionData('unwrapWETH9', [amountOutMin, ctx.recipient])] : [swap]
348
+ return { kind: 'swap', to: out(ctx, c.router), data: swapRouterInterface.encodeFunctionData('multicall', [ctx.deadline, calls]), value }
349
+ }
350
+
351
+ export type SwapTxResult = Omit<LiquidityTxResult, 'kind'> & { kind: SwapTx['kind'] }
352
+
353
+ export interface SwapResult {
354
+ plan: SwapPlan
355
+ /** 每笔交易的结果(授权、兑换) */
356
+ results: SwapTxResult[]
357
+ /** 兑换那一笔的状态和哈希 */
358
+ status: LiquidityTxResult['status']
359
+ hash: string | null
360
+ }
361
+
362
+ export interface SwapSendOptions extends SwapOptions, Omit<BatchCallOptions, 'mode' | 'continueOnError' | 'onUpdate'> {
363
+ onUpdate?: (result: SwapTxResult) => void
364
+ }
365
+
366
+ /**
367
+ * 兑换:构建(同 buildSwap)后用 from 签名发送(钱包同 /write:私钥、ethers Signer、浏览器钱包、Tron 钱包)。
368
+ * 授权失败时不发兑换的交易
369
+ *
370
+ * ```ts
371
+ * const r = await swap(provider, key, { tokenIn: USDT, tokenOut: NATIVE_TOKEN, amountInFormatted: '100' }, { slippage: 0.01 })
372
+ * r.status // 'confirmed'
373
+ * ```
374
+ */
375
+ export async function swap(provider: Provider, from: SignerInput, params: SwapParams, options: SwapSendOptions = {}): Promise<SwapResult> {
376
+ const [{ batchCall, tronModule }, { resolveSigner }, { emptyOutcome, toWriteResult }] = await Promise.all([import('./write/batch.js'), import('./write/signer.js'), import('./write/send.js')])
377
+ const tron = await tronModule(provider)
378
+ const owner = tron ? await tron.tronSignerAddress(from) : (await resolveSigner(from)).address
379
+ let plan = await buildSwap(provider, owner, params, options)
380
+ const send = async (txs: SwapTx[]): Promise<SwapTxResult[]> =>
381
+ (
382
+ await batchCall(
383
+ provider,
384
+ txs.map((tx) => ({ from, to: tx.to, data: tx.data, value: BigInt(tx.value) })),
385
+ {
386
+ ...options,
387
+ mode: 'tx',
388
+ continueOnError: false,
389
+ onUpdate: options.onUpdate ? (r) => options.onUpdate?.({ ...r, kind: (txs[r.index] as SwapTx).kind }) : undefined,
390
+ },
391
+ )
392
+ ).map((r, i) => ({ ...r, kind: (txs[i] as SwapTx).kind }))
393
+ const txs = plan.transactions
394
+ let results: SwapTxResult[]
395
+ if (txs.length === 1) {
396
+ results = await send(txs)
397
+ } else {
398
+ // 有授权时先发授权:确认后重新报价再发兑换,deadline 和最少收到从发兑换那一刻算,
399
+ // 不会因为授权等得久而过期或按旧价成交
400
+ const approved = await send(txs.slice(0, -1))
401
+ const last = txs[txs.length - 1] as SwapTx
402
+ if (approved.every((r) => r.status === 'confirmed')) {
403
+ plan = await buildSwap(provider, owner, params, options)
404
+ const fresh = plan.transactions[plan.transactions.length - 1] as SwapTx
405
+ results = [...approved, ...(await send([fresh])).map((r) => ({ ...r, index: txs.length - 1 }))]
406
+ } else {
407
+ results = [...approved, { ...toWriteResult(txs.length - 1, owner, last.to, emptyOutcome('skipped', 'skipped: the approval did not succeed')), kind: last.kind }]
408
+ }
409
+ }
410
+ const main = results[results.length - 1] as SwapTxResult
411
+ return { plan, results, status: main.status, hash: main.hash }
412
+ }
413
+
@@ -0,0 +1,5 @@
1
+ // 由 scripts/build-contracts.mjs 生成,不要手改。源码:contracts/TokenProbe.sol(solc 0.8.30+commit.73712a01.Emscripten.clang)
2
+
3
+ /** constructor(address router, address token, address pair) payable — 返回 (stage, buyQuote, bought, transferSent, transferReceived, sellAmount, sellQuote, sold, buyGas, sellGas, pairReceived) */
4
+ export const TOKEN_PROBE_BYTECODE =
5
+ '0x6080604052604051610ba0380380610ba0833981016040819052610022916108be565b61007f60405180610160016040528060008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b6000846001600160a01b031663ad5c46486040518163ffffffff1660e01b8152600401602060405180830381865afa1580156100bf573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100e39190610901565b6040805160028082526060820183529293506000929091602083019080368337019050509050818160008151811061011d5761011d610939565b60200260200101906001600160a01b031690816001600160a01b031681525050848160018151811061015157610151610939565b6001600160a01b03928316602091820292909201015260405163d06ca61f60e01b81529087169063d06ca61f9061018e9034908590600401610994565b600060405180830381865afa9250505080156101cc57506040513d6000823e601f3d908101601f191682016040526101c991908101906109b5565b60015b156101f757806001815181106101e4576101e4610939565b6020026020010151846020018181525050505b60005a9050866001600160a01b031663b6f9de953460008530426040518663ffffffff1660e01b81526004016102309493929190610a81565b6000604051808303818588803b15801561024957600080fd5b505af19350505050801561025b575060015b61026857600184526102e7565b5a6102739082610ab6565b6101008501526040516370a0823160e01b81523060048201526001600160a01b038716906370a0823190602401602060405180830381865afa1580156102bd573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102e19190610ad7565b60408501525b83511580156102fb5750600a846040015110155b1561042d5760408051306020808301919091526001600160a01b038916828401528251808303840181526060909201835281519101209085015161034190600a90610af0565b606086018190526040516001600160a01b038316602482015260448101919091526103ad90889063a9059cbb60e01b906064015b60408051808303601f190181529190526020810180516001600160e01b0319939093166001600160e01b039384161790529061080116565b15610426576040516370a0823160e01b81526001600160a01b0382811660048301528816906370a0823190602401602060405180830381865afa1580156103f8573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061041c9190610ad7565b608086015261042b565b600285525b505b83516000036104dd576040516370a0823160e01b81523060048201526001600160a01b038716906370a0823190602401602060405180830381865afa15801561047a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061049e9190610ad7565b60a08501526040516001600160a01b038816602482015260001960448201526104d490879063095ea7b360e01b90606401610375565b6104dd57600384525b835160000361075d5785826000815181106104fa576104fa610939565b60200260200101906001600160a01b031690816001600160a01b031681525050828260018151811061052e5761052e610939565b6001600160a01b03928316602091820292909201015260a085015160405163d06ca61f60e01b81529189169163d06ca61f9161056e918690600401610994565b600060405180830381865afa9250505080156105ac57506040513d6000823e601f3d908101601f191682016040526105a991908101906109b5565b60015b156105d757806001815181106105c4576105c4610939565b60200260200101518560c0018181525050505b6040516370a0823160e01b81526001600160a01b03868116600483015247916000918916906370a0823190602401602060405180830381865afa158015610622573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106469190610ad7565b90505a60a087015160405163791ac94760e01b81529194506001600160a01b038b169163791ac9479161068491600090899030904290600401610b12565b600060405180830381600087803b15801561069e57600080fd5b505af19250505080156106af575060015b6106bc576004865261075a565b5a6106c79084610ab6565b6101208701526106d78247610ab6565b60e08701526040516370a0823160e01b81526001600160a01b0388811660048301528291908a16906370a0823190602401602060405180830381865afa158015610725573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107499190610ad7565b6107539190610ab6565b6101408701525b50505b6000846040516020016107e89190600061016082019050825182526020830151602083015260408301516040830152606083015160608301526080830151608083015260a083015160a083015260c083015160c083015260e083015160e083015261010083015161010083015261012083015161012083015261014083015161014083015292915050565b6040516020818303038152906040529050805160208201f35b6000806000846001600160a01b03168460405161081e9190610b4e565b6000604051808303816000865af19150503d806000811461085b576040519150601f19603f3d011682016040523d82523d6000602084013e610860565b606091505b5091509150818015610897575080511580610897575060208151101580156108975750808060200190518101906108979190610b7d565b925050505b92915050565b80516001600160a01b03811681146108b957600080fd5b919050565b6000806000606084860312156108d357600080fd5b6108dc846108a2565b92506108ea602085016108a2565b91506108f8604085016108a2565b90509250925092565b60006020828403121561091357600080fd5b61091c826108a2565b9392505050565b634e487b7160e01b600052604160045260246000fd5b634e487b7160e01b600052603260045260246000fd5b600081518084526020840193506020830160005b8281101561098a5781516001600160a01b0316865260209586019590910190600101610963565b5093949350505050565b8281526040602082015260006109ad604083018461094f565b949350505050565b6000602082840312156109c757600080fd5b81516001600160401b038111156109dd57600080fd5b8201601f810184136109ee57600080fd5b80516001600160401b03811115610a0757610a07610923565b604051600582901b90603f8201601f191681016001600160401b0381118282101715610a3557610a35610923565b604052918252602081840181019290810187841115610a5357600080fd5b6020850194505b83851015610a7657845180825260209586019590935001610a5a565b509695505050505050565b848152608060208201526000610a9a608083018661094f565b6001600160a01b03949094166040830152506060015292915050565b8181038181111561089c57634e487b7160e01b600052601160045260246000fd5b600060208284031215610ae957600080fd5b5051919050565b600082610b0d57634e487b7160e01b600052601260045260246000fd5b500490565b85815284602082015260a060408201526000610b3160a083018661094f565b6001600160a01b0394909416606083015250608001529392505050565b6000825160005b81811015610b6f5760208186018101518583015201610b55565b506000920191825250919050565b600060208284031215610b8f57600080fd5b8151801515811461091c57600080fdfe'
package/src/write/tron.ts CHANGED
@@ -186,6 +186,11 @@ function resolveTronSigner(input: SignerInput, cache: Map<unknown, Promise<TronK
186
186
  return resolved
187
187
  }
188
188
 
189
+ /** 签名器的地址(T 地址),不发交易、不弹钱包 */
190
+ export async function tronSignerAddress(input: SignerInput): Promise<string> {
191
+ return (await resolveTronSigner(input, new Map())).address
192
+ }
193
+
189
194
  // ---- 交易编码(protobuf) ----
190
195
 
191
196
  function varint(value: bigint | number): number[] {
@@ -739,6 +744,49 @@ function canonical(address: string): string {
739
744
  return toTronAddress(toEvmAddress(address))
740
745
  }
741
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
+
742
790
  /** 转 TRX:普通地址用 TransferContract;合约地址用合约调用(空 calldata 带 call_value,主网禁止 TransferContract 转给合约) */
743
791
  async function trxContract(rpc: TronRpc, owner: string, to: string, amount: bigint): Promise<TronContract> {
744
792
  return (await rpc.isContract(to)) ? { type: 'trigger', owner, contract: to, data: '0x', callValue: amount } : { type: 'transfer', owner, to, amount }