@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
@@ -0,0 +1,371 @@
1
+ import { Interface } from 'ethers'
2
+
3
+ import { LP_PROTOCOLS, type LpPosition, type LpProtocol, type LpToken } from '../lp.js'
4
+ import { sqrtBigInt } from '../lpMath.js'
5
+ import { ZERO_ADDRESS, approvals, evm, isNative, makePlan, minus, out, parseAmount, protocolById, removeAmount, same, tokenInfo, type Ctx } from './common.js'
6
+ import type { IncreaseLiquidityParams, LiquidityPlan, LiquidityTx, NewPositionParams, PositionRef, RemoveLiquidityParams } from './types.js'
7
+
8
+ const routerInterface = new Interface([
9
+ 'function WETH() view returns (address)',
10
+ 'function addLiquidity(address tokenA, address tokenB, uint256 amountADesired, uint256 amountBDesired, uint256 amountAMin, uint256 amountBMin, address to, uint256 deadline) returns (uint256 amountA, uint256 amountB, uint256 liquidity)',
11
+ 'function addLiquidityETH(address token, uint256 amountTokenDesired, uint256 amountTokenMin, uint256 amountETHMin, address to, uint256 deadline) payable returns (uint256 amountToken, uint256 amountETH, uint256 liquidity)',
12
+ 'function removeLiquidity(address tokenA, address tokenB, uint256 liquidity, uint256 amountAMin, uint256 amountBMin, address to, uint256 deadline) returns (uint256 amountA, uint256 amountB)',
13
+ 'function removeLiquidityETH(address token, uint256 liquidity, uint256 amountTokenMin, uint256 amountETHMin, address to, uint256 deadline) returns (uint256 amountToken, uint256 amountETH)',
14
+ ])
15
+ const factoryInterface = new Interface(['function getPair(address tokenA, address tokenB) view returns (address)', 'function feeTo() view returns (address)'])
16
+ const pairInterface = new Interface([
17
+ 'function token0() view returns (address)',
18
+ 'function token1() view returns (address)',
19
+ 'function factory() view returns (address)',
20
+ 'function getReserves() view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast)',
21
+ 'function totalSupply() view returns (uint256)',
22
+ 'function balanceOf(address owner) view returns (uint256)',
23
+ 'function kLast() view returns (uint256)',
24
+ ])
25
+
26
+ /** UniswapV2Pair.MINIMUM_LIQUIDITY:第一次添加时锁定的 LP 代币 */
27
+ const MINIMUM_LIQUIDITY = 1000n
28
+
29
+ interface PairState {
30
+ protocol: LpProtocol
31
+ router: string
32
+ pair: string | null
33
+ token0: string
34
+ token1: string
35
+ reserve0: bigint
36
+ reserve1: bigint
37
+ /** LP 总量,已加上下一次添加 / 移除前会先给 feeTo 增发的协议手续费(_mintFee) */
38
+ totalSupply: bigint
39
+ balance: bigint
40
+ }
41
+
42
+ /**
43
+ * UniswapV2Pair._mintFee:协议手续费开启(factory.feeTo 不为零)时,每次添加 / 移除前先按 √k 的增长给 feeTo 增发 LP,
44
+ * 按份额算数量时要用增发后的总量。比例按 LpProtocol.protocolFee,没填时按 8/25(增发得多、算出的数量偏少,最小值更宽松,不会因此 revert)
45
+ */
46
+ function protocolFeeMint(protocol: LpProtocol, totalSupply: bigint, reserve0: bigint, reserve1: bigint, kLast: bigint): bigint {
47
+ if (kLast === 0n || totalSupply === 0n) {
48
+ return 0n
49
+ }
50
+ const rootK = sqrtBigInt(reserve0 * reserve1)
51
+ const rootKLast = sqrtBigInt(kLast)
52
+ if (rootK <= rootKLast) {
53
+ return 0n
54
+ }
55
+ return protocol.protocolFee === '1/6'
56
+ ? (totalSupply * (rootK - rootKLast)) / (rootK * 5n + rootKLast)
57
+ : (totalSupply * (rootK - rootKLast) * 8n) / (rootK * 17n + rootKLast * 8n)
58
+ }
59
+
60
+ function routerOf(protocol: LpProtocol): string {
61
+ if (!protocol.router) {
62
+ throw new Error(`Protocol ${protocol.id} has no router; set router on its LpProtocol`)
63
+ }
64
+ return evm(protocol.router)
65
+ }
66
+
67
+ /** 读池子:储备、总量、owner 持有的 LP;协议没给时按池子的 factory 找(Router)。已知协议时协议手续费的状态一并读 */
68
+ async function readPair(ctx: Ctx, pair: string, protocol?: LpProtocol): Promise<PairState> {
69
+ const c = ctx.provider.contract(pair, pairInterface)
70
+ const feeToCall = (factory: string) => ctx.provider.contract(factory, factoryInterface).feeTo()
71
+ // kLast / feeTo 有的分叉没有:一起用 tryAll 读,读不到按没开启协议手续费
72
+ const first = (await ctx.provider.tryAll([
73
+ c.token0(),
74
+ c.token1(),
75
+ c.factory(),
76
+ c.getReserves(),
77
+ c.totalSupply(),
78
+ c.balanceOf(ctx.owner),
79
+ c.kLast(),
80
+ ...(protocol?.factory ? [feeToCall(protocol.factory)] : []),
81
+ ])) as [string | null, string | null, string | null, { 0: bigint; 1: bigint } | null, bigint | null, bigint | null, bigint | null, (string | null)?]
82
+ const [token0, token1, factory, reserves, totalSupply, balance, kLast] = first
83
+ if (!token0 || !token1 || !factory || !reserves || totalSupply === null || balance === null) {
84
+ throw new Error(`${out(ctx, pair)} is not a V2 pool`)
85
+ }
86
+ const found =
87
+ protocol ??
88
+ [...(ctx.options.protocols ?? []), ...LP_PROTOCOLS].find((p) => p.chainId === ctx.chainId && p.version === 'v2' && p.factory && same(p.factory, factory))
89
+ if (!found) {
90
+ throw new Error(`Unknown V2 factory ${out(ctx, evm(factory))} of pool ${out(ctx, pair)}; pass its LpProtocol (with router) in options.protocols`)
91
+ }
92
+ if (protocol?.factory && !same(protocol.factory, factory)) {
93
+ throw new Error(`Pool ${out(ctx, pair)} does not belong to ${protocol.id}`)
94
+ }
95
+ const feeTo = protocol?.factory ? (first[7] ?? null) : ((await ctx.provider.tryAll([feeToCall(factory)])) as [string | null])[0]
96
+ const feeOn = feeTo !== null && evm(feeTo) !== ZERO_ADDRESS
97
+ const minted = feeOn ? protocolFeeMint(found, totalSupply, reserves[0], reserves[1], kLast ?? 0n) : 0n
98
+ return {
99
+ protocol: found,
100
+ router: routerOf(found),
101
+ pair,
102
+ token0: evm(token0),
103
+ token1: evm(token1),
104
+ reserve0: reserves[0],
105
+ reserve1: reserves[1],
106
+ totalSupply: totalSupply + minted,
107
+ balance,
108
+ }
109
+ }
110
+
111
+ /** 仓位上带的协议:LpProtocol 对象直接用;协议 id 按内置表 / options.protocols 找(getLpPositions 里不认识的工厂是 'v2:<工厂>',按工厂找) */
112
+ function refProtocol(ctx: Ctx, ref: PositionRef | LpPosition): LpProtocol | undefined {
113
+ if (!ref.protocol || (typeof ref.protocol === 'string' && ref.protocol.startsWith('v2:'))) {
114
+ return undefined
115
+ }
116
+ const protocol = protocolById(ctx, ref.protocol)
117
+ if (protocol.version !== 'v2') {
118
+ throw new Error(`${protocol.id} is not a V2 protocol`)
119
+ }
120
+ return protocol
121
+ }
122
+
123
+ const wethOf = async (ctx: Ctx, router: string) => evm((await ctx.provider.call(ctx.provider.contract(router, routerInterface).WETH())) as string)
124
+
125
+ /** UniswapV2Library.quote:按储备比例换算 */
126
+ const quote = (amountA: bigint, reserveA: bigint, reserveB: bigint) => (amountA * reserveB) / reserveA
127
+
128
+ export async function v2Add(ctx: Ctx, params: IncreaseLiquidityParams | NewPositionParams): Promise<LiquidityPlan> {
129
+ let state: PairState
130
+ /** 新建时 tokenA 是不是 token0;往已有仓位追加时为 null(数量直接按 token0 / token1) */
131
+ let aIs0: boolean | null = null
132
+ let native = ctx.options.native ?? false
133
+ /** Router 的包装币(用主币时才读) */
134
+ let weth: string | null = null
135
+
136
+ if ('position' in params) {
137
+ const ref = params.position as PositionRef | LpPosition
138
+ if (!ref.pool) {
139
+ throw new Error('V2 position needs pool')
140
+ }
141
+ state = await readPair(ctx, evm(ref.pool), refProtocol(ctx, ref))
142
+ } else {
143
+ const protocol = params.protocol as LpProtocol
144
+ const router = routerOf(protocol)
145
+ let a = evm(params.tokenA)
146
+ let b = evm(params.tokenB)
147
+ if (isNative(ctx, a) || isNative(ctx, b)) {
148
+ // 主币:换成 Router 的包装币,用主币支付
149
+ const wrapped = await wethOf(ctx, router)
150
+ a = isNative(ctx, a) ? wrapped : a
151
+ b = isNative(ctx, b) ? wrapped : b
152
+ weth = wrapped
153
+ native = true
154
+ }
155
+ if (a === b) {
156
+ throw new Error('tokenA and tokenB must be different')
157
+ }
158
+ if (!protocol.factory) {
159
+ throw new Error(`Protocol ${protocol.id} has no factory; set factory on its LpProtocol`)
160
+ }
161
+ const pair = evm((await ctx.provider.call(ctx.provider.contract(protocol.factory, factoryInterface).getPair(a, b))) as string)
162
+ if (pair === ZERO_ADDRESS) {
163
+ // 池子还不存在:Router 添加时会创建
164
+ const [token0, token1] = BigInt(a) < BigInt(b) ? [a, b] : [b, a]
165
+ state = { protocol, router, pair: null, token0, token1, reserve0: 0n, reserve1: 0n, totalSupply: 0n, balance: 0n }
166
+ } else {
167
+ state = await readPair(ctx, pair, protocol)
168
+ }
169
+ aIs0 = a === state.token0
170
+ }
171
+
172
+ const { router, token0, token1, reserve0, reserve1, totalSupply } = state
173
+ // 代币信息和(往已有仓位用主币追加时)包装币地址互不依赖,一起读
174
+ const [tokens, wrapped] = await Promise.all([tokenInfo(ctx, [token0, token1]), native && !weth ? wethOf(ctx, router) : Promise.resolve(weth)])
175
+ const [t0, t1] = tokens as [LpToken, LpToken]
176
+ weth = wrapped
177
+ let given0: bigint | undefined
178
+ let given1: bigint | undefined
179
+ if (aIs0 === null) {
180
+ const p = params as IncreaseLiquidityParams
181
+ given0 = parseAmount(p.amount0, p.amount0Formatted, t0, 'amount0')
182
+ given1 = parseAmount(p.amount1, p.amount1Formatted, t1, 'amount1')
183
+ } else {
184
+ const p = params as NewPositionParams
185
+ const amountA = parseAmount(p.amountA, p.amountAFormatted, aIs0 ? t0 : t1, 'amountA')
186
+ const amountB = parseAmount(p.amountB, p.amountBFormatted, aIs0 ? t1 : t0, 'amountB')
187
+ ;[given0, given1] = aIs0 ? [amountA, amountB] : [amountB, amountA]
188
+ }
189
+ if (given0 === undefined && given1 === undefined) {
190
+ throw new Error('Pass the amount of at least one token')
191
+ }
192
+
193
+ // 主币:其中一边必须是包装币
194
+ if (native) {
195
+ weth ??= await wethOf(ctx, router)
196
+ if (weth !== token0 && weth !== token1) {
197
+ throw new Error('native: true needs one side of the pool to be the wrapped native token')
198
+ }
199
+ }
200
+
201
+ // 传给 Router 的数量(desired):给了的按给的,另一边按储备比例
202
+ let desired0: bigint
203
+ let desired1: bigint
204
+ const empty = reserve0 === 0n && reserve1 === 0n
205
+ if (!empty && (reserve0 === 0n || reserve1 === 0n)) {
206
+ throw new Error(`Pool ${out(ctx, state.pair as string)} has one empty reserve; liquidity cannot be added at a meaningful price`)
207
+ }
208
+ if (empty) {
209
+ if (!given0 || !given1) {
210
+ throw new Error('The pool is empty: pass the amounts of both tokens (they set the initial price)')
211
+ }
212
+ ;[desired0, desired1] = [given0, given1]
213
+ } else {
214
+ desired0 = given0 ?? quote(given1 as bigint, reserve1, reserve0)
215
+ desired1 = given1 ?? quote(given0 as bigint, reserve0, reserve1)
216
+ }
217
+ // 实际用掉的数量:与 Router 的 _addLiquidity 一致(A 是 addLiquidity 的第一个代币;addLiquidityETH 的 A 是非包装币那一边)。
218
+ // 先按 A 算 B 的最优数量,超出 B 的 desired 时反过来按 B 算 A
219
+ let amount0 = desired0
220
+ let amount1 = desired1
221
+ if (!empty) {
222
+ const aIsToken0 = weth !== token0
223
+ const [dA, dB, rA, rB] = aIsToken0 ? [desired0, desired1, reserve0, reserve1] : [desired1, desired0, reserve1, reserve0]
224
+ const optimalB = quote(dA, rA, rB)
225
+ const [amountA, amountB] = optimalB <= dB ? [dA, optimalB] : [quote(dB, rB, rA), dB]
226
+ ;[amount0, amount1] = aIsToken0 ? [amountA, amountB] : [amountB, amountA]
227
+ }
228
+ if (amount0 === 0n || amount1 === 0n) {
229
+ throw new Error('Amount too small: one side rounds to 0')
230
+ }
231
+ const liquidity = totalSupply === 0n ? sqrtBigInt(amount0 * amount1) - MINIMUM_LIQUIDITY : minBig((amount0 * totalSupply) / reserve0, (amount1 * totalSupply) / reserve1)
232
+ if (liquidity <= 0n) {
233
+ throw new Error('Amount too small: no LP tokens would be minted')
234
+ }
235
+
236
+ // 最小值按实际会用掉的数量算(两边都给、比例与池子不同时,Router 会按较少的一边收取,那一边的最小值不能按给的数量算)
237
+ const min0 = empty ? desired0 : minus(amount0, ctx.slippage)
238
+ const min1 = empty ? desired1 : minus(amount1, ctx.slippage)
239
+ let main: LiquidityTx
240
+ let value = 0n
241
+ if (weth) {
242
+ const tokenIs0 = weth === token1
243
+ const token = tokenIs0 ? token0 : token1
244
+ // 附带的主币按 desired 给,没用完的 Router 会退回
245
+ value = tokenIs0 ? desired1 : desired0
246
+ main = {
247
+ kind: 'add',
248
+ to: out(ctx, router),
249
+ data: routerInterface.encodeFunctionData('addLiquidityETH', [
250
+ token,
251
+ tokenIs0 ? desired0 : desired1,
252
+ tokenIs0 ? min0 : min1,
253
+ tokenIs0 ? min1 : min0,
254
+ ctx.recipient,
255
+ ctx.deadline,
256
+ ]),
257
+ value: value.toString(),
258
+ }
259
+ } else {
260
+ main = {
261
+ kind: 'add',
262
+ to: out(ctx, router),
263
+ data: routerInterface.encodeFunctionData('addLiquidity', [token0, token1, desired0, desired1, min0, min1, ctx.recipient, ctx.deadline]),
264
+ value: '0',
265
+ }
266
+ }
267
+ const approve = await approvals(
268
+ ctx,
269
+ [
270
+ { token: token0, spender: router, amount: desired0 },
271
+ { token: token1, spender: router, amount: desired1 },
272
+ ].filter((i) => i.token !== weth),
273
+ )
274
+ return makePlan(ctx, {
275
+ action: 'add',
276
+ version: 'v2',
277
+ protocol: state.protocol.id,
278
+ protocolName: state.protocol.name,
279
+ pool: state.pair,
280
+ createPool: state.pair === null,
281
+ token0: t0,
282
+ token1: t1,
283
+ tokenId: null,
284
+ tickLower: null,
285
+ tickUpper: null,
286
+ tick: null,
287
+ price: null,
288
+ priceLower: null,
289
+ priceUpper: null,
290
+ liquidity: liquidity.toString(),
291
+ amount0: amount0.toString(),
292
+ amount1: amount1.toString(),
293
+ amount0Max: desired0.toString(),
294
+ amount1Max: desired1.toString(),
295
+ amount0Min: null,
296
+ amount1Min: null,
297
+ fees0: '0',
298
+ fees1: '0',
299
+ native: weth === null ? null : weth === token0 ? 'token0' : 'token1',
300
+ value: value.toString(),
301
+ transactions: [...approve, main],
302
+ })
303
+ }
304
+
305
+ export async function v2Remove(ctx: Ctx, params: RemoveLiquidityParams): Promise<LiquidityPlan> {
306
+ const ref = params.position
307
+ if (!ref.pool) {
308
+ throw new Error('V2 position needs pool')
309
+ }
310
+ const state = await readPair(ctx, evm(ref.pool), refProtocol(ctx, ref))
311
+ const { router, token0, token1, reserve0, reserve1, totalSupply, balance } = state
312
+ if (balance === 0n) {
313
+ throw new Error(`${out(ctx, ctx.owner)} holds no LP tokens of ${out(ctx, state.pair as string)}`)
314
+ }
315
+ const liquidity = removeAmount(balance, params.percent, params.liquidity)
316
+ const [t0, t1] = (await tokenInfo(ctx, [token0, token1])) as [LpToken, LpToken]
317
+ // 按份额取出(与 pair.burn 一致;总量已含先给 feeTo 增发的协议手续费)
318
+ const amount0 = (liquidity * reserve0) / totalSupply
319
+ const amount1 = (liquidity * reserve1) / totalSupply
320
+ const min0 = minus(amount0, ctx.slippage)
321
+ const min1 = minus(amount1, ctx.slippage)
322
+
323
+ let weth: string | null = null
324
+ if (ctx.options.native) {
325
+ weth = await wethOf(ctx, router)
326
+ if (weth !== token0 && weth !== token1) {
327
+ throw new Error('native: true needs one side of the pool to be the wrapped native token')
328
+ }
329
+ }
330
+ const tokenIs0 = weth === token1
331
+ const main: LiquidityTx = {
332
+ kind: 'remove',
333
+ to: out(ctx, router),
334
+ data: weth
335
+ ? routerInterface.encodeFunctionData('removeLiquidityETH', [tokenIs0 ? token0 : token1, liquidity, tokenIs0 ? min0 : min1, tokenIs0 ? min1 : min0, ctx.recipient, ctx.deadline])
336
+ : routerInterface.encodeFunctionData('removeLiquidity', [token0, token1, liquidity, min0, min1, ctx.recipient, ctx.deadline]),
337
+ value: '0',
338
+ }
339
+ const approve = await approvals(ctx, [{ token: state.pair as string, spender: router, amount: liquidity }])
340
+ return makePlan(ctx, {
341
+ action: 'remove',
342
+ version: 'v2',
343
+ protocol: state.protocol.id,
344
+ protocolName: state.protocol.name,
345
+ pool: state.pair,
346
+ createPool: false,
347
+ token0: t0,
348
+ token1: t1,
349
+ tokenId: null,
350
+ tickLower: null,
351
+ tickUpper: null,
352
+ tick: null,
353
+ price: null,
354
+ priceLower: null,
355
+ priceUpper: null,
356
+ liquidity: liquidity.toString(),
357
+ amount0: amount0.toString(),
358
+ amount1: amount1.toString(),
359
+ amount0Max: null,
360
+ amount1Max: null,
361
+ amount0Min: min0.toString(),
362
+ amount1Min: min1.toString(),
363
+ fees0: '0',
364
+ fees1: '0',
365
+ native: weth === null ? null : weth === token0 ? 'token0' : 'token1',
366
+ value: '0',
367
+ transactions: [...approve, main],
368
+ })
369
+ }
370
+
371
+ const minBig = (a: bigint, b: bigint) => (a < b ? a : b)