@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.
Files changed (103) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +98 -35
  3. package/contracts/DeploylessMulticall3.sol +5 -0
  4. package/dist/aggregate.d.ts +20 -1
  5. package/dist/aggregate.d.ts.map +1 -1
  6. package/dist/aggregate.js +222 -32
  7. package/dist/aggregate.js.map +1 -1
  8. package/dist/batcher.d.ts +6 -1
  9. package/dist/batcher.d.ts.map +1 -1
  10. package/dist/batcher.js +64 -9
  11. package/dist/batcher.js.map +1 -1
  12. package/dist/call.d.ts +27 -2
  13. package/dist/call.d.ts.map +1 -1
  14. package/dist/call.js +22 -0
  15. package/dist/call.js.map +1 -1
  16. package/dist/chains.d.ts +7 -0
  17. package/dist/chains.d.ts.map +1 -1
  18. package/dist/chains.js +11 -0
  19. package/dist/chains.js.map +1 -1
  20. package/dist/deployless.d.ts +1 -1
  21. package/dist/deployless.d.ts.map +1 -1
  22. package/dist/deployless.js +1 -1
  23. package/dist/deployless.js.map +1 -1
  24. package/dist/erc20.d.ts +70 -4
  25. package/dist/erc20.d.ts.map +1 -1
  26. package/dist/erc20.js +133 -5
  27. package/dist/erc20.js.map +1 -1
  28. package/dist/events.d.ts +25 -0
  29. package/dist/events.d.ts.map +1 -0
  30. package/dist/events.js +25 -0
  31. package/dist/events.js.map +1 -0
  32. package/dist/fallback.d.ts +49 -4
  33. package/dist/fallback.d.ts.map +1 -1
  34. package/dist/fallback.js +239 -37
  35. package/dist/fallback.js.map +1 -1
  36. package/dist/index.d.ts +9 -10
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +6 -7
  39. package/dist/index.js.map +1 -1
  40. package/dist/multicall.d.ts.map +1 -1
  41. package/dist/multicall.js +33 -2
  42. package/dist/multicall.js.map +1 -1
  43. package/dist/nft.d.ts +5 -0
  44. package/dist/nft.d.ts.map +1 -1
  45. package/dist/nft.js +5 -0
  46. package/dist/nft.js.map +1 -1
  47. package/dist/owner.d.ts +9 -0
  48. package/dist/owner.d.ts.map +1 -1
  49. package/dist/owner.js +11 -2
  50. package/dist/owner.js.map +1 -1
  51. package/dist/provider.d.ts +55 -29
  52. package/dist/provider.d.ts.map +1 -1
  53. package/dist/provider.js +140 -111
  54. package/dist/provider.js.map +1 -1
  55. package/dist/shortcuts.d.ts +34 -53
  56. package/dist/shortcuts.d.ts.map +1 -1
  57. package/dist/shortcuts.js +41 -91
  58. package/dist/shortcuts.js.map +1 -1
  59. package/dist/source.d.ts +4 -2
  60. package/dist/source.d.ts.map +1 -1
  61. package/dist/source.js +35 -6
  62. package/dist/source.js.map +1 -1
  63. package/dist/subpaths/nft.d.ts +46 -0
  64. package/dist/subpaths/nft.d.ts.map +1 -0
  65. package/dist/subpaths/nft.js +55 -0
  66. package/dist/subpaths/nft.js.map +1 -0
  67. package/dist/subpaths/owner.d.ts +33 -0
  68. package/dist/subpaths/owner.d.ts.map +1 -0
  69. package/dist/subpaths/owner.js +34 -0
  70. package/dist/subpaths/owner.js.map +1 -0
  71. package/dist/tron.d.ts +2 -0
  72. package/dist/tron.d.ts.map +1 -1
  73. package/dist/tron.js +5 -0
  74. package/dist/tron.js.map +1 -1
  75. package/dist/util.d.ts +5 -0
  76. package/dist/util.d.ts.map +1 -1
  77. package/dist/util.js +23 -0
  78. package/dist/util.js.map +1 -1
  79. package/dist/watch.d.ts +25 -0
  80. package/dist/watch.d.ts.map +1 -0
  81. package/dist/watch.js +106 -0
  82. package/dist/watch.js.map +1 -0
  83. package/package.json +19 -10
  84. package/src/aggregate.ts +253 -34
  85. package/src/batcher.ts +73 -9
  86. package/src/call.ts +42 -2
  87. package/src/chains.ts +15 -0
  88. package/src/deployless.ts +1 -1
  89. package/src/erc20.ts +184 -8
  90. package/src/events.ts +42 -0
  91. package/src/fallback.ts +287 -38
  92. package/src/index.ts +19 -61
  93. package/src/multicall.ts +34 -2
  94. package/src/nft.ts +5 -0
  95. package/src/owner.ts +11 -2
  96. package/src/provider.ts +225 -124
  97. package/src/shortcuts.ts +76 -110
  98. package/src/source.ts +35 -8
  99. package/src/subpaths/nft.ts +107 -0
  100. package/src/subpaths/owner.ts +69 -0
  101. package/src/tron.ts +7 -0
  102. package/src/util.ts +27 -0
  103. 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
+ }