@w3lib/evm-batch-call 0.3.0 → 0.4.1
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 +59 -0
- package/README.md +98 -35
- package/contracts/DeploylessMulticall3.sol +5 -0
- package/dist/aggregate.d.ts +20 -1
- package/dist/aggregate.d.ts.map +1 -1
- package/dist/aggregate.js +222 -32
- 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 +64 -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 +133 -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 +49 -4
- package/dist/fallback.d.ts.map +1 -1
- package/dist/fallback.js +239 -37
- package/dist/fallback.js.map +1 -1
- package/dist/index.d.ts +9 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -7
- 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 +9 -0
- package/dist/owner.d.ts.map +1 -1
- package/dist/owner.js +11 -2
- package/dist/owner.js.map +1 -1
- package/dist/provider.d.ts +55 -29
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +140 -111
- package/dist/provider.js.map +1 -1
- package/dist/shortcuts.d.ts +34 -53
- package/dist/shortcuts.d.ts.map +1 -1
- package/dist/shortcuts.js +41 -91
- package/dist/shortcuts.js.map +1 -1
- package/dist/source.d.ts +4 -2
- package/dist/source.d.ts.map +1 -1
- package/dist/source.js +35 -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 +106 -0
- package/dist/watch.js.map +1 -0
- package/package.json +19 -10
- package/src/aggregate.ts +253 -34
- package/src/batcher.ts +73 -9
- package/src/call.ts +42 -2
- package/src/chains.ts +15 -0
- package/src/deployless.ts +1 -1
- package/src/erc20.ts +184 -8
- package/src/events.ts +42 -0
- package/src/fallback.ts +287 -38
- package/src/index.ts +19 -61
- package/src/multicall.ts +34 -2
- package/src/nft.ts +5 -0
- package/src/owner.ts +11 -2
- package/src/provider.ts +225 -124
- package/src/shortcuts.ts +76 -110
- package/src/source.ts +35 -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 +152 -0
package/src/watch.ts
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import type { BalanceToken, RawTokenBalance, TokenBalance } from './erc20.js'
|
|
2
|
+
import type { Provider } from './provider.js'
|
|
3
|
+
import { resolve, type GetBalancesOptions } from './shortcuts.js'
|
|
4
|
+
|
|
5
|
+
type AnyBalance = TokenBalance | RawTokenBalance
|
|
6
|
+
|
|
7
|
+
export interface WatchBalancesOptions<B extends AnyBalance = TokenBalance> extends Omit<GetBalancesOptions, 'signal' | 'minBlock' | 'blockTag'> {
|
|
8
|
+
/** 轮询间隔(毫秒),默认 15000;多处订阅同一份数据时取最小的间隔 */
|
|
9
|
+
interval?: number
|
|
10
|
+
/** 第一次拿到结果、以及之后余额变化时调用;previous 是上一次通知的结果(第一次为 null) */
|
|
11
|
+
onChange: (balances: B[], previous: B[] | null) => void
|
|
12
|
+
/** 某次查询失败时调用(轮询继续) */
|
|
13
|
+
onError?: (err: unknown) => void
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface Subscriber {
|
|
17
|
+
tokens: readonly BalanceToken[]
|
|
18
|
+
interval: number
|
|
19
|
+
onChange: (balances: AnyBalance[], previous: AnyBalance[] | null) => void
|
|
20
|
+
onError?: (err: unknown) => void
|
|
21
|
+
last: AnyBalance[] | null
|
|
22
|
+
signature: string | null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface Watch {
|
|
26
|
+
subscribers: Set<Subscriber>
|
|
27
|
+
timer: ReturnType<typeof setTimeout> | null
|
|
28
|
+
running: boolean
|
|
29
|
+
/** 最近一次的结果(按规范化的代币 key),新订阅者可以立即拿到 */
|
|
30
|
+
latest: Map<string, AnyBalance> | null
|
|
31
|
+
/** 上一次轮询结束的时间:下一次 = 它 + 最小间隔(新订阅者间隔更短时提前,不会因重新计时而推迟) */
|
|
32
|
+
lastPollAt: number
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 按 Provider 实例分组(快捷函数对相同节点参数复用同一个实例),组内按 “钱包 + 代币集合 + 选项” 共用一份轮询
|
|
36
|
+
const watches = new WeakMap<Provider, Map<string, Watch>>()
|
|
37
|
+
|
|
38
|
+
/** 调用订阅者的回调:出错不影响其他订阅者和轮询 */
|
|
39
|
+
function safely(fn: () => void): void {
|
|
40
|
+
try {
|
|
41
|
+
fn()
|
|
42
|
+
} catch {
|
|
43
|
+
// 订阅者自己的错误(如渲染出错)不归轮询处理
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** 0x 地址不区分大小写,统一小写;Tron 的 T 开头地址区分大小写,原样保留 */
|
|
48
|
+
const normalize = (address: string) => (/^0x/i.test(address) ? address.toLowerCase() : address)
|
|
49
|
+
|
|
50
|
+
const tokenKey = (token: BalanceToken) => (typeof token === 'string' ? normalize(token) : `${normalize(token.address)}:${token.decimals ?? ''}`)
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 轮询余额,多处订阅同一个钱包的同一组代币时合并成一份轮询(代币顺序无关),只在余额变化时通知。返回取消订阅的函数;
|
|
54
|
+
* 最后一个订阅者取消后停止轮询。节点参数与 getBalances 相同(相同参数复用同一个 Provider,才能共用轮询)。
|
|
55
|
+
*
|
|
56
|
+
* ```ts
|
|
57
|
+
* const stop = watchBalances(user, [NATIVE_TOKEN, USDT], { chainId: 56, interval: 10_000, onChange: (list) => render(list) })
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export function watchBalances(owner: string, tokens: readonly BalanceToken[], options: WatchBalancesOptions<RawTokenBalance> & { decimals: false }): () => void
|
|
61
|
+
export function watchBalances(owner: string, tokens: readonly BalanceToken[], options: WatchBalancesOptions): () => void
|
|
62
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- 实现签名需兼容两种 onChange 参数类型
|
|
63
|
+
export function watchBalances(owner: string, tokens: readonly BalanceToken[], options: WatchBalancesOptions<any>): () => void {
|
|
64
|
+
const { interval = 15_000, onChange, onError, ...rest } = options
|
|
65
|
+
const { provider, overrides, own } = resolve(rest, ['symbol', 'decimals', 'withBlock'])
|
|
66
|
+
const balancesOptions = { ...overrides, ...own }
|
|
67
|
+
const unique = [...new Map(tokens.map((token) => [tokenKey(token), token])).entries()].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
68
|
+
const key = JSON.stringify([normalize(owner), unique.map(([k]) => k), balancesOptions])
|
|
69
|
+
|
|
70
|
+
let group = watches.get(provider)
|
|
71
|
+
if (!group) {
|
|
72
|
+
group = new Map()
|
|
73
|
+
watches.set(provider, group)
|
|
74
|
+
}
|
|
75
|
+
let watch = group.get(key)
|
|
76
|
+
const isNew = !watch
|
|
77
|
+
if (!watch) {
|
|
78
|
+
watch = { subscribers: new Set(), timer: null, running: false, latest: null, lastPollAt: 0 }
|
|
79
|
+
group.set(key, watch)
|
|
80
|
+
}
|
|
81
|
+
const subscriber: Subscriber = { tokens, interval, onChange: onChange as Subscriber['onChange'], onError, last: null, signature: null }
|
|
82
|
+
watch.subscribers.add(subscriber)
|
|
83
|
+
|
|
84
|
+
const current = watch
|
|
85
|
+
const groupRef = group
|
|
86
|
+
const notify = (sub: Subscriber) => {
|
|
87
|
+
if (!current.latest) {
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
const list = sub.tokens.map((token) => {
|
|
91
|
+
const item = current.latest?.get(tokenKey(token)) as AnyBalance
|
|
92
|
+
return { ...item, token: typeof token === 'string' ? token : token.address }
|
|
93
|
+
})
|
|
94
|
+
const signature = JSON.stringify(list.map((b) => [b.balance, b.success]))
|
|
95
|
+
if (signature === sub.signature) {
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
const previous = sub.last
|
|
99
|
+
sub.last = list
|
|
100
|
+
sub.signature = signature
|
|
101
|
+
safely(() => sub.onChange(list, previous))
|
|
102
|
+
}
|
|
103
|
+
const schedule = () => {
|
|
104
|
+
if (!current.subscribers.size) {
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
const interval = Math.min(...[...current.subscribers].map((sub) => sub.interval))
|
|
108
|
+
current.timer = setTimeout(poll, Math.max(0, current.lastPollAt + interval - Date.now()))
|
|
109
|
+
}
|
|
110
|
+
const poll = async () => {
|
|
111
|
+
current.timer = null
|
|
112
|
+
if (current.running || !current.subscribers.size) {
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
current.running = true
|
|
116
|
+
try {
|
|
117
|
+
const list = (await provider.balances(owner, unique.map(([, token]) => token), balancesOptions)) as AnyBalance[]
|
|
118
|
+
current.latest = new Map(unique.map(([k], i) => [k, list[i] as AnyBalance]))
|
|
119
|
+
current.subscribers.forEach(notify)
|
|
120
|
+
} catch (err) {
|
|
121
|
+
current.subscribers.forEach((sub) => safely(() => sub.onError?.(err)))
|
|
122
|
+
} finally {
|
|
123
|
+
current.running = false
|
|
124
|
+
current.lastPollAt = Date.now()
|
|
125
|
+
schedule()
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (isNew) {
|
|
130
|
+
void poll()
|
|
131
|
+
} else {
|
|
132
|
+
// 已有轮询:有结果就立即给新订阅者;新订阅者的间隔更短时重新计时
|
|
133
|
+
queueMicrotask(() => current.subscribers.has(subscriber) && notify(subscriber))
|
|
134
|
+
if (current.timer && interval < Math.min(...[...current.subscribers].filter((s) => s !== subscriber).map((s) => s.interval))) {
|
|
135
|
+
clearTimeout(current.timer)
|
|
136
|
+
schedule()
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return () => {
|
|
141
|
+
current.subscribers.delete(subscriber)
|
|
142
|
+
if (!current.subscribers.size) {
|
|
143
|
+
if (current.timer) {
|
|
144
|
+
clearTimeout(current.timer)
|
|
145
|
+
current.timer = null
|
|
146
|
+
}
|
|
147
|
+
if (groupRef.get(key) === current) {
|
|
148
|
+
groupRef.delete(key)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|