viem 0.0.0-main.20230810T045306 → 0.0.0-main.20230810T045918

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.
@@ -1,4 +1,4 @@
1
- import type { AbiEvent, Address, Narrow } from 'abitype'
1
+ import type { Abi, AbiEvent, Address, Narrow } from 'abitype'
2
2
 
3
3
  import type { Client } from '../../clients/createClient.js'
4
4
  import type { Transport } from '../../clients/transports/createTransport.js'
@@ -9,11 +9,23 @@ import type {
9
9
  } from '../../types/contract.js'
10
10
  import type { Filter } from '../../types/filter.js'
11
11
  import type { Log } from '../../types/log.js'
12
+ import type { LogTopic } from '../../types/misc.js'
13
+ import type { GetTransportConfig } from '../../types/transport.js'
14
+ import type { EncodeEventTopicsParameters } from '../../utils/index.js'
12
15
  import { observe } from '../../utils/observe.js'
13
16
  import { poll } from '../../utils/poll.js'
14
17
  import { stringify } from '../../utils/stringify.js'
15
18
 
19
+ import {
20
+ DecodeLogDataMismatch,
21
+ DecodeLogTopicsMismatch,
22
+ } from '../../errors/abi.js'
16
23
  import { InvalidInputRpcError } from '../../errors/rpc.js'
24
+ import {
25
+ decodeEventLog,
26
+ encodeEventTopics,
27
+ formatLog,
28
+ } from '../../utils/index.js'
17
29
  import {
18
30
  type CreateEventFilterParameters,
19
31
  createEventFilter,
@@ -23,6 +35,19 @@ import { getFilterChanges } from './getFilterChanges.js'
23
35
  import { type GetLogsParameters, getLogs } from './getLogs.js'
24
36
  import { uninstallFilter } from './uninstallFilter.js'
25
37
 
38
+ type PollOptions = {
39
+ /**
40
+ * Whether or not the transaction hashes should be batched on each invocation.
41
+ * @default true
42
+ */
43
+ batch?: boolean
44
+ /**
45
+ * Polling frequency (in ms). Defaults to Client's pollingInterval config.
46
+ * @default client.pollingInterval
47
+ */
48
+ pollingInterval?: number
49
+ }
50
+
26
51
  export type WatchEventOnLogsParameter<
27
52
  TAbiEvent extends AbiEvent | undefined = undefined,
28
53
  TAbiEvents extends
@@ -55,45 +80,59 @@ export type WatchEventParameters<
55
80
  > = {
56
81
  /** The address of the contract. */
57
82
  address?: Address | Address[]
58
- /**
59
- * Whether or not the event logs should be batched on each invocation.
60
- * @default true
61
- */
62
- batch?: boolean
63
83
  /** The callback to call when an error occurred when trying to get for a new block. */
64
84
  onError?: (error: Error) => void
65
85
  /** The callback to call when new event logs are received. */
66
86
  onLogs: WatchEventOnLogsFn<TAbiEvent, TAbiEvents, TStrict, _EventName>
67
- /** Polling frequency (in ms). Defaults to Client's pollingInterval config. */
68
- pollingInterval?: number
69
- } & (
70
- | {
71
- event: Narrow<TAbiEvent>
72
- events?: never
73
- args?: MaybeExtractEventArgsFromAbi<TAbiEvents, _EventName>
74
- /**
75
- * Whether or not the logs must match the indexed/non-indexed arguments on `event`.
76
- * @default false
77
- */
78
- strict?: TStrict
79
- }
80
- | {
81
- event?: never
82
- events?: Narrow<TAbiEvents>
83
- args?: never
84
- /**
85
- * Whether or not the logs must match the indexed/non-indexed arguments on `event`.
86
- * @default false
87
- */
88
- strict?: TStrict
89
- }
90
- | {
91
- event?: never
92
- events?: never
93
- args?: never
94
- strict?: never
95
- }
96
- )
87
+ } & (GetTransportConfig<Transport>['type'] extends 'webSocket'
88
+ ?
89
+ | {
90
+ batch?: never
91
+ /**
92
+ * Whether or not the WebSocket Transport should poll the JSON-RPC, rather than using `eth_subscribe`.
93
+ * @default false
94
+ */
95
+ poll?: false
96
+ pollingInterval?: never
97
+ }
98
+ | (PollOptions & {
99
+ /**
100
+ * Whether or not the WebSocket Transport should poll the JSON-RPC, rather than using `eth_subscribe`.
101
+ * @default true
102
+ */
103
+ poll?: true
104
+ })
105
+ : PollOptions & {
106
+ poll?: true
107
+ }) &
108
+ (
109
+ | {
110
+ event: Narrow<TAbiEvent>
111
+ events?: never
112
+ args?: MaybeExtractEventArgsFromAbi<TAbiEvents, _EventName>
113
+ /**
114
+ * Whether or not the logs must match the indexed/non-indexed arguments on `event`.
115
+ * @default false
116
+ */
117
+ strict?: TStrict
118
+ }
119
+ | {
120
+ event?: never
121
+ events?: Narrow<TAbiEvents>
122
+ args?: never
123
+ /**
124
+ * Whether or not the logs must match the indexed/non-indexed arguments on `event`.
125
+ * @default false
126
+ */
127
+ strict?: TStrict
128
+ }
129
+ | {
130
+ event?: never
131
+ events?: never
132
+ args?: never
133
+ strict?: never
134
+ }
135
+ )
97
136
 
98
137
  export type WatchEventReturnType = () => void
99
138
 
@@ -148,94 +187,176 @@ export function watchEvent<
148
187
  events,
149
188
  onError,
150
189
  onLogs,
190
+ poll: poll_,
151
191
  pollingInterval = client.pollingInterval,
152
192
  strict: strict_,
153
193
  }: WatchEventParameters<TAbiEvent, TAbiEvents, TStrict>,
154
194
  ): WatchEventReturnType {
155
- const observerId = stringify([
156
- 'watchEvent',
157
- address,
158
- args,
159
- batch,
160
- client.uid,
161
- event,
162
- pollingInterval,
163
- ])
195
+ const enablePolling =
196
+ typeof poll_ !== 'undefined' ? poll_ : client.transport.type !== 'webSocket'
164
197
  const strict = strict_ ?? false
165
198
 
166
- return observe(observerId, { onLogs, onError }, (emit) => {
167
- let previousBlockNumber: bigint
168
- let filter: Filter<'event', TAbiEvents, _EventName, any>
169
- let initialized = false
199
+ const pollEvent = () => {
200
+ const observerId = stringify([
201
+ 'watchEvent',
202
+ address,
203
+ args,
204
+ batch,
205
+ client.uid,
206
+ event,
207
+ pollingInterval,
208
+ ])
170
209
 
171
- const unwatch = poll(
172
- async () => {
173
- if (!initialized) {
174
- try {
175
- filter = (await createEventFilter(client, {
176
- address,
177
- args,
178
- event: event!,
179
- events,
180
- strict,
181
- } as unknown as CreateEventFilterParameters)) as unknown as Filter<
182
- 'event',
183
- TAbiEvents,
184
- _EventName
185
- >
186
- } catch {}
187
- initialized = true
188
- return
189
- }
210
+ return observe(observerId, { onLogs, onError }, (emit) => {
211
+ let previousBlockNumber: bigint
212
+ let filter: Filter<'event', TAbiEvents, _EventName, any>
213
+ let initialized = false
190
214
 
191
- try {
192
- let logs: Log[]
193
- if (filter) {
194
- logs = await getFilterChanges(client, { filter })
195
- } else {
196
- // If the filter doesn't exist, we will fall back to use `getLogs`.
197
- // The fall back exists because some RPC Providers do not support filters.
198
-
199
- // Fetch the block number to use for `getLogs`.
200
- const blockNumber = await getBlockNumber(client)
201
-
202
- // If the block number has changed, we will need to fetch the logs.
203
- // If the block number doesn't exist, we are yet to reach the first poll interval,
204
- // so do not emit any logs.
205
- if (previousBlockNumber && previousBlockNumber !== blockNumber) {
206
- logs = await getLogs(client, {
215
+ const unwatch = poll(
216
+ async () => {
217
+ if (!initialized) {
218
+ try {
219
+ filter = (await createEventFilter(client, {
207
220
  address,
208
221
  args,
209
222
  event: event!,
210
223
  events,
211
- fromBlock: previousBlockNumber + 1n,
212
- toBlock: blockNumber,
213
- } as unknown as GetLogsParameters)
224
+ strict,
225
+ } as unknown as CreateEventFilterParameters)) as unknown as Filter<
226
+ 'event',
227
+ TAbiEvents,
228
+ _EventName
229
+ >
230
+ } catch {}
231
+ initialized = true
232
+ return
233
+ }
234
+
235
+ try {
236
+ let logs: Log[]
237
+ if (filter) {
238
+ logs = await getFilterChanges(client, { filter })
214
239
  } else {
215
- logs = []
240
+ // If the filter doesn't exist, we will fall back to use `getLogs`.
241
+ // The fall back exists because some RPC Providers do not support filters.
242
+
243
+ // Fetch the block number to use for `getLogs`.
244
+ const blockNumber = await getBlockNumber(client)
245
+
246
+ // If the block number has changed, we will need to fetch the logs.
247
+ // If the block number doesn't exist, we are yet to reach the first poll interval,
248
+ // so do not emit any logs.
249
+ if (previousBlockNumber && previousBlockNumber !== blockNumber) {
250
+ logs = await getLogs(client, {
251
+ address,
252
+ args,
253
+ event: event!,
254
+ events,
255
+ fromBlock: previousBlockNumber + 1n,
256
+ toBlock: blockNumber,
257
+ } as unknown as GetLogsParameters)
258
+ } else {
259
+ logs = []
260
+ }
261
+ previousBlockNumber = blockNumber
216
262
  }
217
- previousBlockNumber = blockNumber
263
+
264
+ if (logs.length === 0) return
265
+ if (batch) emit.onLogs(logs as any)
266
+ else logs.forEach((log) => emit.onLogs([log] as any))
267
+ } catch (err) {
268
+ // If a filter has been set and gets uninstalled, providers will throw an InvalidInput error.
269
+ // Reinitalize the filter when this occurs
270
+ if (filter && err instanceof InvalidInputRpcError)
271
+ initialized = false
272
+ emit.onError?.(err as Error)
218
273
  }
274
+ },
275
+ {
276
+ emitOnBegin: true,
277
+ interval: pollingInterval,
278
+ },
279
+ )
280
+
281
+ return async () => {
282
+ if (filter) await uninstallFilter(client, { filter })
283
+ unwatch()
284
+ }
285
+ })
286
+ }
219
287
 
220
- if (logs.length === 0) return
221
- if (batch) emit.onLogs(logs as any)
222
- else logs.forEach((log) => emit.onLogs([log] as any))
223
- } catch (err) {
224
- // If a filter has been set and gets uninstalled, providers will throw an InvalidInput error.
225
- // Reinitalize the filter when this occurs
226
- if (filter && err instanceof InvalidInputRpcError) initialized = false
227
- emit.onError?.(err as Error)
288
+ const subscribeEvent = () => {
289
+ let active = true
290
+ let unsubscribe = () => (active = false)
291
+ ;(async () => {
292
+ try {
293
+ const events_ = events ?? (event ? [event] : undefined)
294
+ let topics: LogTopic[] = []
295
+ if (events_) {
296
+ topics = [
297
+ (events_ as AbiEvent[]).flatMap((event) =>
298
+ encodeEventTopics({
299
+ abi: [event],
300
+ eventName: (event as AbiEvent).name,
301
+ args,
302
+ } as EncodeEventTopicsParameters),
303
+ ),
304
+ ]
305
+ if (event) topics = topics[0] as LogTopic[]
228
306
  }
229
- },
230
- {
231
- emitOnBegin: true,
232
- interval: pollingInterval,
233
- },
234
- )
235
-
236
- return async () => {
237
- if (filter) await uninstallFilter(client, { filter })
238
- unwatch()
239
- }
240
- })
307
+
308
+ const { unsubscribe: unsubscribe_ } = await client.transport.subscribe({
309
+ params: ['logs', { address, topics }],
310
+ onData(data: any) {
311
+ if (!active) return
312
+ const log = data.result
313
+ try {
314
+ const { eventName, args } = decodeEventLog({
315
+ abi: events_ as Abi,
316
+ data: log.data,
317
+ topics: log.topics as any,
318
+ strict,
319
+ })
320
+ const formatted = formatLog(log, {
321
+ args,
322
+ eventName: eventName as string,
323
+ })
324
+ onLogs([formatted] as any)
325
+ } catch (err) {
326
+ let eventName
327
+ let isUnnamed
328
+ if (
329
+ err instanceof DecodeLogDataMismatch ||
330
+ err instanceof DecodeLogTopicsMismatch
331
+ ) {
332
+ // If strict mode is on, and log data/topics do not match event definition, skip.
333
+ if (strict_) return
334
+ eventName = err.abiItem.name
335
+ isUnnamed = err.abiItem.inputs?.some(
336
+ (x) => !('name' in x && x.name),
337
+ )
338
+ }
339
+
340
+ // Set args to empty if there is an error decoding (e.g. indexed/non-indexed params mismatch).
341
+ const formatted = formatLog(log, {
342
+ args: isUnnamed ? [] : {},
343
+ eventName,
344
+ })
345
+ onLogs([formatted] as any)
346
+ }
347
+ },
348
+ onError(error: Error) {
349
+ onError?.(error)
350
+ },
351
+ })
352
+ unsubscribe = unsubscribe_
353
+ if (!active) unsubscribe()
354
+ } catch (err) {
355
+ onError?.(err as Error)
356
+ }
357
+ })()
358
+ return unsubscribe
359
+ }
360
+
361
+ return enablePolling ? pollEvent() : subscribeEvent()
241
362
  }
@@ -1 +1 @@
1
- export const version = '0.0.0-main.20230810T045306'
1
+ export const version = '0.0.0-main.20230810T045918'