@w3lib/evm-batch-call 0.4.1 → 0.5.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 (92) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +166 -3
  3. package/dist/aggregate.d.ts.map +1 -1
  4. package/dist/aggregate.js +153 -40
  5. package/dist/aggregate.js.map +1 -1
  6. package/dist/fallback.d.ts +20 -0
  7. package/dist/fallback.d.ts.map +1 -1
  8. package/dist/fallback.js +36 -5
  9. package/dist/fallback.js.map +1 -1
  10. package/dist/lp.d.ts +149 -0
  11. package/dist/lp.d.ts.map +1 -0
  12. package/dist/lp.js +499 -0
  13. package/dist/lp.js.map +1 -0
  14. package/dist/lpMath.d.ts +25 -0
  15. package/dist/lpMath.d.ts.map +1 -0
  16. package/dist/lpMath.js +103 -0
  17. package/dist/lpMath.js.map +1 -0
  18. package/dist/lpV4.d.ts +108 -0
  19. package/dist/lpV4.d.ts.map +1 -0
  20. package/dist/lpV4.js +283 -0
  21. package/dist/lpV4.js.map +1 -0
  22. package/dist/owner.d.ts +1 -1
  23. package/dist/owner.d.ts.map +1 -1
  24. package/dist/owner.js +18 -5
  25. package/dist/owner.js.map +1 -1
  26. package/dist/scan.d.ts +54 -0
  27. package/dist/scan.d.ts.map +1 -1
  28. package/dist/scan.js +124 -92
  29. package/dist/scan.js.map +1 -1
  30. package/dist/source.d.ts +2 -0
  31. package/dist/source.d.ts.map +1 -1
  32. package/dist/source.js +4 -0
  33. package/dist/source.js.map +1 -1
  34. package/dist/subpaths/lp.d.ts +33 -0
  35. package/dist/subpaths/lp.d.ts.map +1 -0
  36. package/dist/subpaths/lp.js +47 -0
  37. package/dist/subpaths/lp.js.map +1 -0
  38. package/dist/subpaths/write.d.ts +20 -0
  39. package/dist/subpaths/write.d.ts.map +1 -0
  40. package/dist/subpaths/write.js +18 -0
  41. package/dist/subpaths/write.js.map +1 -0
  42. package/dist/tron.d.ts +2 -0
  43. package/dist/tron.d.ts.map +1 -1
  44. package/dist/tron.js +4 -0
  45. package/dist/tron.js.map +1 -1
  46. package/dist/write/batch.d.ts +88 -0
  47. package/dist/write/batch.d.ts.map +1 -0
  48. package/dist/write/batch.js +231 -0
  49. package/dist/write/batch.js.map +1 -0
  50. package/dist/write/collect.d.ts +53 -0
  51. package/dist/write/collect.d.ts.map +1 -0
  52. package/dist/write/collect.js +255 -0
  53. package/dist/write/collect.js.map +1 -0
  54. package/dist/write/rpc.d.ts +68 -0
  55. package/dist/write/rpc.d.ts.map +1 -0
  56. package/dist/write/rpc.js +236 -0
  57. package/dist/write/rpc.js.map +1 -0
  58. package/dist/write/send.d.ts +124 -0
  59. package/dist/write/send.d.ts.map +1 -0
  60. package/dist/write/send.js +373 -0
  61. package/dist/write/send.js.map +1 -0
  62. package/dist/write/signer.d.ts +58 -0
  63. package/dist/write/signer.d.ts.map +1 -0
  64. package/dist/write/signer.js +41 -0
  65. package/dist/write/signer.js.map +1 -0
  66. package/dist/write/transfer.d.ts +83 -0
  67. package/dist/write/transfer.d.ts.map +1 -0
  68. package/dist/write/transfer.js +259 -0
  69. package/dist/write/transfer.js.map +1 -0
  70. package/dist/write/tron.d.ts +58 -0
  71. package/dist/write/tron.d.ts.map +1 -0
  72. package/dist/write/tron.js +956 -0
  73. package/dist/write/tron.js.map +1 -0
  74. package/package.json +27 -14
  75. package/src/aggregate.ts +168 -36
  76. package/src/fallback.ts +55 -11
  77. package/src/lp.ts +721 -0
  78. package/src/lpMath.ts +113 -0
  79. package/src/lpV4.ts +406 -0
  80. package/src/owner.ts +19 -6
  81. package/src/scan.ts +155 -86
  82. package/src/source.ts +5 -0
  83. package/src/subpaths/lp.ts +71 -0
  84. package/src/subpaths/write.ts +26 -0
  85. package/src/tron.ts +5 -0
  86. package/src/write/batch.ts +302 -0
  87. package/src/write/collect.ts +312 -0
  88. package/src/write/rpc.ts +277 -0
  89. package/src/write/send.ts +490 -0
  90. package/src/write/signer.ts +90 -0
  91. package/src/write/transfer.ts +344 -0
  92. package/src/write/tron.ts +1100 -0
package/src/lp.ts ADDED
@@ -0,0 +1,721 @@
1
+ import type { CallOverrides } from './aggregate.js'
2
+ import type { Call } from './call.js'
3
+ import { formatUnits } from './erc20.js'
4
+ import { getAmountsForLiquidity, getFeeGrowthInside, getFeesEarned, getSqrtRatioAtTick, sqrtPriceToPrice, tickToPrice } from './lpMath.js'
5
+ import { v4Positions, type LpV4Options } from './lpV4.js'
6
+ import { defillamaPrices, type PriceSource, type TokenSource } from './owner.js'
7
+ import type { Provider } from './provider.js'
8
+ import { TRON_CHAIN_ID, isTronAddress, isTronChain, toEvmAddress, toTronAddress } from './tron.js'
9
+ import { withSignal } from './util.js'
10
+
11
+ // ---------------------------------------------------------------------------
12
+ // 协议表
13
+ // ---------------------------------------------------------------------------
14
+
15
+ export type LpVersion = 'v2' | 'v3' | 'v4'
16
+
17
+ /** 一个 DEX 在一条链上的部署 */
18
+ export interface LpProtocol {
19
+ /** 协议 id,如 'uniswap-v3'、'pancakeswap-v2' */
20
+ id: string
21
+ /** 显示名,如 'Uniswap V3' */
22
+ name: string
23
+ version: LpVersion
24
+ chainId: number
25
+ /** V2:工厂合约(getPair、识别 LP 代币属于哪个协议);V3:池子的工厂合约(getPool) */
26
+ factory?: string
27
+ /** V3 / V4:仓位 NFT 合约(NonfungiblePositionManager / PositionManager) */
28
+ positionManager?: string
29
+ /** V4:StateView(读池子和仓位的状态) */
30
+ stateView?: string
31
+ /** V4:仓位合约的部署区块(深度扫描 Transfer 事件的默认起点;不知道时为 undefined) */
32
+ deployBlock?: number
33
+ }
34
+
35
+ const v2 = (id: string, name: string, chainId: number, factory: string): LpProtocol => ({ id, name, version: 'v2', chainId, factory })
36
+ const v3 = (id: string, name: string, chainId: number, positionManager: string, factory: string): LpProtocol => ({ id, name, version: 'v3', chainId, positionManager, factory })
37
+ const v4 = (chainId: number, positionManager: string, stateView: string, deployBlock?: number): LpProtocol => ({
38
+ id: 'uniswap-v4',
39
+ name: 'Uniswap V4',
40
+ version: 'v4',
41
+ chainId,
42
+ positionManager,
43
+ stateView,
44
+ ...(deployBlock !== undefined ? { deployBlock } : {}),
45
+ })
46
+
47
+ const UNI_V3_NPM = '0xC36442b4a4522E871399CD717aBDD847Ab11FE88'
48
+ const UNI_V3_FACTORY = '0x1F98431c8aD98523631AE4a59f267346ea31F984'
49
+ const CAKE_V3_NPM = '0x46A15B0b27311cedF172AB29E4f4766fbE7F4364'
50
+ const CAKE_V3_FACTORY = '0x0BFbCF9fa4f9C56B0F40a671Ad40E0805A091865'
51
+ const SUSHI_V2_FACTORY = '0xc35DADB65012eC5796536bD9864eD8773aBc74C4'
52
+
53
+ /**
54
+ * 内置的 DEX 部署(2026-10 在链上逐个核对:V3 的 name / factory、V2 的 allPairsLength)。
55
+ * 不在表里的 DEX / 链可以通过 `protocols` 选项传入自己的 LpProtocol。
56
+ */
57
+ export const LP_PROTOCOLS: readonly LpProtocol[] = [
58
+ // Ethereum
59
+ v2('uniswap-v2', 'Uniswap V2', 1, '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f'),
60
+ v2('sushiswap-v2', 'SushiSwap', 1, '0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac'),
61
+ v2('pancakeswap-v2', 'PancakeSwap V2', 1, '0x1097053Fd2ea711dad45caCcc45EfF7548fCB362'),
62
+ v3('uniswap-v3', 'Uniswap V3', 1, UNI_V3_NPM, UNI_V3_FACTORY),
63
+ v3('pancakeswap-v3', 'PancakeSwap V3', 1, CAKE_V3_NPM, CAKE_V3_FACTORY),
64
+ // BSC
65
+ v2('pancakeswap-v2', 'PancakeSwap V2', 56, '0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73'),
66
+ v2('uniswap-v2', 'Uniswap V2', 56, '0x8909Dc15e40173Ff4699343b6eB8132c65e18eC6'),
67
+ v2('sushiswap-v2', 'SushiSwap', 56, SUSHI_V2_FACTORY),
68
+ v3('pancakeswap-v3', 'PancakeSwap V3', 56, CAKE_V3_NPM, CAKE_V3_FACTORY),
69
+ v3('uniswap-v3', 'Uniswap V3', 56, '0x7b8A01B39D58278b5DE7e48c8449c9f4F5170613', '0xdB1d10011AD0Ff90774D0C6Bb92e5C5c8b4461F7'),
70
+ // Base
71
+ v2('uniswap-v2', 'Uniswap V2', 8453, '0x8909Dc15e40173Ff4699343b6eB8132c65e18eC6'),
72
+ v2('pancakeswap-v2', 'PancakeSwap V2', 8453, '0x02a84c1b3BBD7401a5f7fa98a384EBC70bB5749E'),
73
+ v3('uniswap-v3', 'Uniswap V3', 8453, '0x03a520b32C04BF3bEEf7BEb72E919cf822Ed34f1', '0x33128a8fC17869897dcE68Ed026d694621f6FDfD'),
74
+ v3('pancakeswap-v3', 'PancakeSwap V3', 8453, CAKE_V3_NPM, CAKE_V3_FACTORY),
75
+ // Arbitrum
76
+ v2('uniswap-v2', 'Uniswap V2', 42161, '0xf1D7CC64Fb4452F05c498126312eBE29f30Fbcf9'),
77
+ v2('sushiswap-v2', 'SushiSwap', 42161, SUSHI_V2_FACTORY),
78
+ v2('pancakeswap-v2', 'PancakeSwap V2', 42161, '0x02a84c1b3BBD7401a5f7fa98a384EBC70bB5749E'),
79
+ v3('uniswap-v3', 'Uniswap V3', 42161, UNI_V3_NPM, UNI_V3_FACTORY),
80
+ v3('pancakeswap-v3', 'PancakeSwap V3', 42161, CAKE_V3_NPM, CAKE_V3_FACTORY),
81
+ // Optimism
82
+ v2('uniswap-v2', 'Uniswap V2', 10, '0x0c3c1c532F1e39EdF36BE9Fe0bE1410313E074Bf'),
83
+ v3('uniswap-v3', 'Uniswap V3', 10, UNI_V3_NPM, UNI_V3_FACTORY),
84
+ // Polygon
85
+ v2('uniswap-v2', 'Uniswap V2', 137, '0x9e5A52f57b3038F1B8EeE45F28b3C1967e22799C'),
86
+ v2('sushiswap-v2', 'SushiSwap', 137, SUSHI_V2_FACTORY),
87
+ v3('uniswap-v3', 'Uniswap V3', 137, UNI_V3_NPM, UNI_V3_FACTORY),
88
+ // Avalanche
89
+ v3('uniswap-v3', 'Uniswap V3', 43114, '0x655C406EBFa14EE2006250925e54ec43AD184f8B', '0x740b1c1de25031C31FF4fC9A62f554A55cdC1baD'),
90
+ // Uniswap V4(PositionManager / StateView 在链上核对过:name、poolManager 一致)
91
+ v4(1, '0xbd216513d74c8cf14cf4747e6aaa6420ff64ee9e', '0x7ffe42c4a5deea5b0fec41c94c136cf115597227', 21689089),
92
+ v4(56, '0x7a4a5c919ae2541aed11041a1aeee68f1287f95b', '0xd13dd3d6e93f276fafc9db9e6bb47c1180aee0c4', 45970613),
93
+ v4(8453, '0x7c5f5a4bbd8fd63184577525326123b519429bdc', '0xa3c0c9b65bad0b08107aa264b0f3db444b867a71', 25350993),
94
+ v4(42161, '0xd88f38f930b7952f2db2432cb002e7abbf3dd869', '0x76fd297e2d437cd7f76d50f01afe6160f86e9990', 297842893),
95
+ v4(10, '0x3c3ea4b57a46241e54610e5f022e5c45859a1017', '0xc18a3169788f4f75a170290584eca6395c75ecdb', 130947681),
96
+ v4(137, '0x1ec2ebf4f37e7363fdfe3551602425af0b3ceef9', '0x5ea1bd7974c8a611cbab0bdcafcb1d9cc9b3ba5a', 66980394),
97
+ // Tron:SunSwap(V2 工厂的 allPairsLength、V3 仓位合约的 name / factory 在链上核对过)
98
+ v2('sunswap-v2', 'SunSwap V2', TRON_CHAIN_ID.mainnet, 'TKWJdrQkqHisa1X8HUdHEfREvTzw4pMAaY'),
99
+ v3('sunswap-v3', 'SunSwap V3', TRON_CHAIN_ID.mainnet, 'TLSWrv7eC1AZCXkRjpqMZUmvgd99cj7pPF', 'TThJt8zaJzJMhCEScH7zWKnp5buVZqys9x'),
100
+ ]
101
+
102
+ /**
103
+ * 各链常见的计价币:查 V2 LP 时,用“传入的代币 × 计价币”(以及计价币之间)在工厂合约里查池子。
104
+ * 主流池子几乎都有一边是这些币
105
+ */
106
+ export const LP_QUOTE_TOKENS: Readonly<Record<number, readonly string[]>> = {
107
+ 1: [
108
+ '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH
109
+ '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT
110
+ '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
111
+ '0x6B175474E89094C44Da98b954EedeAC495271d0F', // DAI
112
+ ],
113
+ 56: [
114
+ '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', // WBNB
115
+ '0x55d398326f99059fF775485246999027B3197955', // USDT
116
+ '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d', // USDC
117
+ '0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56', // BUSD
118
+ ],
119
+ 8453: [
120
+ '0x4200000000000000000000000000000000000006', // WETH
121
+ '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC
122
+ ],
123
+ 42161: [
124
+ '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1', // WETH
125
+ '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', // USDC
126
+ '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9', // USDT
127
+ ],
128
+ 10: [
129
+ '0x4200000000000000000000000000000000000006', // WETH
130
+ '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85', // USDC
131
+ '0x94b008aA00579c1307B0EF2c499aD98a8ce58e58', // USDT
132
+ ],
133
+ 137: [
134
+ '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270', // WPOL
135
+ '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359', // USDC
136
+ '0xc2132D05D31c914a87C6611C10748AEb04B58e8F', // USDT
137
+ ],
138
+ 43114: [
139
+ '0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7', // WAVAX
140
+ '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E', // USDC
141
+ ],
142
+ [TRON_CHAIN_ID.mainnet]: [
143
+ 'TNUC9Qb1rRpS5CbWLmNMxXBjyFoydXjWFR', // WTRX
144
+ 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', // USDT
145
+ 'TPYmHEhy5n8TCEfYGqW2rPxsghSfzghPDn', // USDD
146
+ 'TSSMHYeV2uE9qYH95DqyoCuNCzEL1NvU3S', // SUN
147
+ ],
148
+ }
149
+
150
+ // ---------------------------------------------------------------------------
151
+ // 结果类型
152
+ // ---------------------------------------------------------------------------
153
+
154
+ export interface LpToken {
155
+ address: string
156
+ /** 是否是主币(V4 的池子可以直接用主币,地址为零地址) */
157
+ native: boolean
158
+ symbol: string | null
159
+ /** 读不到精度时为 null(这时 formatted 字段也为 null) */
160
+ decimals: number | null
161
+ }
162
+
163
+ interface LpPositionBase {
164
+ chainId: number
165
+ /** 协议 id,如 'uniswap-v3';工厂不在协议表里的 V2 LP 为 'v2:<工厂地址>'(Tron 上是 T 地址;读不到工厂时为 'v2:unknown') */
166
+ protocol: string
167
+ /** 协议显示名,如 'Uniswap V3';不认识的 V2 工厂为 'Unknown V2' */
168
+ protocolName: string
169
+ /** 池子地址(V4 为 poolId) */
170
+ pool: string
171
+ token0: LpToken
172
+ token1: LpToken
173
+ /** 这个仓位对应的 token0 / token1 数量(最小单位的十进制字符串;V3 / V4 是现在全部移除能拿到的数量,不含手续费) */
174
+ amount0: string
175
+ amount1: string
176
+ /** 按精度换算后的数量;读不到精度时为 null */
177
+ amount0Formatted: string | null
178
+ amount1Formatted: string | null
179
+ /** 开启 prices 时:这个仓位的美元价值(数量不为 0 的代币没有价格时为 null);没开启时不返回 */
180
+ valueUsd?: number | null
181
+ }
182
+
183
+ /** V2(Uniswap V2 及其分叉):持有池子的 LP 代币,手续费已经计入份额,没有单独的未领取手续费 */
184
+ export interface LpV2Position extends LpPositionBase {
185
+ version: 'v2'
186
+ /** 持有的 LP 代币数量(最小单位) */
187
+ balance: string
188
+ /** LP 代币总量 */
189
+ totalSupply: string
190
+ /** 占池子的份额(0~1) */
191
+ share: number
192
+ }
193
+
194
+ /** V3 / V4(集中流动性):一个仓位 NFT */
195
+ export interface LpConcentratedPosition extends LpPositionBase {
196
+ version: 'v3' | 'v4'
197
+ /** 仓位 NFT 的 tokenId(十进制字符串) */
198
+ tokenId: string
199
+ /** 仓位 NFT 合约 */
200
+ positionManager: string
201
+ /** 手续费档位(百万分之一:500 = 0.05%);V4 动态手续费池子为当前的 lpFee */
202
+ fee: number
203
+ tickLower: number
204
+ tickUpper: number
205
+ /** 池子当前的 tick */
206
+ tick: number
207
+ /** 当前价格是否在仓位的区间内(在区间内才赚手续费) */
208
+ inRange: boolean
209
+ /** 仓位的流动性 L */
210
+ liquidity: string
211
+ /** 价格都是 token1 / token0(按精度换算,只用于展示);读不到精度时为 null */
212
+ price: number | null
213
+ priceLower: number | null
214
+ priceUpper: number | null
215
+ /** 未领取的手续费(最小单位) */
216
+ fees0: string
217
+ fees1: string
218
+ fees0Formatted: string | null
219
+ fees1Formatted: string | null
220
+ /** 开启 prices 时:未领取手续费的美元价值;没开启时不返回 */
221
+ feesUsd?: number | null
222
+ /** V4:池子的 hooks 合约(没有时为零地址) */
223
+ hooks?: string
224
+ }
225
+
226
+ export type LpPosition = LpV2Position | LpConcentratedPosition
227
+
228
+ export interface LpPositionsOptions extends CallOverrides {
229
+ /**
230
+ * 查哪些协议:协议 id(如 ['uniswap-v3', 'pancakeswap-v2']),或自己的 LpProtocol(内置表里没有的 DEX / 链)。
231
+ * 默认这条链上内置的全部协议
232
+ */
233
+ protocols?: ReadonlyArray<string | LpProtocol>
234
+ /** 只查这些类型,默认全部 */
235
+ versions?: readonly LpVersion[]
236
+ /** V2:直接指定要查的池子(LP 代币)地址,不限协议 */
237
+ pools?: readonly string[]
238
+ /** V2:用这些代币 × 计价币在工厂合约里找池子(如用户持有的代币,可以来自资产列表) */
239
+ tokens?: readonly string[]
240
+ /** V2:计价币,默认 LP_QUOTE_TOKENS 里这条链的 */
241
+ quoteTokens?: readonly string[]
242
+ /** V2:从代币来源发现 LP 代币(如 alchemy({ apiKey }) 能列出地址持有的全部 ERC20,包括 LP 代币) */
243
+ source?: TokenSource
244
+ /**
245
+ * V4:怎么找仓位。V4 的仓位合约不能按地址列出 NFT:传已知的 tokenIds,或开启 scan 扫描 Transfer 事件。
246
+ * 两个都没有时不查 V4
247
+ */
248
+ v4?: LpV4Options
249
+ /** V3 / V4:也返回已经撤出全部流动性、手续费也领完了的仓位(NFT 还在),默认不返回 */
250
+ includeClosed?: boolean
251
+ /** V3 / V4:每个协议最多核对多少个仓位 NFT(超出时取最近的:V3 按持有顺序取最后的,V4 取 tokenId 最大的),默认 500 */
252
+ maxPositions?: number
253
+ /** 美元价值(可选):true 用 DefiLlama,也可以传自己的 PriceSource。默认不查 */
254
+ prices?: boolean | PriceSource
255
+ /**
256
+ * 价格源出错时调用(服务不可用、被拦截等):这时照样返回仓位,只是 valueUsd / feesUsd 为 null,
257
+ * 用它区分“价格服务出错”和“这个代币没有价格”。回调本身出错会被忽略
258
+ */
259
+ onPriceError?: (error: unknown) => void
260
+ /** 自定义 fetch(价格、代币来源用) */
261
+ fetch?: typeof fetch
262
+ }
263
+
264
+ // ---------------------------------------------------------------------------
265
+ // ABI
266
+ // ---------------------------------------------------------------------------
267
+
268
+ const V2_FACTORY_ABI = ['function getPair(address tokenA, address tokenB) view returns (address)']
269
+ const V2_PAIR_ABI = [
270
+ 'function balanceOf(address owner) view returns (uint256)',
271
+ 'function totalSupply() view returns (uint256)',
272
+ 'function token0() view returns (address)',
273
+ 'function token1() view returns (address)',
274
+ 'function factory() view returns (address)',
275
+ 'function getReserves() view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast)',
276
+ ]
277
+ const V3_NPM_ABI = [
278
+ 'function balanceOf(address owner) view returns (uint256)',
279
+ 'function tokenOfOwnerByIndex(address owner, uint256 index) view returns (uint256)',
280
+ 'function positions(uint256 tokenId) view returns (uint96 nonce, address operator, address token0, address token1, uint24 fee, int24 tickLower, int24 tickUpper, uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, uint128 tokensOwed0, uint128 tokensOwed1)',
281
+ ]
282
+ const V3_FACTORY_ABI = ['function getPool(address tokenA, address tokenB, uint24 fee) view returns (address)']
283
+ // 只声明前几个返回值:各分叉后面的字段类型不同(如 PancakeSwap 的 feeProtocol 是 uint32),多出来的数据解码时忽略
284
+ const V3_POOL_ABI = [
285
+ 'function slot0() view returns (uint160 sqrtPriceX96, int24 tick)',
286
+ 'function feeGrowthGlobal0X128() view returns (uint256)',
287
+ 'function feeGrowthGlobal1X128() view returns (uint256)',
288
+ 'function ticks(int24 tick) view returns (uint128 liquidityGross, int128 liquidityNet, uint256 feeGrowthOutside0X128, uint256 feeGrowthOutside1X128)',
289
+ ]
290
+
291
+ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
292
+ const U128_MASK = (1n << 128n) - 1n
293
+ /** 地址统一成小写的 0x 格式再比较(Tron 的 T 地址先转换;合约调用返回的地址都是 0x 格式) */
294
+ const lower = (address: string) => (isTronAddress(address) ? toEvmAddress(address) : address).toLowerCase()
295
+
296
+ /** 选项里传给合约调用的部分(区块、取消等) */
297
+ function callOverrides(options: CallOverrides): CallOverrides {
298
+ const { blockTag, from, minBlock, signal } = options
299
+ return { ...(blockTag !== undefined ? { blockTag } : {}), ...(from !== undefined ? { from } : {}), ...(minBlock !== undefined ? { minBlock } : {}), ...(signal ? { signal } : {}) }
300
+ }
301
+
302
+ // ---------------------------------------------------------------------------
303
+ // 查询
304
+ // ---------------------------------------------------------------------------
305
+
306
+ /** 选出要查的协议 */
307
+ export function resolveLpProtocols(chainId: number, options: Pick<LpPositionsOptions, 'protocols' | 'versions'> = {}): LpProtocol[] {
308
+ const builtin = LP_PROTOCOLS.filter((p) => p.chainId === chainId)
309
+ const selected = options.protocols
310
+ ? options.protocols.flatMap((p) => {
311
+ if (typeof p !== 'string') {
312
+ return p.chainId === chainId ? [p] : []
313
+ }
314
+ const found = builtin.filter((b) => b.id === p)
315
+ if (!found.length) {
316
+ throw new Error(`Unknown LP protocol ${p} on chain ${chainId}; pass an LpProtocol object for protocols that are not built in`)
317
+ }
318
+ return found
319
+ })
320
+ : builtin
321
+ return options.versions ? selected.filter((p) => options.versions?.includes(p.version)) : selected
322
+ }
323
+
324
+ /** 解出 V2 / V3 的原始仓位(还没有代币信息) */
325
+ interface RawV2 {
326
+ version: 'v2'
327
+ protocol: LpProtocol | null
328
+ factory: string
329
+ pool: string
330
+ token0: string
331
+ token1: string
332
+ balance: bigint
333
+ totalSupply: bigint
334
+ amount0: bigint
335
+ amount1: bigint
336
+ }
337
+
338
+ interface RawConcentrated {
339
+ version: 'v3' | 'v4'
340
+ protocol: LpProtocol
341
+ pool: string
342
+ tokenId: bigint
343
+ token0: string
344
+ token1: string
345
+ fee: number
346
+ tickLower: number
347
+ tickUpper: number
348
+ tick: number
349
+ sqrtPriceX96: bigint
350
+ liquidity: bigint
351
+ amount0: bigint
352
+ amount1: bigint
353
+ fees0: bigint
354
+ fees1: bigint
355
+ hooks?: string
356
+ }
357
+
358
+ type RawPosition = RawV2 | RawConcentrated
359
+
360
+ async function v2Positions(provider: Provider, owner: string, protocols: LpProtocol[], allV2: LpProtocol[], options: LpPositionsOptions, chainId: number): Promise<RawV2[]> {
361
+ const { pools = [], tokens = [], quoteTokens = LP_QUOTE_TOKENS[chainId] ?? [], source, fetch: fetchOption } = options
362
+ const overrides = callOverrides(options)
363
+ const candidates = new Set(pools.map(lower))
364
+
365
+ // 代币来源(如 alchemy)列出的代币里可能有 LP 代币:都当作候选,下面用 token0 / token1 / factory 识别
366
+ if (source) {
367
+ const fetchFn = fetchOption ?? ((...args: Parameters<typeof fetch>) => fetch(...args))
368
+ const discovered = (await source.discover({ chainId, owner, fetch: fetchFn, provider })) ?? []
369
+ for (const token of discovered) {
370
+ candidates.add(lower(token.address))
371
+ }
372
+ }
373
+
374
+ // 传入的代币 × 计价币(以及计价币之间)在每个 V2 工厂里查池子
375
+ const quotes = [...new Set(quoteTokens.map(lower))]
376
+ const bases = [...new Set([...tokens.map(lower), ...quotes])]
377
+ const pairKeys = new Set<string>()
378
+ const getPair: Call[] = []
379
+ for (const protocol of protocols) {
380
+ const factory = provider.contract(protocol.factory as string, V2_FACTORY_ABI)
381
+ for (const base of bases) {
382
+ for (const quote of quotes) {
383
+ const key = `${lower(protocol.factory as string)}:${[base, quote].sort().join(':')}`
384
+ if (base === quote || pairKeys.has(key)) {
385
+ continue
386
+ }
387
+ pairKeys.add(key)
388
+ getPair.push(factory.getPair(base, quote))
389
+ }
390
+ }
391
+ }
392
+ for (const pair of await provider.tryAll<string>(getPair, overrides)) {
393
+ if (pair && pair !== ZERO_ADDRESS) {
394
+ candidates.add(lower(pair))
395
+ }
396
+ }
397
+ if (!candidates.size) {
398
+ return []
399
+ }
400
+
401
+ // 持有的 LP 代币
402
+ const list = [...candidates]
403
+ const balances = await provider.tryAll<bigint>(
404
+ list.map((pair) => provider.contract(pair, V2_PAIR_ABI).balanceOf(owner)),
405
+ overrides,
406
+ )
407
+ const held = list.filter((_, i) => (balances[i] ?? 0n) > 0n)
408
+ if (!held.length) {
409
+ return []
410
+ }
411
+
412
+ // 池子详情:token0 / token1 / 储备 / 总量 / 工厂(识别协议;读不到的不是 V2 池子)
413
+ const details = await provider.tryAll(
414
+ held.flatMap((pair) => {
415
+ const c = provider.contract(pair, V2_PAIR_ABI)
416
+ return [c.token0(), c.token1(), c.getReserves(), c.totalSupply(), c.factory()]
417
+ }),
418
+ overrides,
419
+ )
420
+ const selectedFactories = new Set(protocols.map((p) => lower(p.factory as string)))
421
+ return held.flatMap((pool, i): RawV2[] => {
422
+ const [token0, token1, reserves, totalSupply, factory] = details.slice(i * 5, i * 5 + 5) as [string | null, string | null, { 0: bigint; 1: bigint } | null, bigint | null, string | null]
423
+ if (!token0 || !token1 || !reserves || !totalSupply) {
424
+ return []
425
+ }
426
+ const factoryAddress = factory ? lower(factory) : ''
427
+ const protocol = allV2.find((p) => lower(p.factory as string) === factoryAddress) ?? null
428
+ // 显式传入的池子不限协议;查出来 / 来源发现的只保留选中的协议(来源里的普通代币没有 token0,上面已经去掉)
429
+ if (!pools.some((p) => lower(p) === pool) && protocol && !selectedFactories.has(factoryAddress)) {
430
+ return []
431
+ }
432
+ const balance = balances[list.indexOf(pool)] as bigint
433
+ return [
434
+ {
435
+ version: 'v2',
436
+ protocol,
437
+ factory: factoryAddress,
438
+ pool,
439
+ token0,
440
+ token1,
441
+ balance,
442
+ totalSupply,
443
+ amount0: totalSupply === 0n ? 0n : (reserves[0] * balance) / totalSupply,
444
+ amount1: totalSupply === 0n ? 0n : (reserves[1] * balance) / totalSupply,
445
+ },
446
+ ]
447
+ })
448
+ }
449
+
450
+ async function v3Positions(provider: Provider, owner: string, protocols: LpProtocol[], options: LpPositionsOptions): Promise<RawConcentrated[]> {
451
+ const { includeClosed = false } = options
452
+ const maxPositions = Math.floor(options.maxPositions ?? 500)
453
+ const overrides = callOverrides(options)
454
+ if (!protocols.length || !(maxPositions > 0)) {
455
+ return []
456
+ }
457
+
458
+ // 每个协议持有几个仓位 NFT → 逐个取 tokenId
459
+ const counts = await provider.tryAll<bigint>(
460
+ protocols.map((p) => provider.contract(p.positionManager as string, V3_NPM_ABI).balanceOf(owner)),
461
+ overrides,
462
+ )
463
+ const indexCalls: Call[] = []
464
+ const indexOwners: LpProtocol[] = []
465
+ protocols.forEach((protocol, i) => {
466
+ // 仓位很多的地址(做市机器人等)只看最近的 maxPositions 个:新铸造的 NFT 排在最后
467
+ const count = Number(counts[i] ?? 0n)
468
+ const npm = provider.contract(protocol.positionManager as string, V3_NPM_ABI)
469
+ for (let index = Math.max(0, count - maxPositions); index < count; index++) {
470
+ indexCalls.push(npm.tokenOfOwnerByIndex(owner, index))
471
+ indexOwners.push(protocol)
472
+ }
473
+ })
474
+ if (!indexCalls.length) {
475
+ return []
476
+ }
477
+ const tokenIds = await provider.tryAll<bigint>(indexCalls, overrides)
478
+ const ids = tokenIds.flatMap((id, i) => (id === null ? [] : [{ protocol: indexOwners[i] as LpProtocol, tokenId: id }]))
479
+
480
+ // 仓位详情
481
+ type PositionData = { token0: string; token1: string; fee: bigint; tickLower: bigint; tickUpper: bigint; liquidity: bigint; feeGrowthInside0LastX128: bigint; feeGrowthInside1LastX128: bigint; tokensOwed0: bigint; tokensOwed1: bigint }
482
+ const positions = await provider.tryAll<PositionData>(
483
+ ids.map(({ protocol, tokenId }) => provider.contract(protocol.positionManager as string, V3_NPM_ABI).positions(tokenId)),
484
+ overrides,
485
+ )
486
+ const open = ids
487
+ .map((item, i) => ({ ...item, data: positions[i] ?? null }))
488
+ .filter((x): x is typeof x & { data: PositionData } => x.data !== null)
489
+ .filter((x) => includeClosed || x.data.liquidity > 0n || x.data.tokensOwed0 > 0n || x.data.tokensOwed1 > 0n)
490
+ if (!open.length) {
491
+ return []
492
+ }
493
+
494
+ // 池子地址(同一个协议、同一对代币、同一档手续费只查一次)
495
+ const poolKey = (x: (typeof open)[number]) => `${x.protocol.positionManager}:${lower(x.data.token0)}:${lower(x.data.token1)}:${x.data.fee}`
496
+ const poolKeys = [...new Set(open.map(poolKey))]
497
+ const poolSamples = poolKeys.map((key) => open.find((x) => poolKey(x) === key) as (typeof open)[number])
498
+ const poolAddresses = await provider.tryAll<string>(
499
+ poolSamples.map((x) => provider.contract(x.protocol.factory as string, V3_FACTORY_ABI).getPool(x.data.token0, x.data.token1, x.data.fee)),
500
+ overrides,
501
+ )
502
+ const poolOf = new Map(poolKeys.map((key, i) => [key, poolAddresses[i] && poolAddresses[i] !== ZERO_ADDRESS ? (poolAddresses[i] as string) : null]))
503
+
504
+ // 池子状态(每个池子一次)+ 每个仓位两端 tick 的手续费增长
505
+ const pools = [...new Set([...poolOf.values()].filter((p): p is string => p !== null).map(lower))]
506
+ const poolCalls = pools.flatMap((pool) => {
507
+ const c = provider.contract(pool, V3_POOL_ABI)
508
+ return [c.slot0(), c.feeGrowthGlobal0X128(), c.feeGrowthGlobal1X128()]
509
+ })
510
+ const tickCalls = open.flatMap((x) => {
511
+ const pool = poolOf.get(poolKey(x))
512
+ if (!pool) {
513
+ return []
514
+ }
515
+ const c = provider.contract(pool, V3_POOL_ABI)
516
+ return [c.ticks(x.data.tickLower), c.ticks(x.data.tickUpper)]
517
+ })
518
+ const state = await provider.tryAll([...poolCalls, ...tickCalls], overrides)
519
+ const poolState = new Map(
520
+ pools.map((pool, i) => {
521
+ const [slot0, global0, global1] = state.slice(i * 3, i * 3 + 3) as [{ 0: bigint; 1: bigint } | null, bigint | null, bigint | null]
522
+ return [pool, slot0 && global0 !== null && global1 !== null ? { sqrtPriceX96: slot0[0], tick: Number(slot0[1]), global0, global1 } : null]
523
+ }),
524
+ )
525
+
526
+ let t = poolCalls.length
527
+ return open.flatMap((x): RawConcentrated[] => {
528
+ const pool = poolOf.get(poolKey(x))
529
+ if (!pool) {
530
+ return []
531
+ }
532
+ const lowerTick = state[t++] as { 2: bigint; 3: bigint } | null
533
+ const upperTick = state[t++] as { 2: bigint; 3: bigint } | null
534
+ const s = poolState.get(lower(pool))
535
+ if (!s || !lowerTick || !upperTick) {
536
+ return []
537
+ }
538
+ const tickLower = Number(x.data.tickLower)
539
+ const tickUpper = Number(x.data.tickUpper)
540
+ const [amount0, amount1] = getAmountsForLiquidity(s.sqrtPriceX96, getSqrtRatioAtTick(tickLower), getSqrtRatioAtTick(tickUpper), x.data.liquidity)
541
+ const inside0 = getFeeGrowthInside(s.tick, tickLower, tickUpper, s.global0, lowerTick[2], upperTick[2])
542
+ const inside1 = getFeeGrowthInside(s.tick, tickLower, tickUpper, s.global1, lowerTick[3], upperTick[3])
543
+ return [
544
+ {
545
+ version: 'v3',
546
+ protocol: x.protocol,
547
+ pool,
548
+ tokenId: x.tokenId,
549
+ token0: x.data.token0,
550
+ token1: x.data.token1,
551
+ fee: Number(x.data.fee),
552
+ tickLower,
553
+ tickUpper,
554
+ tick: s.tick,
555
+ sqrtPriceX96: s.sqrtPriceX96,
556
+ liquidity: x.data.liquidity,
557
+ amount0,
558
+ amount1,
559
+ // 与仓位合约一致:tokensOwed += uint128(新增),按 uint128 截断 / 回绕(正常仓位不会碰到)
560
+ fees0: (x.data.tokensOwed0 + (getFeesEarned(x.data.liquidity, inside0, x.data.feeGrowthInside0LastX128) & U128_MASK)) & U128_MASK,
561
+ fees1: (x.data.tokensOwed1 + (getFeesEarned(x.data.liquidity, inside1, x.data.feeGrowthInside1LastX128) & U128_MASK)) & U128_MASK,
562
+ },
563
+ ]
564
+ })
565
+ }
566
+
567
+ function reportPriceError(onPriceError: ((error: unknown) => void) | undefined, err: unknown): void {
568
+ try {
569
+ onPriceError?.(err)
570
+ } catch {
571
+ // 回调出错不影响查询结果
572
+ }
573
+ }
574
+
575
+ /** 补上代币信息(symbol / 精度)、换算数量、可选的美元价值 */
576
+ async function finalize(provider: Provider, chainId: number, raw: RawPosition[], options: LpPositionsOptions): Promise<LpPosition[]> {
577
+ const { prices, fetch: fetchOption } = options
578
+ const overrides = callOverrides(options)
579
+ const tron = isTronChain(chainId)
580
+ /** 输出的地址:Tron 上转成 T 地址(与 tronWeb 等一致) */
581
+ const out = (address: string) => (tron && /^0x[0-9a-fA-F]{40}$/.test(address) ? toTronAddress(address) : address)
582
+ /** 价格源的键:EVM 小写 0x 地址;Tron 用 T 地址(区分大小写) */
583
+ const priceKey = (address: string) => (tron ? toTronAddress(address) : lower(address))
584
+ const addresses = [...new Set(raw.flatMap((r) => [lower(r.token0), lower(r.token1)]))]
585
+ // V4 的主币池子 currency 是零地址:Provider 的 nativeTokens 不含零地址时(如配置成只认 0xeeee…),按它的第一个主币地址查主币信息
586
+ const nativeAlias = provider.isNativeToken(ZERO_ADDRESS) ? undefined : provider.nativeTokens[0]
587
+ const query = addresses.map((a) => (a === ZERO_ADDRESS && nativeAlias !== undefined ? nativeAlias : a))
588
+ const details = addresses.length ? await provider.tokens(query, { ...overrides, fields: ['symbol', 'decimals'] }) : []
589
+ const info = new Map(
590
+ addresses.map((address, i): [string, LpToken] => {
591
+ const d = details[i]
592
+ return [address, { address: out(address), native: d?.native ?? false, symbol: d?.symbol ?? null, decimals: d?.decimals ?? null }]
593
+ }),
594
+ )
595
+ const token = (address: string): LpToken => {
596
+ const t = info.get(lower(address)) as LpToken
597
+ return { ...t, address: out(address) }
598
+ }
599
+ const fmt = (value: bigint, t: LpToken) => (t.decimals === null ? null : formatUnits(value, t.decimals))
600
+
601
+ // 美元价值(可选)
602
+ const priceSource = prices === true ? defillamaPrices() : prices || null
603
+ let priceMap = new Map<string, number>()
604
+ if (priceSource && raw.length) {
605
+ const fetchFn = fetchOption ?? ((...args: Parameters<typeof fetch>) => fetch(...args))
606
+ const nativeToken = [...info.values()].find((t) => t.native)
607
+ // 价格源出错(服务不可用、被拦截等)时照样返回仓位,只是不带价格(valueUsd / feesUsd 为 null)
608
+ try {
609
+ priceMap = await priceSource.prices({
610
+ chainId,
611
+ tokens: addresses.filter((a) => !info.get(a)?.native).map((a) => (tron ? toTronAddress(a) : a)),
612
+ nativeSymbol: nativeToken ? nativeToken.symbol : null,
613
+ fetch: fetchFn,
614
+ })
615
+ } catch (err) {
616
+ // 同步抛错和 reject 都在这里接住
617
+ reportPriceError(options.onPriceError, err)
618
+ }
619
+ }
620
+ const usd = (t: LpToken, formatted: string | null) => {
621
+ const price = priceMap.get(t.native ? 'native' : priceKey(t.address))
622
+ return formatted === null || price === undefined ? null : Number(formatted) * price
623
+ }
624
+ const sum = (a: number | null, b: number | null, amountA: bigint, amountB: bigint) => {
625
+ // 数量为 0 的一边没有价格也不影响
626
+ if ((a === null && amountA > 0n) || (b === null && amountB > 0n)) {
627
+ return null
628
+ }
629
+ return (a ?? 0) + (b ?? 0)
630
+ }
631
+
632
+ const list = raw.map((r): LpPosition => {
633
+ const token0 = token(r.token0)
634
+ const token1 = token(r.token1)
635
+ const amount0Formatted = fmt(r.amount0, token0)
636
+ const amount1Formatted = fmt(r.amount1, token1)
637
+ const base = {
638
+ chainId,
639
+ // 不认识的工厂:'v2:<工厂地址>'(Tron 上是 T 地址;读不到工厂时为 'v2:unknown')
640
+ protocol: r.protocol ? r.protocol.id : `v2:${(r as RawV2).factory ? out((r as RawV2).factory as string) : 'unknown'}`,
641
+ protocolName: r.protocol ? r.protocol.name : 'Unknown V2',
642
+ pool: r.version === 'v4' ? r.pool : out(r.pool),
643
+ token0,
644
+ token1,
645
+ amount0: r.amount0.toString(),
646
+ amount1: r.amount1.toString(),
647
+ amount0Formatted,
648
+ amount1Formatted,
649
+ ...(priceSource ? { valueUsd: sum(usd(token0, amount0Formatted), usd(token1, amount1Formatted), r.amount0, r.amount1) } : {}),
650
+ }
651
+ if (r.version === 'v2') {
652
+ return {
653
+ ...base,
654
+ version: 'v2',
655
+ balance: r.balance.toString(),
656
+ totalSupply: r.totalSupply.toString(),
657
+ share: r.totalSupply === 0n ? 0 : Number((r.balance * 10n ** 18n) / r.totalSupply) / 1e18,
658
+ }
659
+ }
660
+ const fees0Formatted = fmt(r.fees0, token0)
661
+ const fees1Formatted = fmt(r.fees1, token1)
662
+ const decimalsKnown = token0.decimals !== null && token1.decimals !== null
663
+ return {
664
+ ...base,
665
+ version: r.version,
666
+ tokenId: r.tokenId.toString(),
667
+ positionManager: out(r.protocol.positionManager as string),
668
+ fee: r.fee,
669
+ tickLower: r.tickLower,
670
+ tickUpper: r.tickUpper,
671
+ tick: r.tick,
672
+ inRange: r.tick >= r.tickLower && r.tick < r.tickUpper,
673
+ liquidity: r.liquidity.toString(),
674
+ price: decimalsKnown ? sqrtPriceToPrice(r.sqrtPriceX96, token0.decimals as number, token1.decimals as number) : null,
675
+ priceLower: decimalsKnown ? tickToPrice(r.tickLower, token0.decimals as number, token1.decimals as number) : null,
676
+ priceUpper: decimalsKnown ? tickToPrice(r.tickUpper, token0.decimals as number, token1.decimals as number) : null,
677
+ fees0: r.fees0.toString(),
678
+ fees1: r.fees1.toString(),
679
+ fees0Formatted,
680
+ fees1Formatted,
681
+ ...(priceSource ? { feesUsd: sum(usd(token0, fees0Formatted), usd(token1, fees1Formatted), r.fees0, r.fees1) } : {}),
682
+ ...(r.hooks !== undefined ? { hooks: r.hooks } : {}),
683
+ }
684
+ })
685
+ // 有价格时按价值从高到低(仓位价值 + 未领取手续费),没有价格的排在后面
686
+ if (priceSource) {
687
+ const total = (p: LpPosition) => (p.valueUsd === null || p.valueUsd === undefined ? -1 : p.valueUsd + ('feesUsd' in p ? (p.feesUsd ?? 0) : 0))
688
+ list.sort((a, b) => total(b) - total(a))
689
+ }
690
+ return list
691
+ }
692
+
693
+ /**
694
+ * 查一个地址的 LP 仓位:V2(持有的 LP 代币)、V3 / V4(仓位 NFT)。所有合约读取都用 multicall 批量完成。
695
+ *
696
+ * ```ts
697
+ * await lpPositions(multi, user) // 这条链上内置的全部协议
698
+ * await lpPositions(multi, user, { tokens: heldTokens, prices: true }) // V2 用持有的代币找池子,带美元价值
699
+ * ```
700
+ */
701
+ export function lpPositions(provider: Provider, owner: string, options: LpPositionsOptions = {}): Promise<LpPosition[]> {
702
+ // 取消时立即 reject(扫描 / 价格等进行中的请求不中断,结果丢弃)
703
+ return withSignal(options.signal, () => queryLpPositions(provider, owner, options))
704
+ }
705
+
706
+ async function queryLpPositions(provider: Provider, owner: string, options: LpPositionsOptions): Promise<LpPosition[]> {
707
+ const chainId = await provider.getChainId()
708
+ const protocols = resolveLpProtocols(chainId, options)
709
+ const allV2 = [...LP_PROTOCOLS.filter((p) => p.chainId === chainId && p.version === 'v2'), ...protocols.filter((p) => p.version === 'v2')]
710
+ const v2Selected = protocols.filter((p) => p.version === 'v2')
711
+ const v3Selected = protocols.filter((p) => p.version === 'v3')
712
+ const v4Selected = protocols.filter((p) => p.version === 'v4')
713
+ const wantV2 = !options.versions || options.versions.includes('v2')
714
+ const wantV4 = v4Selected.length > 0 && !!(options.v4?.tokenIds?.length || options.v4?.scan)
715
+ const [v2List, v3List, v4List] = await Promise.all([
716
+ wantV2 && (v2Selected.length || options.pools?.length || options.source) ? v2Positions(provider, owner, v2Selected, allV2, options, chainId) : Promise.resolve([]),
717
+ v3Positions(provider, owner, v3Selected, options),
718
+ wantV4 ? v4Positions(provider, owner, v4Selected, options.v4 ?? {}, options.includeClosed ?? false, options.maxPositions ?? 500, callOverrides(options)) : Promise.resolve([]),
719
+ ])
720
+ return finalize(provider, chainId, [...v2List, ...v3List, ...v4List], options)
721
+ }