@w3lib/evm-batch-call 0.2.0 → 0.4.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 +56 -0
- package/README.md +184 -30
- package/contracts/DeploylessMulticall3.sol +5 -0
- package/dist/aggregate.d.ts +25 -3
- package/dist/aggregate.d.ts.map +1 -1
- package/dist/aggregate.js +143 -5
- package/dist/aggregate.js.map +1 -1
- package/dist/batcher.d.ts +6 -1
- package/dist/batcher.d.ts.map +1 -1
- package/dist/batcher.js +59 -9
- package/dist/batcher.js.map +1 -1
- package/dist/call.d.ts +27 -2
- package/dist/call.d.ts.map +1 -1
- package/dist/call.js +22 -0
- package/dist/call.js.map +1 -1
- package/dist/chains.d.ts +7 -0
- package/dist/chains.d.ts.map +1 -1
- package/dist/chains.js +11 -0
- package/dist/chains.js.map +1 -1
- package/dist/deployless.d.ts +1 -1
- package/dist/deployless.d.ts.map +1 -1
- package/dist/deployless.js +1 -1
- package/dist/deployless.js.map +1 -1
- package/dist/erc20.d.ts +70 -4
- package/dist/erc20.d.ts.map +1 -1
- package/dist/erc20.js +121 -5
- package/dist/erc20.js.map +1 -1
- package/dist/events.d.ts +25 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +25 -0
- package/dist/events.js.map +1 -0
- package/dist/fallback.d.ts +54 -3
- package/dist/fallback.d.ts.map +1 -1
- package/dist/fallback.js +273 -21
- package/dist/fallback.js.map +1 -1
- package/dist/index.d.ts +10 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -5
- package/dist/index.js.map +1 -1
- package/dist/multicall.d.ts.map +1 -1
- package/dist/multicall.js +33 -2
- package/dist/multicall.js.map +1 -1
- package/dist/nft.d.ts +5 -0
- package/dist/nft.d.ts.map +1 -1
- package/dist/nft.js +5 -0
- package/dist/nft.js.map +1 -1
- package/dist/owner.d.ts +145 -0
- package/dist/owner.d.ts.map +1 -0
- package/dist/owner.js +510 -0
- package/dist/owner.js.map +1 -0
- package/dist/provider.d.ts +63 -18
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +166 -100
- package/dist/provider.js.map +1 -1
- package/dist/scan.d.ts +59 -0
- package/dist/scan.d.ts.map +1 -0
- package/dist/scan.js +245 -0
- package/dist/scan.js.map +1 -0
- package/dist/shortcuts.d.ts +34 -35
- package/dist/shortcuts.d.ts.map +1 -1
- package/dist/shortcuts.js +41 -73
- package/dist/shortcuts.js.map +1 -1
- package/dist/source.d.ts +6 -2
- package/dist/source.d.ts.map +1 -1
- package/dist/source.js +49 -6
- package/dist/source.js.map +1 -1
- package/dist/subpaths/nft.d.ts +46 -0
- package/dist/subpaths/nft.d.ts.map +1 -0
- package/dist/subpaths/nft.js +55 -0
- package/dist/subpaths/nft.js.map +1 -0
- package/dist/subpaths/owner.d.ts +33 -0
- package/dist/subpaths/owner.d.ts.map +1 -0
- package/dist/subpaths/owner.js +34 -0
- package/dist/subpaths/owner.js.map +1 -0
- package/dist/tron.d.ts +2 -0
- package/dist/tron.d.ts.map +1 -1
- package/dist/tron.js +5 -0
- package/dist/tron.js.map +1 -1
- package/dist/util.d.ts +5 -0
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +23 -0
- package/dist/util.js.map +1 -1
- package/dist/watch.d.ts +25 -0
- package/dist/watch.d.ts.map +1 -0
- package/dist/watch.js +105 -0
- package/dist/watch.js.map +1 -0
- package/package.json +19 -10
- package/src/aggregate.ts +177 -9
- package/src/batcher.ts +69 -9
- package/src/call.ts +42 -2
- package/src/chains.ts +15 -0
- package/src/deployless.ts +1 -1
- package/src/erc20.ts +171 -8
- package/src/events.ts +42 -0
- package/src/fallback.ts +306 -22
- package/src/index.ts +20 -27
- package/src/multicall.ts +34 -2
- package/src/nft.ts +5 -0
- package/src/owner.ts +660 -0
- package/src/provider.ts +255 -112
- package/src/scan.ts +293 -0
- package/src/shortcuts.ts +76 -88
- package/src/source.ts +51 -8
- package/src/subpaths/nft.ts +107 -0
- package/src/subpaths/owner.ts +69 -0
- package/src/tron.ts +7 -0
- package/src/util.ts +27 -0
- package/src/watch.ts +149 -0
package/src/provider.ts
CHANGED
|
@@ -1,17 +1,31 @@
|
|
|
1
1
|
import { type BigNumberish } from 'ethers'
|
|
2
2
|
|
|
3
|
-
import { aggregate, type AggregateContext, type BlockTag, type CallOverrides, type EthersLikeProvider } from './aggregate.js'
|
|
3
|
+
import { aggregate, type AggregateContext, type BlockTag, type CallOverrides, type EthersLikeProvider, type NativeBalanceMode } from './aggregate.js'
|
|
4
4
|
import { Batcher, type BatchOptions } from './batcher.js'
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
asStringOrBytes32,
|
|
7
|
+
decodeCall,
|
|
8
|
+
encodeCall,
|
|
9
|
+
settleResult,
|
|
10
|
+
type BoundCall,
|
|
11
|
+
type Call,
|
|
12
|
+
type CallRequest,
|
|
13
|
+
type FailableCall,
|
|
14
|
+
type FailureReason,
|
|
15
|
+
type RawResult,
|
|
16
|
+
type Settled,
|
|
17
|
+
} from './call.js'
|
|
6
18
|
import { Contract, bindCall, type ContractAbi, type ContractRunner } from './contract.js'
|
|
7
19
|
import {
|
|
8
20
|
DEFAULT_NATIVE_TOKENS,
|
|
9
21
|
ERC20_ABI,
|
|
10
|
-
|
|
22
|
+
formatUnits,
|
|
11
23
|
getCachedTokenMeta,
|
|
12
24
|
setCachedTokenMeta,
|
|
13
25
|
type BalanceToken,
|
|
14
26
|
type Erc20Contract,
|
|
27
|
+
type RawTokenAllowance,
|
|
28
|
+
type RawTokenBalance,
|
|
15
29
|
type TokenBalance,
|
|
16
30
|
type TokenAllowance,
|
|
17
31
|
MAX_UINT256,
|
|
@@ -23,26 +37,10 @@ import {
|
|
|
23
37
|
} from './erc20.js'
|
|
24
38
|
import { CallFailedError, isExecutionError } from './errors.js'
|
|
25
39
|
import { MULTICALL3_ADDRESS, getMulticall3, type Multicall } from './multicall.js'
|
|
26
|
-
import { getNativeCurrency } from './chains.js'
|
|
27
|
-
import {
|
|
28
|
-
erc1155Balances,
|
|
29
|
-
nftBalances,
|
|
30
|
-
nftCollections,
|
|
31
|
-
nftOwners,
|
|
32
|
-
nftTokenUris,
|
|
33
|
-
type DefaultNftCollectionField,
|
|
34
|
-
type Erc1155Balance,
|
|
35
|
-
type NftBalance,
|
|
36
|
-
type NftCollection,
|
|
37
|
-
type NftCollectionField,
|
|
38
|
-
type NftCollectionsOptions,
|
|
39
|
-
type NftItem,
|
|
40
|
-
type NftOwner,
|
|
41
|
-
type NftTokenUri,
|
|
42
|
-
type NftTokenUriOptions,
|
|
43
|
-
} from './nft.js'
|
|
40
|
+
import { getNativeBalanceMode, getNativeCurrency } from './chains.js'
|
|
44
41
|
import { detectChainId } from './detect.js'
|
|
45
42
|
import { isTronChain, resolveSource, type ProviderSource, type SourceOptions } from './source.js'
|
|
43
|
+
import { withSignal } from './util.js'
|
|
46
44
|
|
|
47
45
|
export type { ProviderSource }
|
|
48
46
|
|
|
@@ -62,9 +60,11 @@ export type StaticCallResult<T> = { success: true; data: T } | { success: false;
|
|
|
62
60
|
export type CallInput = readonly Call[] | Readonly<Record<string, Call>>
|
|
63
61
|
|
|
64
62
|
type ResultOf<C> = C extends Call<infer R> ? R : any
|
|
63
|
+
/** C 是 any(如 `all<any>(calls)` 显式写了 any,会落到第一个重载上)时按 ethcall 的写法返回 any[] */
|
|
64
|
+
type IsAny<T> = 0 extends 1 & T ? true : false
|
|
65
65
|
/** 与输入同构的结果:数组 → 数组(元组保留各项类型),对象 → 同名字段 */
|
|
66
|
-
export type CallResults<C> = { -readonly [K in keyof C]: ResultOf<C[K]> }
|
|
67
|
-
export type TryCallResults<C> = { -readonly [K in keyof C]: ResultOf<C[K]> | null }
|
|
66
|
+
export type CallResults<C> = IsAny<C> extends true ? any[] : { -readonly [K in keyof C]: ResultOf<C[K]> }
|
|
67
|
+
export type TryCallResults<C> = IsAny<C> extends true ? any[] : { -readonly [K in keyof C]: ResultOf<C[K]> | null }
|
|
68
68
|
|
|
69
69
|
export interface ProviderConfig extends SourceOptions {
|
|
70
70
|
/** 自定义 Multicall3 地址;不填则查内置地址表,表里没有就走 deployless */
|
|
@@ -83,16 +83,48 @@ export interface ProviderConfig extends SourceOptions {
|
|
|
83
83
|
nativeSymbol?: string
|
|
84
84
|
/** 主币名称(tokens 用),默认按内置链信息表,表里没有时为 null */
|
|
85
85
|
nativeName?: string
|
|
86
|
+
/**
|
|
87
|
+
* 主币余额的读取方式,默认按内置表 NATIVE_BALANCE_MODES,表里没有时为 'contract':
|
|
88
|
+
* - 'contract':和其他调用放在同一次 eth_call 里(Multicall3.getEthBalance / deployless 里的 BALANCE)
|
|
89
|
+
* - 'rpc':单独用 eth_getBalance。有的链在合约里读主币余额恒为 0、不报错,需要用这个
|
|
90
|
+
* - `{ erc20: '0x…' }`:主币其实是一个 ERC20 合约,改调它的 balanceOf
|
|
91
|
+
*/
|
|
92
|
+
nativeBalance?: NativeBalanceMode
|
|
86
93
|
}
|
|
87
94
|
|
|
88
|
-
|
|
95
|
+
/** balances / allowances / tokens 共用:结果带读取时的区块号 */
|
|
96
|
+
export interface BlockOptions {
|
|
97
|
+
/**
|
|
98
|
+
* 每项结果带 blockNumber(读取时的区块号,与数据在同一次 eth_call 里读出)。传了 minBlock 时自动带上。
|
|
99
|
+
* 大批量拆成多次 eth_call 时为其中包含区块号查询的那一次
|
|
100
|
+
*/
|
|
101
|
+
withBlock?: boolean
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface TokensOptions<F extends TokenField = DefaultTokenField> extends CallOverrides, BlockOptions {
|
|
89
105
|
/** 要返回的字段,默认 ['name', 'symbol', 'decimals'] */
|
|
90
106
|
fields?: readonly F[]
|
|
91
107
|
}
|
|
92
108
|
|
|
93
|
-
export interface BalancesOptions extends CallOverrides {
|
|
109
|
+
export interface BalancesOptions extends CallOverrides, BlockOptions {
|
|
94
110
|
/** 同时返回 symbol(代币查一次后缓存;主币取 nativeSymbol / 内置链信息表)。默认 false */
|
|
95
111
|
symbol?: boolean
|
|
112
|
+
/**
|
|
113
|
+
* 是否查 decimals 并返回 decimals / formatted。默认 true。
|
|
114
|
+
* false:只查余额(没有 decimals 的合约如 ERC721 也能拿到余额,冷启动时子调用数减半),结果里不带 decimals / formatted
|
|
115
|
+
*/
|
|
116
|
+
decimals?: boolean
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** multiBalances / getMultiBalances 的单项查询 */
|
|
120
|
+
export interface BalancesQuery {
|
|
121
|
+
owner: string
|
|
122
|
+
tokens: readonly BalanceToken[]
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface AllowancesOptions extends CallOverrides, BlockOptions {
|
|
126
|
+
/** 是否查 decimals 并返回 decimals / formatted。默认 true;false 时只查额度,结果里不带 decimals / formatted */
|
|
127
|
+
decimals?: boolean
|
|
96
128
|
}
|
|
97
129
|
|
|
98
130
|
/** 构造参数第一个是 chainId 还是节点:数字、数字字符串、0x 十六进制视为 chainId,其余(URL、对象、数组)视为节点 */
|
|
@@ -106,6 +138,7 @@ function isChainIdArg(value: unknown): value is number | bigint | string {
|
|
|
106
138
|
|
|
107
139
|
const ETH_BALANCE_INPUTS = [{ name: 'addr', type: 'address' }] as const
|
|
108
140
|
const ETH_BALANCE_OUTPUTS = [{ name: 'balance', type: 'uint256' }] as const
|
|
141
|
+
const BLOCK_NUMBER_OUTPUTS = [{ name: 'blockNumber', type: 'uint256' }] as const
|
|
109
142
|
|
|
110
143
|
/**
|
|
111
144
|
* Multicall Provider,API 与 ethcall 的 Provider 保持一致:
|
|
@@ -212,6 +245,7 @@ export class Provider implements ContractRunner {
|
|
|
212
245
|
chainId,
|
|
213
246
|
multicall: config.deployless ? null : resolveMulticall(chainId, config.multicall),
|
|
214
247
|
chunkSize: config.chunkSize ?? 500,
|
|
248
|
+
nativeBalance: config.nativeBalance ?? getNativeBalanceMode(chainId),
|
|
215
249
|
}
|
|
216
250
|
}
|
|
217
251
|
|
|
@@ -265,8 +299,14 @@ export class Provider implements ContractRunner {
|
|
|
265
299
|
* - decimals、symbol 查到一次后缓存,之后只查 balanceOf;也可以直接传 `{ address, decimals }`
|
|
266
300
|
* - 单个代币失败(非合约地址、非法地址等)不影响其他代币,该项 success 为 false
|
|
267
301
|
*/
|
|
268
|
-
|
|
269
|
-
|
|
302
|
+
balances(owner: string, tokens: readonly BalanceToken[], options: BalancesOptions & { decimals: false }): Promise<RawTokenBalance[]>
|
|
303
|
+
balances(owner: string, tokens: readonly BalanceToken[], options?: BalancesOptions): Promise<TokenBalance[]>
|
|
304
|
+
balances(owner: string, tokens: readonly BalanceToken[], options: BalancesOptions = {}): Promise<Array<TokenBalance | RawTokenBalance>> {
|
|
305
|
+
return withSignal(options.signal, () => this.#balances(owner, tokens, options))
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async #balances(owner: string, tokens: readonly BalanceToken[], options: BalancesOptions): Promise<Array<TokenBalance | RawTokenBalance>> {
|
|
309
|
+
const { symbol: withSymbol = false, decimals: withDecimals = true, ...overrides } = options
|
|
270
310
|
const { chainId } = await this.#ensureReady()
|
|
271
311
|
const nativeCurrency = this.#nativeCurrency(chainId)
|
|
272
312
|
const items = tokens.map((token) => {
|
|
@@ -278,41 +318,65 @@ export class Provider implements ContractRunner {
|
|
|
278
318
|
const calls: Call[] = []
|
|
279
319
|
const plan = items.map(({ address, native, decimals, symbol }) => {
|
|
280
320
|
const balanceIndex = calls.push(native ? this.getEthBalance(owner) : this.erc20(address).balanceOf(owner)) - 1
|
|
281
|
-
const decimalsIndex = decimals === undefined ? calls.push(this.erc20(address).decimals()) - 1 : -1
|
|
321
|
+
const decimalsIndex = withDecimals && decimals === undefined ? calls.push(this.erc20(address).decimals()) - 1 : -1
|
|
282
322
|
const symbolIndex = withSymbol && !native && symbol === undefined ? calls.push(this.#symbolCall(address)) - 1 : -1
|
|
283
323
|
return { balanceIndex, decimalsIndex, symbolIndex }
|
|
284
324
|
})
|
|
285
|
-
const
|
|
325
|
+
const blockIndex = this.#pushBlockNumber(calls, options)
|
|
326
|
+
const results = await this.#settleAll(calls, overrides)
|
|
327
|
+
const block = blockField(results, blockIndex)
|
|
286
328
|
|
|
287
329
|
return items.map((item, i) => {
|
|
288
330
|
const { balanceIndex, decimalsIndex, symbolIndex } = plan[i] as { balanceIndex: number; decimalsIndex: number; symbolIndex: number }
|
|
289
|
-
const balance = results[balanceIndex] as bigint
|
|
290
|
-
const fetchedDecimals =
|
|
291
|
-
const fetchedSymbol =
|
|
331
|
+
const balance = results[balanceIndex] as Settled<bigint>
|
|
332
|
+
const fetchedDecimals = valueOf<bigint>(results[decimalsIndex])
|
|
333
|
+
const fetchedSymbol = valueOf<string>(results[symbolIndex])
|
|
292
334
|
if (!item.native && (fetchedDecimals !== null || fetchedSymbol !== null)) {
|
|
293
335
|
setCachedTokenMeta(chainId, item.address, {
|
|
294
336
|
...(fetchedDecimals !== null ? { decimals: Number(fetchedDecimals) } : {}),
|
|
295
337
|
...(fetchedSymbol !== null ? { symbol: fetchedSymbol } : {}),
|
|
296
338
|
})
|
|
297
339
|
}
|
|
340
|
+
const extra = { ...(withSymbol ? { symbol: item.symbol ?? fetchedSymbol ?? null } : {}), ...block }
|
|
341
|
+
if (!withDecimals) {
|
|
342
|
+
return balance.ok
|
|
343
|
+
? { token: item.address, native: item.native, balance: balance.value.toString(), ...extra, success: true }
|
|
344
|
+
: { token: item.address, native: item.native, balance: '0', ...extra, success: false, error: balance.reason, errorField: 'balance' as const }
|
|
345
|
+
}
|
|
298
346
|
const decimals = item.decimals ?? (fetchedDecimals === null ? null : Number(fetchedDecimals))
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
347
|
+
if (!balance.ok || decimals === null) {
|
|
348
|
+
const failure = !balance.ok
|
|
349
|
+
? { error: balance.reason, errorField: 'balance' as const }
|
|
350
|
+
: { error: reasonOf(results[decimalsIndex]), errorField: 'decimals' as const }
|
|
351
|
+
return { token: item.address, native: item.native, balance: '0', decimals: decimals ?? 0, formatted: '0', ...extra, success: false, ...failure }
|
|
302
352
|
}
|
|
303
353
|
// 结果全是字符串 / 数字 / 布尔 / null,可以直接 JSON.stringify
|
|
304
354
|
return {
|
|
305
355
|
token: item.address,
|
|
306
356
|
native: item.native,
|
|
307
|
-
balance: balance.toString(),
|
|
357
|
+
balance: balance.value.toString(),
|
|
308
358
|
decimals,
|
|
309
|
-
formatted:
|
|
359
|
+
formatted: formatUnits(balance.value, decimals),
|
|
310
360
|
...extra,
|
|
311
361
|
success: true,
|
|
312
362
|
}
|
|
313
363
|
})
|
|
314
364
|
}
|
|
315
365
|
|
|
366
|
+
/**
|
|
367
|
+
* 多个钱包一次查:同一条链上所有钱包的余额合成一次 multicall(与 balances 选项相同),结果与 queries 一一对应。
|
|
368
|
+
*
|
|
369
|
+
* ```ts
|
|
370
|
+
* const [a, b] = await multi.multiBalances([{ owner: walletA, tokens: [NATIVE_TOKEN, USDT] }, { owner: walletB, tokens: [USDT] }])
|
|
371
|
+
* ```
|
|
372
|
+
*/
|
|
373
|
+
multiBalances(queries: readonly BalancesQuery[], options: BalancesOptions & { decimals: false }): Promise<RawTokenBalance[][]>
|
|
374
|
+
multiBalances(queries: readonly BalancesQuery[], options?: BalancesOptions): Promise<TokenBalance[][]>
|
|
375
|
+
multiBalances(queries: readonly BalancesQuery[], options: BalancesOptions = {}): Promise<Array<Array<TokenBalance | RawTokenBalance>>> {
|
|
376
|
+
// 各钱包的子调用在同一 tick 进入合并队列,合成一次 eth_call(相同的 decimals / 区块号查询只发一次)
|
|
377
|
+
return withSignal(options.signal, () => Promise.all(queries.map((query) => this.#balances(query.owner, query.tokens, options))))
|
|
378
|
+
}
|
|
379
|
+
|
|
316
380
|
/**
|
|
317
381
|
* 批量查 ERC20 授权额度(发交易前判断是否需要 approve),一次请求。
|
|
318
382
|
*
|
|
@@ -324,72 +388,88 @@ export class Provider implements ContractRunner {
|
|
|
324
388
|
* - 主币不需要授权:native 为 true,额度视为 MaxUint256、unlimited 为 true,不发请求
|
|
325
389
|
* - decimals 与 balances / tokens 共用缓存;也可以直接传 `{ address, decimals }`
|
|
326
390
|
*/
|
|
327
|
-
|
|
391
|
+
allowances(owner: string, spender: string, tokens: readonly BalanceToken[], options: AllowancesOptions & { decimals: false }): Promise<RawTokenAllowance[]>
|
|
392
|
+
allowances(owner: string, spender: string, tokens: readonly BalanceToken[], options?: AllowancesOptions): Promise<TokenAllowance[]>
|
|
393
|
+
allowances(
|
|
328
394
|
owner: string,
|
|
329
395
|
spender: string,
|
|
330
396
|
tokens: readonly BalanceToken[],
|
|
331
|
-
|
|
332
|
-
): Promise<TokenAllowance
|
|
397
|
+
options: AllowancesOptions = {},
|
|
398
|
+
): Promise<Array<TokenAllowance | RawTokenAllowance>> {
|
|
399
|
+
return withSignal(options.signal, () => this.#allowances(owner, spender, tokens, options))
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
async #allowances(owner: string, spender: string, tokens: readonly BalanceToken[], options: AllowancesOptions): Promise<Array<TokenAllowance | RawTokenAllowance>> {
|
|
403
|
+
const { decimals: withDecimals = true, ...overrides } = options
|
|
333
404
|
const { chainId } = await this.#ensureReady()
|
|
334
405
|
const nativeDecimals = this.#nativeCurrency(chainId).decimals
|
|
335
406
|
const calls: Call[] = []
|
|
336
407
|
const plan = tokens.map((token) => {
|
|
337
408
|
const { address, native, decimals } = this.#resolveToken(chainId, token, nativeDecimals)
|
|
338
409
|
const allowanceIndex = native ? -1 : calls.push(this.erc20(address).allowance(owner, spender)) - 1
|
|
339
|
-
const decimalsIndex = native || decimals !== undefined ? -1 : calls.push(this.erc20(address).decimals()) - 1
|
|
410
|
+
const decimalsIndex = native || !withDecimals || decimals !== undefined ? -1 : calls.push(this.erc20(address).decimals()) - 1
|
|
340
411
|
return { address, native, decimals, allowanceIndex, decimalsIndex }
|
|
341
412
|
})
|
|
342
|
-
const
|
|
413
|
+
const blockIndex = this.#pushBlockNumber(calls, options)
|
|
414
|
+
const results = await this.#settleAll(calls, overrides)
|
|
415
|
+
const block = blockField(results, blockIndex)
|
|
343
416
|
|
|
344
417
|
return plan.map(({ address, native, decimals: known, allowanceIndex, decimalsIndex }) => {
|
|
345
|
-
const
|
|
346
|
-
const fetched =
|
|
418
|
+
const settled: Settled<bigint> = native ? { ok: true, value: MAX_UINT256 } : (results[allowanceIndex] as Settled<bigint>)
|
|
419
|
+
const fetched = valueOf<bigint>(results[decimalsIndex])
|
|
347
420
|
if (fetched !== null) {
|
|
348
421
|
setCachedTokenMeta(chainId, address, { decimals: Number(fetched) })
|
|
349
422
|
}
|
|
423
|
+
if (!withDecimals) {
|
|
424
|
+
return settled.ok
|
|
425
|
+
? { token: address, spender, native, allowance: settled.value.toString(), unlimited: settled.value >= UNLIMITED_ALLOWANCE_THRESHOLD, ...block, success: true }
|
|
426
|
+
: { token: address, spender, native, allowance: '0', unlimited: false, ...block, success: false, error: settled.reason, errorField: 'allowance' as const }
|
|
427
|
+
}
|
|
350
428
|
const decimals = known ?? (fetched === null ? null : Number(fetched))
|
|
351
|
-
if (
|
|
352
|
-
|
|
429
|
+
if (!settled.ok || decimals === null) {
|
|
430
|
+
const failure = !settled.ok
|
|
431
|
+
? { error: settled.reason, errorField: 'allowance' as const }
|
|
432
|
+
: { error: reasonOf(results[decimalsIndex]), errorField: 'decimals' as const }
|
|
433
|
+
return { token: address, spender, native, allowance: '0', decimals: decimals ?? 0, formatted: '0', unlimited: false, ...block, success: false, ...failure }
|
|
353
434
|
}
|
|
354
435
|
return {
|
|
355
436
|
token: address,
|
|
356
437
|
spender,
|
|
357
438
|
native,
|
|
358
|
-
allowance:
|
|
439
|
+
allowance: settled.value.toString(),
|
|
359
440
|
decimals,
|
|
360
|
-
formatted:
|
|
361
|
-
unlimited:
|
|
441
|
+
formatted: formatUnits(settled.value, decimals),
|
|
442
|
+
unlimited: settled.value >= UNLIMITED_ALLOWANCE_THRESHOLD,
|
|
443
|
+
...block,
|
|
362
444
|
success: true,
|
|
363
445
|
}
|
|
364
446
|
})
|
|
365
447
|
}
|
|
366
448
|
|
|
367
|
-
/**
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
): Promise<
|
|
372
|
-
return
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
/** 批量查 ERC721 持有数量(balanceOf) */
|
|
376
|
-
nftBalances(owner: string, collections: readonly string[], overrides?: CallOverrides): Promise<NftBalance[]> {
|
|
377
|
-
return nftBalances(this, owner, collections, overrides)
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
/** 批量查 ERC721 持有人(ownerOf),可混合多个集合 */
|
|
381
|
-
nftOwners(items: readonly NftItem[], overrides?: CallOverrides): Promise<NftOwner[]> {
|
|
382
|
-
return nftOwners(this, items, overrides)
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
/** 批量查 NFT 元数据地址:兼容 ERC721 tokenURI 与 ERC1155 uri({id} 按规范替换),可转换 ipfs:// */
|
|
386
|
-
nftTokenUris(items: readonly NftItem[], options?: NftTokenUriOptions): Promise<NftTokenUri[]> {
|
|
387
|
-
return nftTokenUris(this, items, options)
|
|
449
|
+
/**
|
|
450
|
+
* balances / tokens / allowances 的子调用进自动合并队列:与同一时刻其他组件发起的查询合并成一次 multicall。
|
|
451
|
+
* 逐条返回值或失败原因;节点错误照常抛出
|
|
452
|
+
*/
|
|
453
|
+
#settleAll(calls: readonly Call[], overrides: CallOverrides = {}): Promise<Settled[]> {
|
|
454
|
+
return this.#batcher.loadSettled(calls, { blockTag: overrides.blockTag, from: overrides.from, minBlock: overrides.minBlock, signal: overrides.signal })
|
|
388
455
|
}
|
|
389
456
|
|
|
390
|
-
/**
|
|
391
|
-
|
|
392
|
-
|
|
457
|
+
/** withBlock / minBlock 时在同一批里加一条区块号查询,返回它的下标(不需要时为 -1) */
|
|
458
|
+
#pushBlockNumber(calls: Call[], options: BlockOptions & CallOverrides): number {
|
|
459
|
+
if (!options.withBlock && options.minBlock === undefined) {
|
|
460
|
+
return -1
|
|
461
|
+
}
|
|
462
|
+
return (
|
|
463
|
+
calls.push({
|
|
464
|
+
// 地址只用于展示:执行时换成实际的 multicall 地址,deployless 时由合约内部处理
|
|
465
|
+
contract: { address: MULTICALL3_ADDRESS },
|
|
466
|
+
name: 'getBlockNumber',
|
|
467
|
+
inputs: [],
|
|
468
|
+
outputs: BLOCK_NUMBER_OUTPUTS,
|
|
469
|
+
params: [],
|
|
470
|
+
kind: 'blockNumber',
|
|
471
|
+
}) - 1
|
|
472
|
+
)
|
|
393
473
|
}
|
|
394
474
|
|
|
395
475
|
/** symbol() 调用:string 解码失败时按 bytes32 解析(MKR 等老代币) */
|
|
@@ -420,10 +500,11 @@ export class Provider implements ContractRunner {
|
|
|
420
500
|
* - symbol / name 兼容返回 bytes32 的老代币(MKR 等)
|
|
421
501
|
* - 读取失败的字段为 null;请求的字段都读到时 success 为 true
|
|
422
502
|
*/
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
503
|
+
tokens<const F extends TokenField = DefaultTokenField>(tokens: readonly string[], options: TokensOptions<F> = {}): Promise<TokenDetails<F>[]> {
|
|
504
|
+
return withSignal(options.signal, () => this.#tokens<F>(tokens, options))
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
async #tokens<F extends TokenField>(tokens: readonly string[], options: TokensOptions<F>): Promise<TokenDetails<F>[]> {
|
|
427
508
|
const { fields = DEFAULT_TOKEN_FIELDS as unknown as readonly F[], ...overrides } = options
|
|
428
509
|
const wanted = new Set<TokenField>(fields)
|
|
429
510
|
const needDecimals = wanted.has('decimals') || wanted.has('totalSupply')
|
|
@@ -454,8 +535,10 @@ export class Provider implements ContractRunner {
|
|
|
454
535
|
}
|
|
455
536
|
return { address, native, meta, index }
|
|
456
537
|
})
|
|
457
|
-
const
|
|
458
|
-
const
|
|
538
|
+
const blockIndex = this.#pushBlockNumber(calls, options)
|
|
539
|
+
const results = await this.#settleAll(calls, overrides)
|
|
540
|
+
const block = blockField(results, blockIndex)
|
|
541
|
+
const read = <T>(i: number | undefined): T | null => (i === undefined ? null : valueOf<T>(results[i]))
|
|
459
542
|
|
|
460
543
|
return plan.map(({ address, native, meta, index }) => {
|
|
461
544
|
const name = meta.name ?? read<string>(index.name)
|
|
@@ -478,18 +561,25 @@ export class Provider implements ContractRunner {
|
|
|
478
561
|
totalSupply: totalSupply === null ? null : totalSupply.toString(),
|
|
479
562
|
}
|
|
480
563
|
const out: Record<string, unknown> = { address, native }
|
|
481
|
-
let
|
|
564
|
+
let failedField: TokenField | null = null
|
|
482
565
|
for (const field of fields) {
|
|
483
566
|
out[field] = values[field]
|
|
484
567
|
// 主币没有 totalSupply,不算失败
|
|
485
568
|
if (values[field] === null && !(native && field === 'totalSupply')) {
|
|
486
|
-
|
|
569
|
+
failedField ??= field
|
|
487
570
|
}
|
|
488
571
|
}
|
|
489
572
|
if (wanted.has('totalSupply')) {
|
|
490
|
-
out.totalSupplyFormatted = totalSupply === null || decimals === null ? null :
|
|
573
|
+
out.totalSupplyFormatted = totalSupply === null || decimals === null ? null : formatUnits(totalSupply, decimals)
|
|
574
|
+
}
|
|
575
|
+
Object.assign(out, block)
|
|
576
|
+
out.success = failedField === null
|
|
577
|
+
if (failedField !== null) {
|
|
578
|
+
// 没发请求的字段(主币信息不在内置表、也没有配置)不是调用失败
|
|
579
|
+
const callIndex = index[failedField]
|
|
580
|
+
out.error = callIndex === undefined ? 'not-configured' : reasonOf(results[callIndex])
|
|
581
|
+
out.errorField = failedField
|
|
491
582
|
}
|
|
492
|
-
out.success = success
|
|
493
583
|
return out as TokenDetails<F>
|
|
494
584
|
})
|
|
495
585
|
}
|
|
@@ -517,10 +607,12 @@ export class Provider implements ContractRunner {
|
|
|
517
607
|
*/
|
|
518
608
|
all<const C extends CallInput>(calls: C, overrides?: CallOverrides): Promise<CallResults<C>>
|
|
519
609
|
all<T = any>(calls: readonly Call[], overrides?: CallOverrides): Promise<T[]>
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
610
|
+
all(calls: CallInput, overrides?: CallOverrides): Promise<unknown> {
|
|
611
|
+
return withSignal(overrides?.signal, async () => {
|
|
612
|
+
const [list, pack] = unpack(calls)
|
|
613
|
+
const results = await this.#aggregate(list.map((call) => encodeCall(call, false)), overrides)
|
|
614
|
+
return pack(list.map((call, i) => decodeCall(call, (results[i] as RawResult).returnData)))
|
|
615
|
+
})
|
|
524
616
|
}
|
|
525
617
|
|
|
526
618
|
/**
|
|
@@ -537,7 +629,11 @@ export class Provider implements ContractRunner {
|
|
|
537
629
|
/**
|
|
538
630
|
* 逐条指定是否允许失败:允许失败的位置返回 null,不允许失败的一旦失败整体抛错。
|
|
539
631
|
*/
|
|
540
|
-
|
|
632
|
+
tryEach<T = any>(calls: readonly Call[], canFail: readonly boolean[], overrides?: CallOverrides): Promise<(T | null)[]> {
|
|
633
|
+
return withSignal(overrides?.signal, () => this.#tryEach<T>(calls, canFail, overrides))
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
async #tryEach<T>(calls: readonly Call[], canFail: readonly boolean[], overrides?: CallOverrides): Promise<(T | null)[]> {
|
|
541
637
|
// 允许失败的条目编码出错(如后端给的脏地址)只让该条为 null,不拖垮整批
|
|
542
638
|
const requests: Array<CallRequest | null> = calls.map((call, i) => {
|
|
543
639
|
const callCanFail = canFail[i]
|
|
@@ -566,7 +662,12 @@ export class Provider implements ContractRunner {
|
|
|
566
662
|
*/
|
|
567
663
|
call<T = any>(call: Call, overrides?: CallOverrides): Promise<T> {
|
|
568
664
|
const own = call.overrides
|
|
569
|
-
return
|
|
665
|
+
return withSignal(overrides?.signal, () =>
|
|
666
|
+
this.#batcher.load<T>(
|
|
667
|
+
call,
|
|
668
|
+
merge({ blockTag: own?.blockTag, from: own?.from }, { blockTag: overrides?.blockTag, from: overrides?.from, minBlock: overrides?.minBlock, signal: overrides?.signal }),
|
|
669
|
+
),
|
|
670
|
+
)
|
|
570
671
|
}
|
|
571
672
|
|
|
572
673
|
/**
|
|
@@ -578,12 +679,14 @@ export class Provider implements ContractRunner {
|
|
|
578
679
|
*
|
|
579
680
|
* overrides 优先级:参数 overrides > Call 上的 overrides(`contract.swap(params, { value })`)。
|
|
580
681
|
*/
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
682
|
+
staticCall<T = any>(call: Call, overrides: StaticCallOverrides = {}): Promise<T> {
|
|
683
|
+
return withSignal(overrides.signal, async () => {
|
|
684
|
+
const result = await this.#staticCall<T>(call, overrides)
|
|
685
|
+
if (!result.success) {
|
|
686
|
+
throw result.error
|
|
687
|
+
}
|
|
688
|
+
return result.data
|
|
689
|
+
})
|
|
587
690
|
}
|
|
588
691
|
|
|
589
692
|
/**
|
|
@@ -593,10 +696,11 @@ export class Provider implements ContractRunner {
|
|
|
593
696
|
* @param items Call,或 `{ call, overrides }` 以便每条带不同的 value / from
|
|
594
697
|
* @param overrides 所有条目共享的 overrides,优先级最低
|
|
595
698
|
*/
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
699
|
+
staticCallAll<T = any>(items: ReadonlyArray<Call | StaticCallItem>, overrides: StaticCallOverrides = {}): Promise<StaticCallResult<T>[]> {
|
|
700
|
+
return withSignal(overrides.signal, () => this.#staticCallAll<T>(items, overrides))
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
#staticCallAll<T>(items: ReadonlyArray<Call | StaticCallItem>, overrides: StaticCallOverrides): Promise<StaticCallResult<T>[]> {
|
|
600
704
|
return Promise.all(
|
|
601
705
|
items.map((item) =>
|
|
602
706
|
'call' in item && 'contract' in item.call
|
|
@@ -615,7 +719,10 @@ export class Provider implements ContractRunner {
|
|
|
615
719
|
const merged: StaticCallOverrides = merge(merge({ ...shared }, call.overrides), explicit)
|
|
616
720
|
if (call.kind === 'ethBalance') {
|
|
617
721
|
try {
|
|
618
|
-
const { provider } = await this.#ensureReady()
|
|
722
|
+
const { provider, nativeBalance } = await this.#ensureReady()
|
|
723
|
+
if (typeof nativeBalance === 'object') {
|
|
724
|
+
return await this.#staticCall<T>(this.erc20(nativeBalance.erc20).balanceOf(call.params[0]), explicit, shared)
|
|
725
|
+
}
|
|
619
726
|
return { success: true, data: (await provider.getBalance(call.params[0], merged.blockTag)) as T }
|
|
620
727
|
} catch (err) {
|
|
621
728
|
return { success: false, error: err instanceof Error ? err : new Error(String(err)) }
|
|
@@ -648,6 +755,34 @@ export class Provider implements ContractRunner {
|
|
|
648
755
|
}
|
|
649
756
|
}
|
|
650
757
|
|
|
758
|
+
/** 当前区块号(底层节点需支持 getBlockNumber) */
|
|
759
|
+
async getBlockNumber(): Promise<number> {
|
|
760
|
+
const { provider } = await this.#ensureReady()
|
|
761
|
+
if (!provider.getBlockNumber) {
|
|
762
|
+
throw new Error('getBlockNumber is not supported by this provider')
|
|
763
|
+
}
|
|
764
|
+
return provider.getBlockNumber()
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
/** eth_getLogs(底层节点需支持 getLogs)。多节点时依次尝试,全部失败抛 GetLogsError */
|
|
768
|
+
async getLogs(filter: Parameters<NonNullable<EthersLikeProvider['getLogs']>>[0]): Promise<Awaited<ReturnType<NonNullable<EthersLikeProvider['getLogs']>>>> {
|
|
769
|
+
const { provider } = await this.#ensureReady()
|
|
770
|
+
if (!provider.getLogs) {
|
|
771
|
+
throw new Error('getLogs is not supported by this provider')
|
|
772
|
+
}
|
|
773
|
+
return provider.getLogs(filter)
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/** 是否是按主币处理的地址(config.nativeTokens,默认 0xeeee…eeee 和零地址) */
|
|
777
|
+
isNativeToken(address: string): boolean {
|
|
778
|
+
return this.#nativeTokens.has(address.toLowerCase())
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
/** 按主币处理的地址(小写),顺序同 config.nativeTokens */
|
|
782
|
+
get nativeTokens(): readonly string[] {
|
|
783
|
+
return [...this.#nativeTokens]
|
|
784
|
+
}
|
|
785
|
+
|
|
651
786
|
/** 底层连接(多节点时是 FallbackRpc)。只传节点构造时,需在 ready() 之后读取 */
|
|
652
787
|
get rpc(): EthersLikeProvider {
|
|
653
788
|
if (!this.#ctx) {
|
|
@@ -695,18 +830,26 @@ function unpack(calls: CallInput): [readonly Call[], (results: unknown[]) => unk
|
|
|
695
830
|
}
|
|
696
831
|
|
|
697
832
|
function decodeTry<T>(calls: ReadonlyArray<Call | FailableCall>, results: RawResult[]): (T | null)[] {
|
|
698
|
-
return calls.map((call, i) =>
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
833
|
+
return calls.map((call, i) => valueOf<T>(settleResult<T>(call, results[i] as RawResult)))
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
/** withBlock 时每项结果附带的 { blockNumber }(区块号查询失败时为 null);不需要时为空对象 */
|
|
837
|
+
function blockField(results: readonly Settled[], index: number): { blockNumber?: number | null } {
|
|
838
|
+
if (index === -1) {
|
|
839
|
+
return {}
|
|
840
|
+
}
|
|
841
|
+
const value = valueOf<bigint>(results[index])
|
|
842
|
+
return { blockNumber: value === null ? null : Number(value) }
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/** 成功的值;失败或没有这一项(下标为 -1)时为 null */
|
|
846
|
+
function valueOf<T>(settled: Settled | undefined): T | null {
|
|
847
|
+
return settled?.ok ? (settled.value as T) : null
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/** 失败原因;没有对应的调用时(不应发生)按 decode-failed */
|
|
851
|
+
function reasonOf(settled: Settled | undefined): FailureReason {
|
|
852
|
+
return settled && !settled.ok ? settled.reason : 'decode-failed'
|
|
710
853
|
}
|
|
711
854
|
|
|
712
855
|
export default Provider
|