@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.
- package/CHANGELOG.md +38 -0
- package/README.md +240 -4
- package/contracts/TokenProbe.sol +103 -0
- package/dist/aggregate.d.ts.map +1 -1
- package/dist/aggregate.js +114 -3
- package/dist/aggregate.js.map +1 -1
- package/dist/approvals.d.ts +169 -0
- package/dist/approvals.d.ts.map +1 -0
- package/dist/approvals.js +310 -0
- package/dist/approvals.js.map +1 -0
- package/dist/chains.d.ts.map +1 -1
- package/dist/chains.js +4 -0
- package/dist/chains.js.map +1 -1
- package/dist/fees.d.ts +97 -0
- package/dist/fees.d.ts.map +1 -0
- package/dist/fees.js +115 -0
- package/dist/fees.js.map +1 -0
- package/dist/http.d.ts +10 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +30 -0
- package/dist/http.js.map +1 -0
- package/dist/liquidity/common.d.ts +72 -0
- package/dist/liquidity/common.d.ts.map +1 -0
- package/dist/liquidity/common.js +271 -0
- package/dist/liquidity/common.js.map +1 -0
- package/dist/liquidity/concentrated.d.ts +19 -0
- package/dist/liquidity/concentrated.d.ts.map +1 -0
- package/dist/liquidity/concentrated.js +106 -0
- package/dist/liquidity/concentrated.js.map +1 -0
- package/dist/liquidity/index.d.ts +42 -0
- package/dist/liquidity/index.d.ts.map +1 -0
- package/dist/liquidity/index.js +143 -0
- package/dist/liquidity/index.js.map +1 -0
- package/dist/liquidity/types.d.ts +182 -0
- package/dist/liquidity/types.d.ts.map +1 -0
- package/dist/liquidity/types.js +2 -0
- package/dist/liquidity/types.js.map +1 -0
- package/dist/liquidity/v2.d.ts +5 -0
- package/dist/liquidity/v2.d.ts.map +1 -0
- package/dist/liquidity/v2.js +342 -0
- package/dist/liquidity/v2.js.map +1 -0
- package/dist/liquidity/v3.d.ts +6 -0
- package/dist/liquidity/v3.d.ts.map +1 -0
- package/dist/liquidity/v3.js +310 -0
- package/dist/liquidity/v3.js.map +1 -0
- package/dist/liquidity/v4.d.ts +5 -0
- package/dist/liquidity/v4.d.ts.map +1 -0
- package/dist/liquidity/v4.js +279 -0
- package/dist/liquidity/v4.js.map +1 -0
- package/dist/lp.d.ts +10 -2
- package/dist/lp.d.ts.map +1 -1
- package/dist/lp.js +31 -19
- package/dist/lp.js.map +1 -1
- package/dist/lpMath.d.ts +55 -0
- package/dist/lpMath.d.ts.map +1 -1
- package/dist/lpMath.js +209 -0
- package/dist/lpMath.js.map +1 -1
- package/dist/multicall.d.ts.map +1 -1
- package/dist/multicall.js +3 -0
- package/dist/multicall.js.map +1 -1
- package/dist/names.d.ts +55 -0
- package/dist/names.d.ts.map +1 -0
- package/dist/names.js +93 -0
- package/dist/names.js.map +1 -0
- package/dist/owner.d.ts +4 -26
- package/dist/owner.d.ts.map +1 -1
- package/dist/owner.js +7 -112
- package/dist/owner.js.map +1 -1
- package/dist/portfolio.d.ts +54 -0
- package/dist/portfolio.d.ts.map +1 -0
- package/dist/portfolio.js +49 -0
- package/dist/portfolio.js.map +1 -0
- package/dist/price.d.ts +95 -0
- package/dist/price.d.ts.map +1 -0
- package/dist/price.js +329 -0
- package/dist/price.js.map +1 -0
- package/dist/risk.d.ts +42 -0
- package/dist/risk.d.ts.map +1 -0
- package/dist/risk.js +134 -0
- package/dist/risk.js.map +1 -0
- package/dist/rpcNodes.d.ts.map +1 -1
- package/dist/rpcNodes.js +18 -0
- package/dist/rpcNodes.js.map +1 -1
- package/dist/subpaths/approvals.d.ts +26 -0
- package/dist/subpaths/approvals.d.ts.map +1 -0
- package/dist/subpaths/approvals.js +27 -0
- package/dist/subpaths/approvals.js.map +1 -0
- package/dist/subpaths/fees.d.ts +23 -0
- package/dist/subpaths/fees.d.ts.map +1 -0
- package/dist/subpaths/fees.js +25 -0
- package/dist/subpaths/fees.js.map +1 -0
- package/dist/subpaths/liquidity.d.ts +24 -0
- package/dist/subpaths/liquidity.d.ts.map +1 -0
- package/dist/subpaths/liquidity.js +21 -0
- package/dist/subpaths/liquidity.js.map +1 -0
- package/dist/subpaths/lp.d.ts +1 -1
- package/dist/subpaths/lp.d.ts.map +1 -1
- package/dist/subpaths/lp.js +1 -1
- package/dist/subpaths/lp.js.map +1 -1
- package/dist/subpaths/names.d.ts +35 -0
- package/dist/subpaths/names.d.ts.map +1 -0
- package/dist/subpaths/names.js +39 -0
- package/dist/subpaths/names.js.map +1 -0
- package/dist/subpaths/price.d.ts +38 -0
- package/dist/subpaths/price.d.ts.map +1 -0
- package/dist/subpaths/price.js +40 -0
- package/dist/subpaths/price.js.map +1 -0
- package/dist/subpaths/risk.d.ts +19 -0
- package/dist/subpaths/risk.d.ts.map +1 -0
- package/dist/subpaths/risk.js +20 -0
- package/dist/subpaths/risk.js.map +1 -0
- package/dist/subpaths/swap.d.ts +19 -0
- package/dist/subpaths/swap.d.ts.map +1 -0
- package/dist/subpaths/swap.js +20 -0
- package/dist/subpaths/swap.js.map +1 -0
- package/dist/swap.d.ts +128 -0
- package/dist/swap.d.ts.map +1 -0
- package/dist/swap.js +291 -0
- package/dist/swap.js.map +1 -0
- package/dist/tokenProbe.d.ts +3 -0
- package/dist/tokenProbe.d.ts.map +1 -0
- package/dist/tokenProbe.js +4 -0
- package/dist/tokenProbe.js.map +1 -0
- package/dist/write/tron.d.ts +27 -0
- package/dist/write/tron.d.ts.map +1 -1
- package/dist/write/tron.js +30 -0
- package/dist/write/tron.js.map +1 -1
- package/package.json +32 -1
- package/src/aggregate.ts +110 -3
- package/src/approvals.ts +462 -0
- package/src/chains.ts +4 -0
- package/src/fees.ts +201 -0
- package/src/http.ts +34 -0
- package/src/liquidity/common.ts +314 -0
- package/src/liquidity/concentrated.ts +121 -0
- package/src/liquidity/index.ts +171 -0
- package/src/liquidity/types.ts +194 -0
- package/src/liquidity/v2.ts +371 -0
- package/src/liquidity/v3.ts +382 -0
- package/src/liquidity/v4.ts +348 -0
- package/src/lp.ts +39 -19
- package/src/lpMath.ts +223 -0
- package/src/multicall.ts +3 -0
- package/src/names.ts +141 -0
- package/src/owner.ts +7 -138
- package/src/portfolio.ts +91 -0
- package/src/price.ts +415 -0
- package/src/risk.ts +166 -0
- package/src/rpcNodes.ts +18 -0
- package/src/subpaths/approvals.ts +48 -0
- package/src/subpaths/fees.ts +43 -0
- package/src/subpaths/liquidity.ts +52 -0
- package/src/subpaths/lp.ts +20 -1
- package/src/subpaths/names.ts +43 -0
- package/src/subpaths/price.ts +60 -0
- package/src/subpaths/risk.ts +23 -0
- package/src/subpaths/swap.ts +36 -0
- package/src/swap.ts +413 -0
- package/src/tokenProbe.ts +5 -0
- package/src/write/tron.ts +48 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
### 新功能
|
|
6
|
+
|
|
7
|
+
- 价格,子路径 `@w3lib/evm-batch-call/price`:
|
|
8
|
+
- `getTokenPrices` / `tokenPrices`:代币(含主币)的美元价格,依次使用多个价格源,结果带给出价格的源
|
|
9
|
+
- `onchainPrices()`:从内置协议表的 V2 / V3 池子算价格(对包装主币、稳定币,用最深的池子,流动性下限 `minLiquidityUsd`),也支持 Tron 的 SunSwap
|
|
10
|
+
- `defillamaPrices({ at })`:历史价格
|
|
11
|
+
- `combinePrices([...])`:组合多个价格源,可以传给 `getLpPositions` / `getOwnerTokens` 的 `prices`
|
|
12
|
+
- `getPortfolio` / `portfolio`:地址的资产总值(传入代币的余额 × 价格,可选加上 LP 仓位)
|
|
13
|
+
- 兑换,子路径 `@w3lib/evm-batch-call/swap`:`getSwapQuote` / `buildSwap`(V2 / V3 池子的直接和两跳路线,一次 multicall 报价,选输出最多的,按滑点算最少收到、价格影响)、`swap`(授权 + 兑换,主币自动包装 / 解包),Tron 上是 SunSwap V2
|
|
14
|
+
- `swap` 有授权时,授权确认后重新报价再发兑换(deadline、最少收到从发兑换那一刻算);`tax` 选项:V2 报价不含转账税,带税代币的最少收到要再乘 (1 − tax)
|
|
15
|
+
- 代币风险检查,子路径 `@w3lib/evm-batch-call/risk`:`getTokenRisks` / `tokenRisks`,在 `eth_call` 里用 deployless 的探测合约(`contracts/TokenProbe.sol`)模拟买入 / 转账 / 卖出,算出买入税、转账税、卖出税,识别貔貅盘;检查 EIP-1967 代理和 owner
|
|
16
|
+
- 手续费估算,子路径 `@w3lib/evm-batch-call/fees`:`getFeeEstimate` / `estimateFees`(按 `eth_feeHistory` 的三档 EIP-1559 费用,不支持时按 `eth_gasPrice`)、`getCostEstimate` / `estimateCost`(一笔交易的手续费,含 OP Stack / Scroll 的 L1 数据费;Tron 按能量、带宽和账户剩余资源算要烧的 TRX,含激活费)
|
|
17
|
+
- 域名解析,子路径 `@w3lib/evm-batch-call/names`:`getNameAddresses` / `resolveNames`(名字 → 地址)、`getAddressNames` / `lookupAddresses`(地址 → 主名字,正查核对),内置 ENS、SPACE ID(.bnb)、Basenames(.base.eth),名字按 ENSIP-15 规范化
|
|
18
|
+
- 授权管理,子路径 `@w3lib/evm-batch-call/approvals`:
|
|
19
|
+
- `getApprovals` / `approvals`:批量查 ERC20 额度、Permit2 额度(含过期时间)、NFT 的 setApprovalForAll;默认检查 `knownSpenders(chainId)`(内置协议的 Router / 仓位合约、Uniswap / PancakeSwap Router、1inch、0x、ParaSwap、Permit2、OpenSea / Blur)
|
|
20
|
+
- `scanApprovals`:按 Approval / ApprovalForAll / Permit2 事件增量发现授权过的代币和 NFT 合约,进度可以保存
|
|
21
|
+
- `buildRevokes` / `revokeApprovals`:批量撤销(approve 0、Permit2 lockdown、setApprovalForAll false),发送同 `batchCall`,支持 7702 合成一笔
|
|
22
|
+
- Tron 支持查询和撤销(没有 Permit2,不支持扫描)
|
|
23
|
+
- 价格源的 `PriceSourceContext` 新增 `provider`(链上价格源用);`defillamaPrices`、`PriceSource` 仍可以从 `/owner`、`/lp` 导入
|
|
24
|
+
- 批量查询的链:Multicall3 地址表和内置节点补充 Conflux eSpace(1030)、Injective EVM(1776)、Pharos(1672,主币 PROS),星空链(202308)只有节点(没有 Multicall3,走 deployless);FSC(201022)补主币 FON
|
|
25
|
+
- deployless 不可用(合约创建式 `eth_call` 返回空数据 / 无法解码)时,自动改成逐条 `eth_call`(主币余额用 `eth_getBalance`,区块号用 `getBlockNumber`);确认不支持后记住 10 分钟,不再先试一次注定失败的 deployless
|
|
26
|
+
|
|
27
|
+
## 0.6.0
|
|
28
|
+
|
|
29
|
+
### 新功能
|
|
30
|
+
|
|
31
|
+
- 添加 / 移除流动性,子路径 `@w3lib/evm-batch-call/liquidity`:
|
|
32
|
+
- `buildAddLiquidity` / `buildRemoveLiquidity` / `buildCollectFees` 只构建(报价 + 按顺序发送的交易,授权在前),`addLiquidity` / `removeLiquidity` / `collectFees` 构建后签名发送(钱包同 `/write`)
|
|
33
|
+
- V2:Router 的 `addLiquidity(ETH)` / `removeLiquidity(ETH)`,数量与 Router 的取整逐位一致;V3:`mint` / `increaseLiquidity` / `decreaseLiquidity` + `collect`(+ `burn`),池子不存在时可传 `initialPrice` 创建;V4:PositionManager 的 actions + Permit2 授权,主币池子
|
|
34
|
+
- 新建仓位按价格区间(tokenB / tokenA)、tick 或全区间,给一边的数量自动算另一边;往已有仓位追加、按比例移除可以直接传 `getLpPositions` 的结果
|
|
35
|
+
- 主币:V2 / V3 的包装币那一边可以用主币支付 / 收取;滑点按价格范围算 V3 的 `amountMin`、V4 的 `amountMax` 和移除的最小数量
|
|
36
|
+
- Tron 的 SunSwap V2 / V3 同样支持
|
|
37
|
+
- `LpProtocol.router`:V2 协议的 Router(内置表里的 V2 协议都已填好、在链上核对过)。SunSwap V2 用 SunSwap 网页当前使用的 Router
|
|
38
|
+
- `LpProtocol.protocolFee`:V2 协议手续费给 feeTo 增发的比例(`'1/6'` / `'8/25'`),添加 / 移除时按它算 LP 总量
|
|
39
|
+
- `/lp` 的数学工具新增 `getLiquidityForAmounts`、`getMintAmounts`、`getTickAtSqrtRatio`、`priceToTick`、`priceToSqrtPriceX96`、`nearestUsableTick` 等(也从 `/liquidity` 导出)
|
|
40
|
+
|
|
3
41
|
## 0.5.0
|
|
4
42
|
|
|
5
43
|
### 新功能
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
pnpm add @w3lib/evm-batch-call ethers
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
**在线 demo**:<https://web3-lib.github.io/libs/evm/>(余额、代币信息、资产列表、LP
|
|
9
|
+
**在线 demo**:<https://web3-lib.github.io/libs/evm/>(余额、代币信息、资产列表、LP 仓位、添加 / 移除流动性(报价,连接浏览器钱包后可以发送)、NFT、价格 / 资产总值、授权(批量查询 + 撤销)、域名(ENS / SPACE ID / Basenames 正反向解析)、手续费估算、兑换(报价,连接浏览器钱包后可以发送,可填转账税)、代币风险(买卖税 / 貔貅检查),8 条链含 Tron,显示每次查询实际发出的节点请求数)。本地运行:`pnpm --filter @w3lib/evm-batch-call demo`(http://localhost:5174)
|
|
10
10
|
|
|
11
11
|
## 快速上手
|
|
12
12
|
|
|
@@ -53,8 +53,9 @@ await getErc1155Balances(user, [{ contract: ITEMS, tokenId: 7 }], { chainId: 137
|
|
|
53
53
|
| `getErc1155Balances(owner, items, opts)` | `erc1155Balances(multi, owner, items)` | **`/nft`**。ERC1155 余额 |
|
|
54
54
|
| `getOwnerTokens(owner, opts)` | `ownerTokens(multi, owner, opts)` | **子路径 `/owner`**。资产列表:地址持有的代币(**不推荐使用**,结果不保证完整,见[资产列表](#资产列表)) |
|
|
55
55
|
| `getLpPositions(owner, opts)` | `lpPositions(multi, owner, opts)` | **子路径 `/lp`**。LP 仓位:V2 的 LP 代币、V3 / V4 的仓位 NFT,代币数量、未领取手续费、可选美元价值,见 [LP 仓位](#lp-仓位) |
|
|
56
|
+
| | `addLiquidity` / `removeLiquidity` / `collectFees`(`build*` 只构建不发送) | **子路径 `/liquidity`**。添加 / 移除流动性、领取手续费,见[添加 / 移除流动性](#添加--移除流动性) |
|
|
56
57
|
|
|
57
|
-
**子路径**:NFT 查询在 `@w3lib/evm-batch-call/nft`,资产列表(含代币来源、价格源、Transfer 增量扫描)在 `@w3lib/evm-batch-call/owner`,LP 仓位在 `@w3lib/evm-batch-call/lp`,批量写操作在 `@w3lib/evm-batch-call/write`。不用这些功能时打包不会带上它们的代码(拆出后约少 15KB:只用 `getBalances` 时 minify 后约 50KB、gzip 约 17KB,均不含 ethers)。
|
|
58
|
+
**子路径**:NFT 查询在 `@w3lib/evm-batch-call/nft`,资产列表(含代币来源、价格源、Transfer 增量扫描)在 `@w3lib/evm-batch-call/owner`,LP 仓位在 `@w3lib/evm-batch-call/lp`,添加 / 移除流动性在 `@w3lib/evm-batch-call/liquidity`,批量写操作在 `@w3lib/evm-batch-call/write`。不用这些功能时打包不会带上它们的代码(拆出后约少 15KB:只用 `getBalances` 时 minify 后约 50KB、gzip 约 17KB,均不含 ethers)。
|
|
58
59
|
|
|
59
60
|
共同的规则:
|
|
60
61
|
|
|
@@ -275,7 +276,7 @@ await getLpPositions(user, { chainId: 1, v4: { scan: { storage } } }) //
|
|
|
275
276
|
| Avalanche | | Uniswap | |
|
|
276
277
|
| Tron | SunSwap | SunSwap | |
|
|
277
278
|
|
|
278
|
-
地址都在链上核对过(2026-10)。其他 DEX / 链传自己的协议:`protocols: [{ id: 'my-dex', name: 'My DEX', version: 'v3', chainId, positionManager, factory }]`(V2 只需要 `factory`;V3 需要 `positionManager` 和 `factory`,ABI 与 Uniswap V3 相同的分叉都可以;V4 需要 `positionManager` 和 `stateView`,`deployBlock` 可选)。`protocols: ['uniswap-v3']` 只查某些协议,`versions: ['v3']` 只查某些类型。
|
|
279
|
+
地址都在链上核对过(2026-10)。其他 DEX / 链传自己的协议:`protocols: [{ id: 'my-dex', name: 'My DEX', version: 'v3', chainId, positionManager, factory }]`(V2 只需要 `factory`,添加 / 移除流动性还要 `router`;V3 需要 `positionManager` 和 `factory`,ABI 与 Uniswap V3 相同的分叉都可以;V4 需要 `positionManager` 和 `stateView`,`deployBlock` 可选)。`protocols: ['uniswap-v3']` 只查某些协议,`versions: ['v3']` 只查某些类型。
|
|
279
280
|
|
|
280
281
|
### 每种类型怎么查、能不能查全
|
|
281
282
|
|
|
@@ -298,15 +299,246 @@ await getLpPositions(user, { chainId: 1, v4: { scan: { storage } } }) //
|
|
|
298
299
|
|
|
299
300
|
**局限**:PancakeSwap 等质押到农场(MasterChef)的仓位 NFT 不在用户名下,查不到;V2 只能尽量查(见上表);V4 需要传 `tokenIds` 或开启扫描。
|
|
300
301
|
|
|
302
|
+
## 添加 / 移除流动性
|
|
303
|
+
|
|
304
|
+
子路径 `@w3lib/evm-batch-call/liquidity`:V2(通过 Router)、V3(仓位 NFT)、Uniswap V4(PositionManager + Permit2),协议与 [LP 仓位](#内置的协议) 的内置表相同(含 Tron 的 SunSwap V2 / V3)。
|
|
305
|
+
|
|
306
|
+
- `buildAddLiquidity` / `buildRemoveLiquidity` / `buildCollectFees`:只读取、不签名,返回预计的数量和要按顺序发送的交易(授权在前)。可以当作报价(输入一边的数量,看另一边要多少),也可以把 `transactions` 交给钱包自己发送
|
|
307
|
+
- `addLiquidity` / `removeLiquidity` / `collectFees`:构建后签名发送,钱包与 `/write` 的 `batchCall` 相同(私钥、ethers Signer、浏览器钱包;Tron 上还可以是 tronWeb / tronwallet-adapter)。授权不够时先发授权交易,等它确认后再发主交易;授权失败时不发主交易
|
|
308
|
+
|
|
309
|
+
```ts
|
|
310
|
+
import { NATIVE_TOKEN, Provider } from '@w3lib/evm-batch-call'
|
|
311
|
+
import { getLpPositions } from '@w3lib/evm-batch-call/lp'
|
|
312
|
+
import { addLiquidity, buildAddLiquidity, collectFees, removeLiquidity } from '@w3lib/evm-batch-call/liquidity'
|
|
313
|
+
|
|
314
|
+
const multi = new Provider(56, rpc)
|
|
315
|
+
|
|
316
|
+
// 报价:新建 V3 仓位,价格区间是“1 个 tokenA 值多少个 tokenB”,给一边的数量
|
|
317
|
+
const plan = await buildAddLiquidity(multi, owner, {
|
|
318
|
+
protocol: 'pancakeswap-v3', tokenA: WBNB, tokenB: USDT, fee: 500,
|
|
319
|
+
priceLower: 550, priceUpper: 650, // 或 fullRange: true,或 tickLower / tickUpper
|
|
320
|
+
amountAFormatted: '1', // 或 amountA(最小单位);也可以两边都给
|
|
321
|
+
})
|
|
322
|
+
// { version: 'v3', token0, token1, tickLower, tickUpper, tick, price, liquidity,
|
|
323
|
+
// amount0Formatted, amount1Formatted, // 按当前价格预计存入的数量
|
|
324
|
+
// amount0Max, amount1Max, native: null, value: '0',
|
|
325
|
+
// transactions: [{ kind: 'approve', to: USDT, data, value: '0', token, spender, amount }, …, { kind: 'add', to: positionManager, data, value }] }
|
|
326
|
+
|
|
327
|
+
// 发送:主币直接用 NATIVE_TOKEN(V2 / V3 换成包装币、附带主币支付;V4 是主币池子)
|
|
328
|
+
const { status, tokenId, results } = await addLiquidity(multi, privateKey, {
|
|
329
|
+
protocol: 'uniswap-v2', tokenA: NATIVE_TOKEN, tokenB: USDT, amountAFormatted: '0.5',
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
// 已有仓位:直接用 getLpPositions 的结果(或 { protocol, tokenId } / { pool })
|
|
333
|
+
const [position] = await getLpPositions(owner, { chainId: 56 })
|
|
334
|
+
await addLiquidity(multi, privateKey, { position, amount0Formatted: '0.1' }) // 追加,另一边按当前价格算
|
|
335
|
+
await removeLiquidity(multi, privateKey, { position, percent: 50 }) // 按比例移除(V3 / V4 同时领取全部手续费)
|
|
336
|
+
await removeLiquidity(multi, privateKey, { position, burn: true }, { native: true }) // 全部移除、销毁 NFT,包装币换成主币
|
|
337
|
+
await collectFees(multi, privateKey, { position }) // 只领取手续费
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
| | V2 | V3 | V4 |
|
|
341
|
+
| --- | --- | --- | --- |
|
|
342
|
+
| 新建 / 追加 | Router `addLiquidity(ETH)`,池子不存在时创建(两边数量都要给) | `mint` / `increaseLiquidity`;池子不存在时传 `initialPrice` 创建 | `MINT_POSITION` / `INCREASE_LIQUIDITY`;池子没初始化时传 `initialPrice` |
|
|
343
|
+
| 移除 | Router `removeLiquidity(ETH)`,先授权 LP 代币 | `decreaseLiquidity` + `collect`(+ `burn`),用 multicall 合成一笔 | `DECREASE_LIQUIDITY` / `BURN_POSITION` + `TAKE_PAIR` |
|
|
344
|
+
| 授权 | 代币授权给 Router | 代币授权给仓位合约 | 代币授权给 Permit2,再在 Permit2 里授权给仓位合约(30 天有效) |
|
|
345
|
+
| 主币 | `native: true` 或 `tokenA` 传主币:包装币那一边用主币 | 同 V2(`refundETH` / `unwrapWETH9` + `sweepToken`) | 主币池子(`currency0` 是零地址)直接用主币 |
|
|
346
|
+
| 手续费 | 已计入 LP 份额 | 追加不领取;移除时一并领取,`collectFees` 单独领取 | 修改流动性时自动领取(计划里的 `fees0` / `fees1`) |
|
|
347
|
+
|
|
348
|
+
选项(构建和发送都可以传):
|
|
349
|
+
|
|
350
|
+
- `slippage`:价格滑点,默认 `0.005`(0.5%)。V2 是数量 × (1 − 滑点);V3 / V4 按价格在 `[p × (1 − s), p × (1 + s)]` 内变动时的数量算:V3 添加给出 `amountMin`(三个价格下合约实际会用掉的最小值),V4 添加给出 `amountMax`(会多授权 / 附带一点,用不完的不扣、主币退回),移除给出 `amount0Min` / `amount1Min`
|
|
351
|
+
- `deadline`:交易有效期(秒),默认 1200;`recipient`:收到 LP 代币 / NFT / 移除的代币的地址,默认发送者
|
|
352
|
+
- `approve`:`'exact'`(默认,本次需要的数量)或 `'max'`。已有非零但不够的额度直接改成新额度;只有 Ethereum 上的 USDT 等要求先归零的代币才先 approve 0(有的代币反过来不允许 approve 0)
|
|
353
|
+
- `native`:V2 / V3 的包装币那一边用主币支付 / 收取(新建时 `tokenA` / `tokenB` 传主币地址会自动开启)
|
|
354
|
+
- `protocols`:内置表里没有的协议(自己的 `LpProtocol`,V2 要带 `router`);`hookData`:V4 传给 hooks 的数据
|
|
355
|
+
- 发送时还有 `/write` 的发送参数(`wait`、`timeout`、`gasPrice`、`signal`、`onUpdate`…)和 `checkBalance`(默认 true:代币按预计数量、主币按 `value` 检查余额,不够时直接报错,不发交易)
|
|
356
|
+
|
|
357
|
+
数值:
|
|
358
|
+
|
|
359
|
+
- **流动性**:V3 / V4 的 `liquidity` 与合约算出来的逐位一致;V2 的 LP 数量和移除数量已按协议手续费算过(`feeTo` 开启时每次添加 / 移除前先给手续费地址增发 LP:比例看 `LpProtocol.protocolFee`:内置的 Uniswap / SushiSwap / SunSwap 是 `'1/6'`,PancakeSwap 是 `'8/25'`;自定义协议没填时按 `'8/25'` 偏保守地算),Uniswap V2 和 SunSwap V2 上与实际逐位一致
|
|
360
|
+
- **数量**:添加时 `amount0` / `amount1` 是按当前价格实际会用掉的(V2 与 Router 的取整一致;给了的一边可能因为取整少用几 wei),`amount0Max` / `amount1Max` 是传给合约的上限;移除时是按当前价格能拿到的(不含手续费),`fees0` / `fees1` 是一并领取的手续费
|
|
361
|
+
- **区间**:价格按 tokenB / tokenA 给,换算成池子的 tick(token1 / token0 方向,tokenA 是 token1 时自动取倒数),再取最近的可用 tick(`tickSpacing` 的整数倍);计划里的 `priceLower` / `priceUpper` 是取整后的实际区间(token1 / token0)。价格在区间外时只需要一种代币,给错了会报错并说明需要哪一种
|
|
362
|
+
- **新仓位的 tokenId**:发送后从回执里读(EVM 上主交易 confirmed 时);Tron 上为 null,用 `getLpPositions` 查
|
|
363
|
+
- 往已有仓位追加、移除、领取都会先确认发送者是仓位的所有者(V3 也可以是被授权的 operator),不是时构建就报错(V3 的合约允许任何人往别人的仓位里加流动性,tokenId 写错时代币会白送给别人)
|
|
364
|
+
- **V4 的上限可能超过给的数量**:V4 直接指定流动性,`amountMax` 是价格在滑点范围内最不利时需要的数量(与 Uniswap 的 SDK 相同),授权和附带的主币按它给,实际只扣按成交价格算的部分、主币多余的退回。主币池子要求余额够 `value`(= `amount0Max`),给的数量接近全部余额时请留出这部分或调小滑点。V3 传给合约的就是给的数量,不会超出
|
|
365
|
+
- `dryRun: true` 时如果需要授权,主交易会预估失败(授权还没上链);只想看数量用 `build*`
|
|
366
|
+
|
|
367
|
+
核对方式:EVM 在 Base 主网分叉(anvil)上真实发交易,覆盖 Uniswap V2 / V3 / V4 和 PancakeSwap V3 的新建、追加、部分移除、全部移除并销毁;Tron 以近期真实用户的身份在主网预执行(SunSwap V2 添加 / 移除、V3 移除 / 追加),结果与计划逐位核对。
|
|
368
|
+
|
|
369
|
+
**局限**:手续费转账代币(fee-on-transfer)不支持;V4 的 hooks 如果额外收取 / 返还代币,计划里的数量不含这部分(交易仍按 `amountMax` / `amountMin` 保护);PancakeSwap 质押到农场的仓位要先取出。
|
|
370
|
+
|
|
371
|
+
打包:在已经用到 `Provider` 的基础上,只用 `build*` 约增加 40KB(minify,gzip 约 13KB),加上发送约 77KB(gzip 约 26KB,其中大部分是 `/write` 的发送代码),均不含 ethers。
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
## 兑换
|
|
375
|
+
|
|
376
|
+
子路径 `@w3lib/evm-batch-call/swap`:在内置协议表的 V2 / V3 池子里找最好的路线,报价并构建 / 发送兑换交易(Tron 上是 SunSwap V2)。
|
|
377
|
+
|
|
378
|
+
```ts
|
|
379
|
+
import { NATIVE_TOKEN } from '@w3lib/evm-batch-call'
|
|
380
|
+
import { getSwapQuote, swap } from '@w3lib/evm-batch-call/swap'
|
|
381
|
+
|
|
382
|
+
const plan = await getSwapQuote(user, { tokenIn: NATIVE_TOKEN, tokenOut: USDT, amountInFormatted: '0.1' }, { chainId: 56 })
|
|
383
|
+
plan.amountOutFormatted // '72.6'
|
|
384
|
+
plan.route // { protocol: 'pancakeswap-v3', version: 'v3', path: [WBNB, USDT], fees: [100], … }
|
|
385
|
+
plan.transactions // 授权(卖出代币且额度不够时)+ 兑换
|
|
386
|
+
|
|
387
|
+
await swap(provider, key, { tokenIn: USDT, tokenOut: NATIVE_TOKEN, amountInFormatted: '100' }, { slippage: 0.01 })
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
- **路线**:直接兑换,加上经过包装主币 / 稳定币(`LP_QUOTE_TOKENS`,可用 `intermediates` 改)的两跳;V2 的每个协议、V3 的每个手续费档位都算一条候选。V3 先查哪些池子存在、当前流动性(没有流动性的不算,两跳时每一跳只用流动性最大的 2 个档位),然后批量报价(V2 用 Router 的 `getAmountsOut`,一次报完;QuoterV2 很费 gas,每 3 个一组并发,避免超过节点的 gas 上限),选输出最多的。`routes` 里是全部候选的报价
|
|
391
|
+
- **最少收到**:`amountOutMin = 报价 × (1 − slippage)`(默认 0.5%),交易里由 Router 检查;`priceImpact` 是这笔的价格比极小数量的价格差多少
|
|
392
|
+
- **带转账税的代币**:V2 的报价(`getAmountsOut`)不含转账税,实际收到的更少,默认滑点下会 `INSUFFICIENT_OUTPUT_AMOUNT` 回滚。用 `tax`(如 `0.05`)把最少收到再乘 (1 − tax),税率可用 `/risk` 查;`plan.amountOut` 仍是不含税的报价
|
|
393
|
+
- **授权后重新报价**:`swap` 在有授权时先发授权,确认后重新报价再发兑换,deadline 和最少收到从发兑换那一刻算
|
|
394
|
+
- **交易**:V2 走 Router 的 `swapExact…SupportingFeeOnTransferTokens`(主币进出用 ETH 版本);V3 走 SwapRouter02 / PancakeSwap SmartRouter 的 `multicall(deadline, …)`,卖出主币时附带 value,买入主币时先收到 Router 再 `unwrapWETH9`。卖出代币时额度不够先授权给 Router(`approve: 'max'` 可设无限额度)
|
|
395
|
+
- **协议**:`swapProtocols(chainId)`——V2 是协议表里有 Router 的,V3 是 `V3_SWAP_CONTRACTS` 里有报价和兑换合约的(Uniswap V3:7 条链;PancakeSwap V3:Ethereum、BSC、Base)。V4 和聚合器暂不支持
|
|
396
|
+
- 报价是池子当前的状态,交易上链前价格可能变化;有转账税的代币报价会偏高(实际收到的少),可以先用 [代币风险检查](#代币风险检查) 看税率
|
|
397
|
+
|
|
398
|
+
主网核对:8 条链(含 Tron)报价正常。在 Base、BSC 上固定一个区块报价(`blockTag`),在同一个区块上用 `eth_call` 执行构建出的兑换交易(卖出代币时用 state override 设置额度):最少收到 = 报价时成功、= 报价 + 1 时失败,说明收到的正好等于报价——覆盖主币 → 稳定币、稳定币 → 主币(授权 + 解包)、V3 两跳、V2 的三种函数;Tron 的 SunSwap V2 两个方向同样核对过。
|
|
399
|
+
|
|
400
|
+
其他选项:`route` 指定走 `routes` 里的某一条(如让用户选另一条路线);`blockTag` 按某个区块的状态报价。
|
|
401
|
+
|
|
402
|
+
打包:在已经用到 `Provider` 的基础上,`buildSwap` 约增加 16KB(minify,gzip 约 7KB,含 `/liquidity` 的授权代码和协议表),`swap` 另外按需加载 `/write` 的发送代码。
|
|
403
|
+
|
|
404
|
+
## 代币风险检查
|
|
405
|
+
|
|
406
|
+
子路径 `@w3lib/evm-batch-call/risk`:在 `eth_call` 里模拟“买入 → 转账 → 卖出”(deployless 的探测合约,不部署、不发交易),算出税率、判断能不能卖。
|
|
407
|
+
|
|
408
|
+
```ts
|
|
409
|
+
import { getTokenRisks } from '@w3lib/evm-batch-call/risk'
|
|
410
|
+
|
|
411
|
+
const [risk] = await getTokenRisks([token], { chainId: 56 })
|
|
412
|
+
// { stage: 'ok', honeypot: false, buyTax: 0.05, sellTax: 0.08, transferTax: 0.02, owner: '0x…', implementation: null,
|
|
413
|
+
// flags: ['has-tax', 'owned'], protocol: 'pancakeswap-v2', pool: '0x…', buyGas: 125000, sellGas: 98000 }
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
- 用代币 / 包装主币里包装主币一侧最深的 V2 池子,从包装主币合约发起 `eth_call`(它持有大量主币,`eth_call` 不需要签名),默认用储备的 1 / 1000(最多 1 个主币)买入
|
|
417
|
+
- **税率**:买入税 = 1 − 实际收到 / Router 报价;转账税:转一成给新地址,看对方收到多少;卖出税取两种算法中较大的:池子实际收到的代币(转账时扣的税,精确),以及换回的主币比报价少多少(有的代币卖出时顺便把收来的税也卖进同一个池子)
|
|
418
|
+
- 节点、网络、限频的错误会抛出,不当成代币的风险;只有执行失败才标为买不进
|
|
419
|
+
- **判断**:`stage` 是模拟到哪一步(`buy-failed` 买不进:可能还没开放交易或限制了合约;`sell-failed` 卖不出)。`honeypot`:卖不出、转不了,或卖出税 ≥ 90%。`flags` 汇总:`honeypot`、`cannot-buy`、`high-buy-tax` / `high-sell-tax` / `high-transfer-tax`(> 10%)、`has-tax`(> 0.5%)、`proxy`(EIP-1967 可升级代理,规则以后可以改)、`owned`(`owner()` 不是零地址)、`no-pool`
|
|
420
|
+
- 只能发现模拟时的行为:针对特定地址、时间、数量才生效的限制(黑名单、交易冷却、单笔上限)不一定能发现;Tron 不支持
|
|
421
|
+
|
|
422
|
+
核对:在 BSC 主网分叉上部署测试代币(买 5% / 卖 8% / 转账 2% 税、卖出时把攒下的税也卖进池子、禁止卖出、卖出税 95%)和 PancakeSwap V2 池子,税率一致、貔貅盘都能识别;主网上 CAKE、USDT、PEPE 等没有税。
|
|
423
|
+
|
|
424
|
+
打包:探测合约约 3KB 字节码;在已经用到 `Provider` 的基础上 `getTokenRisks` 约增加 19KB(minify,gzip 约 8KB)。
|
|
425
|
+
|
|
426
|
+
## 手续费估算
|
|
427
|
+
|
|
428
|
+
子路径 `@w3lib/evm-batch-call/fees`:当前的手续费建议,和一笔交易大概要付多少手续费。
|
|
429
|
+
|
|
430
|
+
```ts
|
|
431
|
+
import { getCostEstimate, getFeeEstimate } from '@w3lib/evm-batch-call/fees'
|
|
432
|
+
|
|
433
|
+
await getFeeEstimate({ chainId: 1 })
|
|
434
|
+
// { type: 'eip1559', baseFee: '1200000000', slow: { maxFeePerGas, maxPriorityFeePerGas }, normal: {…}, fast: {…} }
|
|
435
|
+
|
|
436
|
+
await getCostEstimate({ from: user, to: USDT, data }, { chainId: 8453 })
|
|
437
|
+
// { type: 'evm', gasLimit: '45000', gasPrice, fee, maxFee, l1Fee: '…', total, totalFormatted: '0.0000123', symbol: 'ETH' }
|
|
438
|
+
|
|
439
|
+
await getCostEstimate({ from, to: USDT, data }, { chainId: 728126428 })
|
|
440
|
+
// { type: 'tron', energy: 64285, bandwidth: 345, energyBurn: '6428500', bandwidthBurn: '345000', activates: false, totalFormatted: '6.7735', symbol: 'TRX' }
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
- **费用建议**:按最近 20 个区块的 `eth_feeHistory`,baseFee 取下一个区块的,三档优先费是各区块第 10 / 50 / 90 百分位的中位数(空区块不算),`maxFeePerGas = baseFee × 2 + 优先费`(`/write` 默认用节点的 `getFeeData`,优先费取 `eth_maxPriorityFeePerGas`,公式相同但优先费来源不同)。节点不支持 `eth_feeHistory` 或 baseFee 为 0 的链(如 BSC)按 `eth_gasPrice`(快档 1.2 倍);网络等其他错误照常抛出
|
|
444
|
+
- **一笔交易**:预估 gas(会执行一遍,revert 时抛出)× 这一档的价格;`fee` 按 baseFee + 优先费算(预计实际付的),`maxFee` 是最多付的。OP Stack / Scroll 链加上 L1 数据费(含 25% 余量)。多笔交易可以先查一次 `estimateFees` 再传 `fees` 共用
|
|
445
|
+
- **Tron**:模拟合约调用得到能量,按发送者剩余的质押能量、免费 / 质押带宽抵扣,不够的部分按链参数的价格烧 TRX;转 TRX 给没激活的地址时算上激活费(`activates: true`)。能量按模拟值,不加余量
|
|
446
|
+
|
|
447
|
+
主网核对:Ethereum、Base(含 L1 数据费)、BSC 上的 ERC20 转账;Tron 上有质押资源和全新地址两种发送者的 USDT 转账,以及给没激活的地址转 TRX。
|
|
448
|
+
|
|
449
|
+
打包:在已经用到 `Provider` 的基础上,`getFeeEstimate` 约增加 6KB(minify,gzip 约 2KB);`getCostEstimate` 另外按需加载 `/write` 的节点代码,Tron 的部分只在 Tron 上加载。
|
|
450
|
+
|
|
451
|
+
## 域名解析
|
|
452
|
+
|
|
453
|
+
子路径 `@w3lib/evm-batch-call/names`:名字 → 地址、地址 → 名字,用 Provider 这条链的域名服务。
|
|
454
|
+
|
|
455
|
+
| 链 | 服务 | 名字 |
|
|
456
|
+
| --- | --- | --- |
|
|
457
|
+
| Ethereum | ENS | `.eth` 以及导入的 DNS 域名 |
|
|
458
|
+
| BSC | SPACE ID | `.bnb` |
|
|
459
|
+
| Base | Basenames | `.base.eth` |
|
|
460
|
+
|
|
461
|
+
```ts
|
|
462
|
+
import { getAddressNames, getNameAddresses } from '@w3lib/evm-batch-call/names'
|
|
463
|
+
|
|
464
|
+
await getNameAddresses(['vitalik.eth', 'nick.eth'], { chainId: 1 })
|
|
465
|
+
// [{ name: 'vitalik.eth', normalized: 'vitalik.eth', address: '0xd8dA…6045', service: 'ens' }, …]
|
|
466
|
+
await getAddressNames([user], { chainId: 56 })
|
|
467
|
+
// [{ address: user, name: 'alice.bnb', service: 'spaceid' }]
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
- 名字按 ENSIP-15 规范化(大小写、全角等),结果的 `normalized` 是规范化后的名字;不合法的名字 `normalized` / `address` 为 null
|
|
471
|
+
- 正查读注册表的 resolver、再读 `addr`,两次 multicall;反查读主名字后再正查核对(名字要解析回同一个地址,没核对的反查记录谁都能设置),共四次 multicall
|
|
472
|
+
- 不支持通配解析(ENSIP-10)和链下解析(CCIP-Read),这类名字(如部分子域名)返回 null;Tron 上没有域名服务
|
|
473
|
+
- `services` 可以换成自己的 ENS 式注册表(`{ id, name, chainId, registry, suffixes, reverseSuffix }`)
|
|
474
|
+
|
|
475
|
+
打包:在已经用到 `Provider` 的基础上约增加 2KB(minify,gzip 不到 1KB;名字规范化用 ethers 自带的 `ensNormalize`)。
|
|
476
|
+
|
|
477
|
+
## 授权管理
|
|
478
|
+
|
|
479
|
+
子路径 `@w3lib/evm-batch-call/approvals`:查一个地址给出的授权,批量撤销。
|
|
480
|
+
|
|
481
|
+
```ts
|
|
482
|
+
import { approvals, getApprovals, revokeApprovals, scanApprovals } from '@w3lib/evm-batch-call/approvals'
|
|
483
|
+
|
|
484
|
+
// 持有的代币 × 已知的被授权方(内置协议的 Router / 仓位合约、Uniswap / PancakeSwap 的 Router、1inch、0x、ParaSwap、Permit2),一次 multicall
|
|
485
|
+
const list = await getApprovals(user, { chainId: 56, tokens: [USDT, CAKE] })
|
|
486
|
+
// [{ type: 'erc20', token: USDT, symbol: 'USDT', spender: '0x1111…', spenderName: '1inch Router v6', amount: '…', unlimited: true },
|
|
487
|
+
// { type: 'permit2', token: USDT, spender: '0x66a9…', spenderName: 'Uniswap Universal Router', amount: '…', expiration: 1767225600, expired: false, … }]
|
|
488
|
+
|
|
489
|
+
await revokeApprovals(provider, key, list.filter((a) => a.type !== 'nft' && a.unlimited))
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
- **三种授权**:ERC20 的 `allowance`;Permit2 的额度(`amount` / `expiration` / `nonce`,过期的不返回);NFT 的 `isApprovedForAll`(传 `nfts`,默认检查 OpenSea、Blur 的 operator)。`includeZero: true` 时也返回额度为 0 / 已过期 / 未授权的项
|
|
493
|
+
- **检查范围**:`tokens` × `spenders`(默认 `knownSpenders(chainId)`),加上直接给出的 `pairs` / `nftPairs`。被授权方不在已知列表里时 `spenderName` 为 null
|
|
494
|
+
- **按事件发现**:`scanApprovals` 扫地址发出的 `Approval` / `ApprovalForAll` 事件和 Permit2 的 `Approval` / `Permit` 事件,返回授权过的 `pairs` / `nftPairs`(只发现,当前是否还有效交给 `approvals` 查),可以保存 `progress` 下次接着扫。需要节点支持不带合约地址的 `eth_getLogs`(publicnode 等节点要求带地址,不行;drpc、1rpc、flashbots 可以),公共节点的区块范围限制很严,适合从某个时间点开始增量扫描,不适合扫全部历史。Tron 不支持
|
|
495
|
+
- **撤销**:`buildRevokes` 只构建交易(ERC20 每个一笔 `approve(spender, 0)`,Permit2 的全部合成一笔 `lockdown`,NFT 每个一笔 `setApprovalForAll(false)`);`revokeApprovals` 构建后发送,钱包和选项同 `batchCall`(`mode: '7702'` 时同一钱包的撤销合成一笔交易),默认一笔失败不影响后面的
|
|
496
|
+
- **Tron**:SunSwap 的 Router / 仓位合约的额度和撤销(T 地址),没有 Permit2
|
|
497
|
+
|
|
498
|
+
主网核对:Ethereum 上从最近区块的真实 `Approval` / `Permit` / `ApprovalForAll` 事件取授权,额度和合约一致,`scanApprovals` 能发现,撤销的三种交易都能预估通过;Tron 上读到 SunSwap 的真实授权,撤销能模拟。
|
|
499
|
+
|
|
500
|
+
打包:在已经用到 `Provider` 的基础上,`getApprovals` 约增加 11KB(minify,gzip 约 5KB),`scanApprovals` 很小(约 4KB gzip,不含 Provider);发送的代码按需加载,只查授权时不会打包。
|
|
501
|
+
|
|
502
|
+
## 价格
|
|
503
|
+
|
|
504
|
+
子路径 `@w3lib/evm-batch-call/price`:代币的美元价格和地址的资产总值。
|
|
505
|
+
|
|
506
|
+
```ts
|
|
507
|
+
import { NATIVE_TOKEN } from '@w3lib/evm-batch-call'
|
|
508
|
+
import { getPortfolio, getTokenPrices } from '@w3lib/evm-batch-call/price'
|
|
509
|
+
|
|
510
|
+
await getTokenPrices([NATIVE_TOKEN, USDT, newToken], { chainId: 56 })
|
|
511
|
+
// [{ token: '0xEeee…', native: true, price: 612.3, source: 'defillama' }, …, { token: newToken, price: 0.0012, source: 'onchain' }]
|
|
512
|
+
|
|
513
|
+
const p = await getPortfolio(user, { chainId: 56, tokens: [NATIVE_TOKEN, USDT, CAKE], lp: true })
|
|
514
|
+
p.totalUsd // 代币 + LP 仓位的合计;p.tokens 按价值排序,p.lp 是 LP 仓位
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
- 价格源依次使用,前面没有价格的代币交给后面的(`sources`,默认 `[defillamaPrices(), onchainPrices()]`),结果的 `source` 是给出价格的源
|
|
518
|
+
- `defillamaPrices({ at })`:历史价格(unix 秒或 `Date`);`minConfidence` 过滤置信度低的价格(默认 0.9)
|
|
519
|
+
- `onchainPrices()`:在内置协议表的 V2 / V3 池子里找代币对包装主币、稳定币的池子,用计价币一侧最深的池子的价格;稳定币按 1 美元。V3 池子的深度取计价币余额和当前价格附近的流动性折算值中较小的(只看余额会被“远处放一笔、再推价格”的方式骗过),`minLiquidityUsd`(默认 10000)以下的池子不用。这只能挡住很小的池子:有人愿意自己出资建一个够深的池子时,价格仍然可以是他定的,所以新币的链上价格只适合展示。主网实测和 DefiLlama 相差在 0.5% 以内(Ethereum、BSC、Base、Tron)
|
|
520
|
+
- `combinePrices([…])` 把多个源组合成一个,可以传给 `getLpPositions` / `getOwnerTokens` 的 `prices`
|
|
521
|
+
- 资产总值只计入传入的代币(EVM 不能只靠节点列出地址持有的全部代币),`lp: true` 时加上 LP 仓位(参数同 `getLpPositions`)
|
|
522
|
+
|
|
523
|
+
价格只用于展示和估值:链上价格是池子当前的价格,可能被单个区块内的交易影响,不要用它算交易的最小数量。
|
|
524
|
+
|
|
525
|
+
打包:在已经用到 `Provider` 的基础上,`getTokenPrices` 约增加 12KB(minify,gzip 约 5KB),`getPortfolio`(含 LP 仓位)约 31KB(gzip 约 12KB),均不含 ethers。
|
|
526
|
+
|
|
301
527
|
## 功能
|
|
302
528
|
|
|
303
529
|
- **常用查询**:余额、代币详情、授权额度、NFT(集合 / 持有 / 元数据地址 / ERC1155 余额,子路径 `/nft`),一个函数一次请求
|
|
530
|
+
- **兑换**(子路径 `/swap`):V2 / V3 池子的最佳路线(直接或两跳)、报价、构建和发送,主币自动包装 / 解包
|
|
531
|
+
- **代币风险检查**(子路径 `/risk`):模拟买入 / 转账 / 卖出,算出买卖税、识别貔貅盘,代理合约、owner
|
|
532
|
+
- **手续费估算**(子路径 `/fees`):EIP-1559 三档费用建议,一笔交易的手续费(含 L1 数据费),Tron 按能量 / 带宽算要烧的 TRX
|
|
533
|
+
- **域名解析**(子路径 `/names`):ENS、SPACE ID(.bnb)、Basenames(.base.eth),批量正查、反查(正查核对)
|
|
534
|
+
- **授权管理**(子路径 `/approvals`):ERC20 / Permit2 / NFT 授权的批量查询(默认检查常见 Router、聚合器、NFT 市场),按事件发现,批量撤销
|
|
535
|
+
- **价格**(子路径 `/price`):DefiLlama 当前 / 历史价格、链上 V2 / V3 池子价格,组合价格源,地址的资产总值(代币 + LP 仓位)
|
|
304
536
|
- **资产列表**(不推荐使用,尽力而为,子路径 `/owner`):`getOwnerTokens`,免费公开代币列表或带 Key 的索引服务发现代币,multicall 核对余额,可选 DefiLlama 价格
|
|
305
537
|
- **批量读取**:`all` / `tryAll` / `tryEach`,N 条读调用合成一次 `eth_call`;支持数组或对象输入
|
|
306
538
|
- **自动合并**:绑定合约直接 `await`、`provider.call(x)`,以及 `balances` / `tokens` / `allowances`,同一收集窗口内的调用合并成一次请求并去重
|
|
307
539
|
- **预执行**:`staticCall` / `staticCallAll` / `method.staticCall`,带 `from` / `value` 模拟交易,返回解析好的 revert 原因
|
|
308
540
|
- **主币余额合并**:`getEthBalance` 与合约调用在同一次 `eth_call` 里——无论链上有没有 Multicall3
|
|
309
|
-
- **任意链可用**:内置 Multicall3 地址表;表里没有、地址无效、或查询早于部署区块时,自动改用 deployless(链上无需部署任何合约)。少数链不支持 deployless(合约创建式 `eth_call
|
|
541
|
+
- **任意链可用**:内置 Multicall3 地址表;表里没有、地址无效、或查询早于部署区块时,自动改用 deployless(链上无需部署任何合约)。少数链不支持 deployless(合约创建式 `eth_call` 返回空数据,如 Oasis Emerald、Aurora):表里有 Multicall3 的链只能查合约部署之后的状态;deployless 失败时(空数据 / 无法解码)自动改成逐条 `eth_call`(主币余额用 `eth_getBalance`,合并在一个 HTTP batch 里,慢一些但任何链都能用),确认不支持后记住 10 分钟、不再先试一次 deployless;`pnpm check:rpc` 会检查内置节点表里每条链至少有一种批量方式可用
|
|
310
542
|
- **备用节点**:传多个节点自动故障切换;不传节点使用内置公共节点表(34 条 EVM 链 + Tron)
|
|
311
543
|
- **chainId 可选**:传了节点就能自动识别 chainId,结果缓存,钱包切链自动失效
|
|
312
544
|
- **Tron**:走 Tron HTTP API,T 开头地址可以直接用
|
|
@@ -655,6 +887,10 @@ await router.swap.staticCall(params, { value })
|
|
|
655
887
|
```bash
|
|
656
888
|
pnpm test # 单元测试(mock 节点,不需要网络)
|
|
657
889
|
pnpm test:live # 连真实节点:BSC / Ethereum / Tron 主网(BSC_RPC / ETH_RPC / TRON_HOST / TRON_API_KEY 可覆盖)
|
|
890
|
+
pnpm test:live:lp # LP 仓位连主网核对(Ethereum / BSC / Base)
|
|
891
|
+
pnpm test:live:liquidity # SunSwap 加减流动性:在 Tron 主网上以真实用户的地址模拟(不发送)
|
|
892
|
+
pnpm test:fork # 加减流动性在主网分叉上真实执行:先 anvil --fork-url https://mainnet.base.org --port 18545,再带 FORK_RPC=http://127.0.0.1:18545
|
|
893
|
+
pnpm demo # 本地运行 demo(demo/,直接用 src 的源码)
|
|
658
894
|
pnpm build # 编译到 dist
|
|
659
895
|
pnpm build:contracts # 修改 contracts/ 后重新生成 src/deployless.ts
|
|
660
896
|
pnpm check:rpc # 检查内置公共节点表的可用性(--chain=56 只查一条链)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.20;
|
|
3
|
+
|
|
4
|
+
interface IRouter {
|
|
5
|
+
function WETH() external view returns (address);
|
|
6
|
+
function getAmountsOut(uint256 amountIn, address[] calldata path) external view returns (uint256[] memory amounts);
|
|
7
|
+
function swapExactETHForTokensSupportingFeeOnTransferTokens(uint256 amountOutMin, address[] calldata path, address to, uint256 deadline) external payable;
|
|
8
|
+
function swapExactTokensForETHSupportingFeeOnTransferTokens(uint256 amountIn, uint256 amountOutMin, address[] calldata path, address to, uint256 deadline) external;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface IERC20 {
|
|
12
|
+
function balanceOf(address owner) external view returns (uint256);
|
|
13
|
+
function transfer(address to, uint256 amount) external returns (bool);
|
|
14
|
+
function approve(address spender, uint256 amount) external returns (bool);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/// @notice 代币风险探测:只在 eth_call 里以 deployless 方式执行(构造函数跑完把结果作为“代码”返回),不部署、不发交易。
|
|
18
|
+
/// 用附带的主币通过 V2 Router 买入,把一部分转给一个新地址,再把剩下的卖回主币,分别记录报价和实际数量,用来算买入税、转账税、卖出税,
|
|
19
|
+
/// 以及能不能卖(貔貅盘)。每一步失败都不影响返回结果,失败的步骤在 stage 里标出。
|
|
20
|
+
contract TokenProbe {
|
|
21
|
+
struct Result {
|
|
22
|
+
uint256 stage;
|
|
23
|
+
uint256 buyQuote;
|
|
24
|
+
uint256 bought;
|
|
25
|
+
uint256 transferSent;
|
|
26
|
+
uint256 transferReceived;
|
|
27
|
+
uint256 sellAmount;
|
|
28
|
+
uint256 sellQuote;
|
|
29
|
+
uint256 sold;
|
|
30
|
+
uint256 buyGas;
|
|
31
|
+
uint256 sellGas;
|
|
32
|
+
/// 卖出时池子实际收到的代币(扣掉卖出税之后的)
|
|
33
|
+
uint256 pairReceived;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/// stage:0 全部完成;1 买入失败;2 转账失败;3 授权失败;4 卖出失败
|
|
37
|
+
constructor(address router, address token, address pair) payable {
|
|
38
|
+
Result memory r;
|
|
39
|
+
address weth = IRouter(router).WETH();
|
|
40
|
+
address[] memory path = new address[](2);
|
|
41
|
+
path[0] = weth;
|
|
42
|
+
path[1] = token;
|
|
43
|
+
try IRouter(router).getAmountsOut(msg.value, path) returns (uint256[] memory amounts) {
|
|
44
|
+
r.buyQuote = amounts[1];
|
|
45
|
+
} catch {}
|
|
46
|
+
|
|
47
|
+
uint256 gasBefore = gasleft();
|
|
48
|
+
try IRouter(router).swapExactETHForTokensSupportingFeeOnTransferTokens{value: msg.value}(0, path, address(this), block.timestamp) {
|
|
49
|
+
r.buyGas = gasBefore - gasleft();
|
|
50
|
+
r.bought = IERC20(token).balanceOf(address(this));
|
|
51
|
+
} catch {
|
|
52
|
+
r.stage = 1;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 几乎没买到(买入税接近 100%)时跳过转账这一步(转 0 个可能失败,阶段会被误标),直接试卖出
|
|
56
|
+
if (r.stage == 0 && r.bought >= 10) {
|
|
57
|
+
// 转一成给一个没用过的地址,看对方实际收到多少
|
|
58
|
+
address receiver = address(uint160(uint256(keccak256(abi.encode(address(this), token)))));
|
|
59
|
+
r.transferSent = r.bought / 10;
|
|
60
|
+
if (_call(token, abi.encodeWithSelector(IERC20.transfer.selector, receiver, r.transferSent))) {
|
|
61
|
+
r.transferReceived = IERC20(token).balanceOf(receiver);
|
|
62
|
+
} else {
|
|
63
|
+
r.stage = 2;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (r.stage == 0) {
|
|
68
|
+
r.sellAmount = IERC20(token).balanceOf(address(this));
|
|
69
|
+
if (!_call(token, abi.encodeWithSelector(IERC20.approve.selector, router, type(uint256).max))) {
|
|
70
|
+
r.stage = 3;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (r.stage == 0) {
|
|
75
|
+
path[0] = token;
|
|
76
|
+
path[1] = weth;
|
|
77
|
+
try IRouter(router).getAmountsOut(r.sellAmount, path) returns (uint256[] memory amounts) {
|
|
78
|
+
r.sellQuote = amounts[1];
|
|
79
|
+
} catch {}
|
|
80
|
+
uint256 balanceBefore = address(this).balance;
|
|
81
|
+
uint256 pairBefore = IERC20(token).balanceOf(pair);
|
|
82
|
+
gasBefore = gasleft();
|
|
83
|
+
try IRouter(router).swapExactTokensForETHSupportingFeeOnTransferTokens(r.sellAmount, 0, path, address(this), block.timestamp) {
|
|
84
|
+
r.sellGas = gasBefore - gasleft();
|
|
85
|
+
r.sold = address(this).balance - balanceBefore;
|
|
86
|
+
r.pairReceived = IERC20(token).balanceOf(pair) - pairBefore;
|
|
87
|
+
} catch {
|
|
88
|
+
r.stage = 4;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
bytes memory result = abi.encode(r);
|
|
93
|
+
assembly {
|
|
94
|
+
return(add(result, 32), mload(result))
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/// 调用 transfer / approve:成功且(没有返回值或返回 true)。USDT 等不返回 bool 的代币也算成功
|
|
99
|
+
function _call(address token, bytes memory data) private returns (bool) {
|
|
100
|
+
(bool ok, bytes memory ret) = token.call(data);
|
|
101
|
+
return ok && (ret.length == 0 || (ret.length >= 32 && abi.decode(ret, (bool))));
|
|
102
|
+
}
|
|
103
|
+
}
|
package/dist/aggregate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aggregate.d.ts","sourceRoot":"","sources":["../src/aggregate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,SAAS,EAAkC,KAAK,QAAQ,IAAI,cAAc,EAAE,MAAM,QAAQ,CAAA;AAE7G,OAAO,EAAiB,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,MAAM,WAAW,CAAA;AAI3E,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAGnE;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG,IAAI,CAAC,cAAc,EAAE,MAAM,GAAG,YAAY,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,cAAc,EAAE,SAAS,GAAG,gBAAgB,CAAC,CAAC,CAAA;AAE1I,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAA;AAE/C,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,yDAAyD;IACzD,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB;AAED;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,KAAK,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAItE,eAAO,MAAM,mBAAmB,WAI9B,CAAA;
|
|
1
|
+
{"version":3,"file":"aggregate.d.ts","sourceRoot":"","sources":["../src/aggregate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,SAAS,EAAkC,KAAK,QAAQ,IAAI,cAAc,EAAE,MAAM,QAAQ,CAAA;AAE7G,OAAO,EAAiB,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,MAAM,WAAW,CAAA;AAI3E,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAGnE;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG,IAAI,CAAC,cAAc,EAAE,MAAM,GAAG,YAAY,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,cAAc,EAAE,SAAS,GAAG,gBAAgB,CAAC,CAAC,CAAA;AAE1I,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAA;AAE/C,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,yDAAyD;IACzD,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB;AAED;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,KAAK,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAItE,eAAO,MAAM,mBAAmB,WAI9B,CAAA;AA+JF,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,kBAAkB,CAAA;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,4BAA4B;IAC5B,SAAS,EAAE,SAAS,GAAG,IAAI,CAAA;IAC3B,SAAS,EAAE,MAAM,CAAA;IACjB,8BAA8B;IAC9B,aAAa,CAAC,EAAE,iBAAiB,CAAA;CAClC;AAED;;;;GAIG;AACH,wBAAsB,SAAS,CAC7B,GAAG,EAAE,gBAAgB,EACrB,QAAQ,EAAE,WAAW,EAAE,EACvB,SAAS,GAAE,aAAkB,GAC5B,OAAO,CAAC,SAAS,EAAE,CAAC,CAmBtB;AAodD,uBAAuB;AACvB,wBAAgB,mBAAmB,IAAI,IAAI,CAI1C"}
|
package/dist/aggregate.js
CHANGED
|
@@ -40,6 +40,11 @@ const AGGREGATE3_RESULT_SELECTOR = id('Aggregate3Result((bool,bytes)[])').slice(
|
|
|
40
40
|
* 这样业务里每次 new Provider 也只会多试一次。
|
|
41
41
|
*/
|
|
42
42
|
const unusableMulticalls = new Map();
|
|
43
|
+
/**
|
|
44
|
+
* 不支持 deployless 的链(合约创建式 eth_call 返回空数据 / 无法解码,如 Oasis Emerald、Aurora),记录到什么时候为止
|
|
45
|
+
* 直接逐条 eth_call,不再先试一次注定失败的 deployless;到期后重新尝试
|
|
46
|
+
*/
|
|
47
|
+
const deploylessUnsupported = new Map();
|
|
43
48
|
/** 判定不可用后多久重新尝试合约(节点偶发返回空数据时不至于整个会话都不用 Multicall3) */
|
|
44
49
|
const UNUSABLE_TTL = 10 * 60 * 1000;
|
|
45
50
|
/** 单节点时连续返回空数据 / 无法解码的次数(多节点时由多个节点一致的结果确认,见 aggregateRawChunk) */
|
|
@@ -392,9 +397,114 @@ async function callContract(ctx, multicall, requests, overrides, atLeast) {
|
|
|
392
397
|
* 返回超过 24KB 的情况由合约改用 revert 带回,见 contracts/DeploylessMulticall3.sol。
|
|
393
398
|
*/
|
|
394
399
|
async function callDeployless(ctx, requests, overrides, atLeast) {
|
|
395
|
-
const
|
|
396
|
-
|
|
397
|
-
|
|
400
|
+
const until = deploylessUnsupported.get(ctx.chainId);
|
|
401
|
+
if (until !== undefined) {
|
|
402
|
+
if (Date.now() < until) {
|
|
403
|
+
return callPlain(ctx, requests, overrides, atLeast);
|
|
404
|
+
}
|
|
405
|
+
deploylessUnsupported.delete(ctx.chainId);
|
|
406
|
+
}
|
|
407
|
+
try {
|
|
408
|
+
const chunks = splitByInitcodeSize(requests);
|
|
409
|
+
const results = await Promise.all(chunks.map((chunk) => callDeploylessChunk(ctx, chunk, overrides, atLeast)));
|
|
410
|
+
invalidStrikes.delete(deploylessKey(ctx.chainId));
|
|
411
|
+
return results.flat();
|
|
412
|
+
}
|
|
413
|
+
catch (err) {
|
|
414
|
+
const verdict = deploylessVerdict(err);
|
|
415
|
+
if (verdict === null) {
|
|
416
|
+
throw err;
|
|
417
|
+
}
|
|
418
|
+
// 空数据 / 无法解码:这条链(或这个节点)不支持合约创建式 eth_call。这一次改成逐条 eth_call;
|
|
419
|
+
// 多个节点一致、或单节点连续 2 次才记住(节点偶发返回一次空数据不算)
|
|
420
|
+
// 只看最新状态:历史区块查询返回空数据可能只是节点裁剪了状态,不能据此判定这条链不支持
|
|
421
|
+
if (isLatest(overrides.blockTag)) {
|
|
422
|
+
const key = deploylessKey(ctx.chainId);
|
|
423
|
+
const strikes = (invalidStrikes.get(key) ?? 0) + 1;
|
|
424
|
+
if (verdict === 'confirmed' || strikes >= 2) {
|
|
425
|
+
deploylessUnsupported.set(ctx.chainId, Date.now() + UNUSABLE_TTL);
|
|
426
|
+
invalidStrikes.delete(key);
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
invalidStrikes.set(key, strikes);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return callPlain(ctx, requests, overrides, atLeast);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
const deploylessKey = (chainId) => `deployless:${chainId}`;
|
|
436
|
+
/** deployless 的失败是不是“不支持”:InvalidResultError;经 FallbackRpc 时是所有节点都这样(2 个以上为 'confirmed') */
|
|
437
|
+
function deploylessVerdict(err) {
|
|
438
|
+
if (err instanceof InvalidResultError) {
|
|
439
|
+
return 'maybe';
|
|
440
|
+
}
|
|
441
|
+
if (err instanceof AllNodesFailedError && err.errors.length > 0 && err.errors.every((e) => e.error instanceof InvalidResultError)) {
|
|
442
|
+
return err.errors.length >= 2 ? 'confirmed' : 'maybe';
|
|
443
|
+
}
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
/** 逐条 eth_call 的并发上限(公共节点常对 batch 大小和并发限频) */
|
|
447
|
+
const PLAIN_CONCURRENCY = 50;
|
|
448
|
+
/**
|
|
449
|
+
* 最后的兜底:每条请求单独 eth_call(主币余额用 eth_getBalance),不需要链上有合约、也不要求节点支持合约创建。
|
|
450
|
+
* 同时最多 PLAIN_CONCURRENCY 条在途,JsonRpcProvider / FallbackRpc 会把同一时刻的请求合到 HTTP batch 里。
|
|
451
|
+
* 区块号查询:blockTag 是数字(或 earliest)时就是它,最新状态时用 getBlockNumber 并把所有读取固定在这个区块上
|
|
452
|
+
* (minBlock 时先校验再读,保证各条一致);finalized / safe 这类标签没法换算成区块号,带区块号查询时报错
|
|
453
|
+
*/
|
|
454
|
+
async function callPlain(ctx, requests, overrides, atLeast) {
|
|
455
|
+
const { provider } = ctx;
|
|
456
|
+
let blockTag = overrides.blockTag;
|
|
457
|
+
let block = null;
|
|
458
|
+
if (atLeast !== undefined || requests.some((req) => req.blockNumber)) {
|
|
459
|
+
if (isLatest(blockTag)) {
|
|
460
|
+
if (!provider.getBlockNumber) {
|
|
461
|
+
throw new Error('This chain does not support deployless calls and the provider has no getBlockNumber(): block number queries (minBlock / withBlock) are unavailable');
|
|
462
|
+
}
|
|
463
|
+
block = BigInt(await provider.getBlockNumber());
|
|
464
|
+
if (atLeast !== undefined) {
|
|
465
|
+
assertBlock(block, atLeast);
|
|
466
|
+
}
|
|
467
|
+
blockTag = Number(block);
|
|
468
|
+
}
|
|
469
|
+
else if (blockTag === 'earliest') {
|
|
470
|
+
block = 0n;
|
|
471
|
+
}
|
|
472
|
+
else if (typeof blockTag === 'number' || typeof blockTag === 'bigint' || (typeof blockTag === 'string' && /^0x[0-9a-f]+$/i.test(blockTag))) {
|
|
473
|
+
block = BigInt(blockTag);
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
throw new Error(`Block number queries are not supported with blockTag '${String(blockTag)}' on a chain without deployless calls; pass a block number`);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
const results = new Array(requests.length);
|
|
480
|
+
let next = 0;
|
|
481
|
+
const worker = async () => {
|
|
482
|
+
for (let i = next++; i < requests.length; i = next++) {
|
|
483
|
+
const req = requests[i];
|
|
484
|
+
if (req.blockNumber && block !== null) {
|
|
485
|
+
results[i] = { success: true, returnData: encodeUint256(block) };
|
|
486
|
+
}
|
|
487
|
+
else if (req.ethBalanceOf !== undefined) {
|
|
488
|
+
results[i] = { success: true, returnData: encodeUint256(await provider.getBalance(req.ethBalanceOf, blockTag)) };
|
|
489
|
+
}
|
|
490
|
+
else {
|
|
491
|
+
try {
|
|
492
|
+
results[i] = { success: true, returnData: await provider.call({ to: req.target, data: req.callData, blockTag, from: overrides.from }) };
|
|
493
|
+
}
|
|
494
|
+
catch (err) {
|
|
495
|
+
// 合约自己 revert 才算这一条失败(allowFailure 为 false 时和 Multicall3 一样整批失败);限频、超时等节点错误照常抛出
|
|
496
|
+
if (req.allowFailure && isExecutionError(err)) {
|
|
497
|
+
results[i] = { success: false, returnData: err.data ?? '0x' };
|
|
498
|
+
}
|
|
499
|
+
else {
|
|
500
|
+
throw err;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
await Promise.all(Array.from({ length: Math.min(PLAIN_CONCURRENCY, requests.length) }, worker));
|
|
507
|
+
return results;
|
|
398
508
|
}
|
|
399
509
|
async function callDeploylessChunk(ctx, requests, overrides, atLeast) {
|
|
400
510
|
// minBlock:每片都附带区块号查询并各自校验——只校验带区块号的那一片的话,其他片可能来自落后节点、混进旧数据
|
|
@@ -497,5 +607,6 @@ function cacheKey(chainId, address) {
|
|
|
497
607
|
export function resetMulticallCache() {
|
|
498
608
|
unusableMulticalls.clear();
|
|
499
609
|
invalidStrikes.clear();
|
|
610
|
+
deploylessUnsupported.clear();
|
|
500
611
|
}
|
|
501
612
|
//# sourceMappingURL=aggregate.js.map
|