@w3lib/evm-batch-call 0.3.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.
Files changed (103) hide show
  1. package/CHANGELOG.md +45 -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 +143 -5
  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 +59 -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 +121 -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 +31 -3
  33. package/dist/fallback.d.ts.map +1 -1
  34. package/dist/fallback.js +165 -27
  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 +105 -0
  82. package/dist/watch.js.map +1 -0
  83. package/package.json +19 -10
  84. package/src/aggregate.ts +172 -7
  85. package/src/batcher.ts +69 -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 +171 -8
  90. package/src/events.ts +42 -0
  91. package/src/fallback.ts +193 -26
  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 +149 -0
package/src/watch.ts ADDED
@@ -0,0 +1,149 @@
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
+
33
+ // 按 Provider 实例分组(快捷函数对相同节点参数复用同一个实例),组内按 “钱包 + 代币集合 + 选项” 共用一份轮询
34
+ const watches = new WeakMap<Provider, Map<string, Watch>>()
35
+
36
+ /** 调用订阅者的回调:出错不影响其他订阅者和轮询 */
37
+ function safely(fn: () => void): void {
38
+ try {
39
+ fn()
40
+ } catch {
41
+ // 订阅者自己的错误(如渲染出错)不归轮询处理
42
+ }
43
+ }
44
+
45
+ /** 0x 地址不区分大小写,统一小写;Tron 的 T 开头地址区分大小写,原样保留 */
46
+ const normalize = (address: string) => (/^0x/i.test(address) ? address.toLowerCase() : address)
47
+
48
+ const tokenKey = (token: BalanceToken) => (typeof token === 'string' ? normalize(token) : `${normalize(token.address)}:${token.decimals ?? ''}`)
49
+
50
+ /**
51
+ * 轮询余额,多处订阅同一个钱包的同一组代币时合并成一份轮询(代币顺序无关),只在余额变化时通知。返回取消订阅的函数;
52
+ * 最后一个订阅者取消后停止轮询。节点参数与 getBalances 相同(相同参数复用同一个 Provider,才能共用轮询)。
53
+ *
54
+ * ```ts
55
+ * const stop = watchBalances(user, [NATIVE_TOKEN, USDT], { chainId: 56, interval: 10_000, onChange: (list) => render(list) })
56
+ * ```
57
+ */
58
+ export function watchBalances(owner: string, tokens: readonly BalanceToken[], options: WatchBalancesOptions<RawTokenBalance> & { decimals: false }): () => void
59
+ export function watchBalances(owner: string, tokens: readonly BalanceToken[], options: WatchBalancesOptions): () => void
60
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- 实现签名需兼容两种 onChange 参数类型
61
+ export function watchBalances(owner: string, tokens: readonly BalanceToken[], options: WatchBalancesOptions<any>): () => void {
62
+ const { interval = 15_000, onChange, onError, ...rest } = options
63
+ const { provider, overrides, own } = resolve(rest, ['symbol', 'decimals', 'withBlock'])
64
+ const balancesOptions = { ...overrides, ...own }
65
+ const unique = [...new Map(tokens.map((token) => [tokenKey(token), token])).entries()].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
66
+ const key = JSON.stringify([normalize(owner), unique.map(([k]) => k), balancesOptions])
67
+
68
+ let group = watches.get(provider)
69
+ if (!group) {
70
+ group = new Map()
71
+ watches.set(provider, group)
72
+ }
73
+ let watch = group.get(key)
74
+ const isNew = !watch
75
+ if (!watch) {
76
+ watch = { subscribers: new Set(), timer: null, running: false, latest: null }
77
+ group.set(key, watch)
78
+ }
79
+ const subscriber: Subscriber = { tokens, interval, onChange: onChange as Subscriber['onChange'], onError, last: null, signature: null }
80
+ watch.subscribers.add(subscriber)
81
+
82
+ const current = watch
83
+ const groupRef = group
84
+ const notify = (sub: Subscriber) => {
85
+ if (!current.latest) {
86
+ return
87
+ }
88
+ const list = sub.tokens.map((token) => {
89
+ const item = current.latest?.get(tokenKey(token)) as AnyBalance
90
+ return { ...item, token: typeof token === 'string' ? token : token.address }
91
+ })
92
+ const signature = JSON.stringify(list.map((b) => [b.balance, b.success]))
93
+ if (signature === sub.signature) {
94
+ return
95
+ }
96
+ const previous = sub.last
97
+ sub.last = list
98
+ sub.signature = signature
99
+ safely(() => sub.onChange(list, previous))
100
+ }
101
+ const schedule = () => {
102
+ if (!current.subscribers.size) {
103
+ return
104
+ }
105
+ const wait = Math.min(...[...current.subscribers].map((sub) => sub.interval))
106
+ current.timer = setTimeout(poll, wait)
107
+ }
108
+ const poll = async () => {
109
+ current.timer = null
110
+ if (current.running || !current.subscribers.size) {
111
+ return
112
+ }
113
+ current.running = true
114
+ try {
115
+ const list = (await provider.balances(owner, unique.map(([, token]) => token), balancesOptions)) as AnyBalance[]
116
+ current.latest = new Map(unique.map(([k], i) => [k, list[i] as AnyBalance]))
117
+ current.subscribers.forEach(notify)
118
+ } catch (err) {
119
+ current.subscribers.forEach((sub) => safely(() => sub.onError?.(err)))
120
+ } finally {
121
+ current.running = false
122
+ schedule()
123
+ }
124
+ }
125
+
126
+ if (isNew) {
127
+ void poll()
128
+ } else {
129
+ // 已有轮询:有结果就立即给新订阅者;新订阅者的间隔更短时重新计时
130
+ queueMicrotask(() => current.subscribers.has(subscriber) && notify(subscriber))
131
+ if (current.timer && interval < Math.min(...[...current.subscribers].filter((s) => s !== subscriber).map((s) => s.interval))) {
132
+ clearTimeout(current.timer)
133
+ schedule()
134
+ }
135
+ }
136
+
137
+ return () => {
138
+ current.subscribers.delete(subscriber)
139
+ if (!current.subscribers.size) {
140
+ if (current.timer) {
141
+ clearTimeout(current.timer)
142
+ current.timer = null
143
+ }
144
+ if (groupRef.get(key) === current) {
145
+ groupRef.delete(key)
146
+ }
147
+ }
148
+ }
149
+ }