@waku/rln 0.0.2-09108d9.0 → 0.0.2-8a6571f.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.
@@ -6,9 +6,10 @@ import type { IdentityCredential } from "../identity.js";
6
6
  import type { DecryptedCredentials } from "../keystore/index.js";
7
7
  import type { RLNInstance } from "../rln.js";
8
8
  import { MerkleRootTracker } from "../root_tracker.js";
9
- import { zeroPadLE } from "../utils/index.js";
9
+ import { zeroPadLE } from "../utils/bytes.js";
10
10
 
11
- import { RLN_REGISTRY_ABI, RLN_STORAGE_ABI } from "./constants.js";
11
+ import { RLN_ABI } from "./abi.js";
12
+ import { DEFAULT_RATE_LIMIT, RATE_LIMIT_PARAMS } from "./constants.js";
12
13
 
13
14
  const log = new Logger("waku:rln:contract");
14
15
 
@@ -17,18 +18,21 @@ type Member = {
17
18
  index: ethers.BigNumber;
18
19
  };
19
20
 
20
- type Signer = ethers.Signer;
21
-
22
- type RLNContractOptions = {
23
- signer: Signer;
24
- registryAddress: string;
25
- };
21
+ interface RLNContractOptions {
22
+ signer: ethers.Signer;
23
+ address: string;
24
+ rateLimit?: number;
25
+ }
26
26
 
27
- type RLNStorageOptions = {
28
- storageIndex?: number;
29
- };
27
+ interface RLNContractInitOptions extends RLNContractOptions {
28
+ contract?: ethers.Contract;
29
+ }
30
30
 
31
- type RLNContractInitOptions = RLNContractOptions & RLNStorageOptions;
31
+ export interface MembershipRegisteredEvent {
32
+ idCommitment: string;
33
+ rateLimit: number;
34
+ index: ethers.BigNumber;
35
+ }
32
36
 
33
37
  type FetchMembersOptions = {
34
38
  fromBlock?: number;
@@ -36,80 +40,157 @@ type FetchMembersOptions = {
36
40
  fetchChunks?: number;
37
41
  };
38
42
 
43
+ export interface MembershipInfo {
44
+ index: ethers.BigNumber;
45
+ idCommitment: string;
46
+ rateLimit: number;
47
+ startBlock: number;
48
+ endBlock: number;
49
+ state: MembershipState;
50
+ }
51
+
52
+ export enum MembershipState {
53
+ Active = "Active",
54
+ GracePeriod = "GracePeriod",
55
+ Expired = "Expired",
56
+ ErasedAwaitsWithdrawal = "ErasedAwaitsWithdrawal"
57
+ }
58
+
39
59
  export class RLNContract {
40
- private registryContract: ethers.Contract;
60
+ public contract: ethers.Contract;
41
61
  private merkleRootTracker: MerkleRootTracker;
42
62
 
43
63
  private deployBlock: undefined | number;
44
- private storageIndex: undefined | number;
45
- private storageContract: undefined | ethers.Contract;
46
- private _membersFilter: undefined | ethers.EventFilter;
64
+ private rateLimit: number;
47
65
 
48
66
  private _members: Map<number, Member> = new Map();
67
+ private _membersFilter: ethers.EventFilter;
68
+ private _membersRemovedFilter: ethers.EventFilter;
49
69
 
70
+ /**
71
+ * Asynchronous initializer for RLNContract.
72
+ * Allows injecting a mocked contract for testing purposes.
73
+ */
50
74
  public static async init(
51
75
  rlnInstance: RLNInstance,
52
76
  options: RLNContractInitOptions
53
77
  ): Promise<RLNContract> {
54
78
  const rlnContract = new RLNContract(rlnInstance, options);
55
79
 
56
- await rlnContract.initStorageContract(options.signer);
57
80
  await rlnContract.fetchMembers(rlnInstance);
58
81
  rlnContract.subscribeToMembers(rlnInstance);
59
82
 
60
83
  return rlnContract;
61
84
  }
62
85
 
63
- public constructor(
86
+ private constructor(
64
87
  rlnInstance: RLNInstance,
65
- { registryAddress, signer }: RLNContractOptions
88
+ options: RLNContractInitOptions
66
89
  ) {
90
+ const {
91
+ address,
92
+ signer,
93
+ rateLimit = DEFAULT_RATE_LIMIT,
94
+ contract
95
+ } = options;
96
+
97
+ if (
98
+ rateLimit < RATE_LIMIT_PARAMS.MIN_RATE ||
99
+ rateLimit > RATE_LIMIT_PARAMS.MAX_RATE
100
+ ) {
101
+ throw new Error(
102
+ `Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE} messages per epoch`
103
+ );
104
+ }
105
+
106
+ this.rateLimit = rateLimit;
107
+
67
108
  const initialRoot = rlnInstance.zerokit.getMerkleRoot();
68
109
 
69
- this.registryContract = new ethers.Contract(
70
- registryAddress,
71
- RLN_REGISTRY_ABI,
72
- signer
73
- );
110
+ // Use the injected contract if provided; otherwise, instantiate a new one.
111
+ this.contract = contract || new ethers.Contract(address, RLN_ABI, signer);
74
112
  this.merkleRootTracker = new MerkleRootTracker(5, initialRoot);
113
+
114
+ // Initialize event filters for MembershipRegistered and MembershipErased
115
+ this._membersFilter = this.contract.filters.MembershipRegistered();
116
+ this._membersRemovedFilter = this.contract.filters.MembershipErased();
75
117
  }
76
118
 
77
- private async initStorageContract(
78
- signer: Signer,
79
- options: RLNStorageOptions = {}
80
- ): Promise<void> {
81
- const storageIndex = options?.storageIndex
82
- ? options.storageIndex
83
- : await this.registryContract.usingStorageIndex();
84
- const storageAddress = await this.registryContract.storages(storageIndex);
119
+ /**
120
+ * Gets the current rate limit for this contract instance
121
+ */
122
+ public getRateLimit(): number {
123
+ return this.rateLimit;
124
+ }
85
125
 
86
- if (!storageAddress || storageAddress === ethers.constants.AddressZero) {
87
- throw Error("No RLN Storage initialized on registry contract.");
88
- }
126
+ /**
127
+ * Gets the contract address
128
+ */
129
+ public get address(): string {
130
+ return this.contract.address;
131
+ }
89
132
 
90
- this.storageIndex = storageIndex;
91
- this.storageContract = new ethers.Contract(
92
- storageAddress,
93
- RLN_STORAGE_ABI,
94
- signer
95
- );
96
- this._membersFilter = this.storageContract.filters.MemberRegistered();
133
+ /**
134
+ * Gets the contract provider
135
+ */
136
+ public get provider(): ethers.providers.Provider {
137
+ return this.contract.provider;
138
+ }
97
139
 
98
- this.deployBlock = await this.storageContract.deployedBlockNumber();
140
+ /**
141
+ * Gets the minimum allowed rate limit from the contract
142
+ * @returns Promise<number> The minimum rate limit in messages per epoch
143
+ */
144
+ public async getMinRateLimit(): Promise<number> {
145
+ const minRate = await this.contract.minMembershipRateLimit();
146
+ return minRate.toNumber();
99
147
  }
100
148
 
101
- public get registry(): ethers.Contract {
102
- if (!this.registryContract) {
103
- throw Error("Registry contract was not initialized");
104
- }
105
- return this.registryContract as ethers.Contract;
149
+ /**
150
+ * Gets the maximum allowed rate limit from the contract
151
+ * @returns Promise<number> The maximum rate limit in messages per epoch
152
+ */
153
+ public async getMaxRateLimit(): Promise<number> {
154
+ const maxRate = await this.contract.maxMembershipRateLimit();
155
+ return maxRate.toNumber();
106
156
  }
107
157
 
108
- public get contract(): ethers.Contract {
109
- if (!this.storageContract) {
110
- throw Error("Storage contract was not initialized");
111
- }
112
- return this.storageContract as ethers.Contract;
158
+ /**
159
+ * Gets the maximum total rate limit across all memberships
160
+ * @returns Promise<number> The maximum total rate limit in messages per epoch
161
+ */
162
+ public async getMaxTotalRateLimit(): Promise<number> {
163
+ const maxTotalRate = await this.contract.maxTotalRateLimit();
164
+ return maxTotalRate.toNumber();
165
+ }
166
+
167
+ /**
168
+ * Gets the current total rate limit usage across all memberships
169
+ * @returns Promise<number> The current total rate limit usage in messages per epoch
170
+ */
171
+ public async getCurrentTotalRateLimit(): Promise<number> {
172
+ const currentTotal = await this.contract.currentTotalRateLimit();
173
+ return currentTotal.toNumber();
174
+ }
175
+
176
+ /**
177
+ * Gets the remaining available total rate limit that can be allocated
178
+ * @returns Promise<number> The remaining rate limit that can be allocated
179
+ */
180
+ public async getRemainingTotalRateLimit(): Promise<number> {
181
+ const [maxTotal, currentTotal] = await Promise.all([
182
+ this.contract.maxTotalRateLimit(),
183
+ this.contract.currentTotalRateLimit()
184
+ ]);
185
+ return maxTotal.sub(currentTotal).toNumber();
186
+ }
187
+
188
+ /**
189
+ * Updates the rate limit for future registrations
190
+ * @param newRateLimit The new rate limit to use
191
+ */
192
+ public async setRateLimit(newRateLimit: number): Promise<void> {
193
+ this.rateLimit = newRateLimit;
113
194
  }
114
195
 
115
196
  public get members(): Member[] {
@@ -123,7 +204,14 @@ export class RLNContract {
123
204
  if (!this._membersFilter) {
124
205
  throw Error("Members filter was not initialized.");
125
206
  }
126
- return this._membersFilter as ethers.EventFilter;
207
+ return this._membersFilter;
208
+ }
209
+
210
+ private get membersRemovedFilter(): ethers.EventFilter {
211
+ if (!this._membersRemovedFilter) {
212
+ throw Error("MembersErased filter was not initialized.");
213
+ }
214
+ return this._membersRemovedFilter;
127
215
  }
128
216
 
129
217
  public async fetchMembers(
@@ -135,7 +223,14 @@ export class RLNContract {
135
223
  ...options,
136
224
  membersFilter: this.membersFilter
137
225
  });
138
- this.processEvents(rlnInstance, registeredMemberEvents);
226
+ const removedMemberEvents = await queryFilter(this.contract, {
227
+ fromBlock: this.deployBlock,
228
+ ...options,
229
+ membersFilter: this.membersRemovedFilter
230
+ });
231
+
232
+ const events = [...registeredMemberEvents, ...removedMemberEvents];
233
+ this.processEvents(rlnInstance, events);
139
234
  }
140
235
 
141
236
  public processEvents(rlnInstance: RLNInstance, events: ethers.Event[]): void {
@@ -147,8 +242,8 @@ export class RLNContract {
147
242
  return;
148
243
  }
149
244
 
150
- if (evt.removed) {
151
- const index: ethers.BigNumber = evt.args.index;
245
+ if (evt.event === "MembershipErased") {
246
+ const index = evt.args.index as ethers.BigNumber;
152
247
  const toRemoveVal = toRemoveTable.get(evt.blockNumber);
153
248
  if (toRemoveVal != undefined) {
154
249
  toRemoveVal.push(index.toNumber());
@@ -156,7 +251,7 @@ export class RLNContract {
156
251
  } else {
157
252
  toRemoveTable.set(evt.blockNumber, [index.toNumber()]);
158
253
  }
159
- } else {
254
+ } else if (evt.event === "MembershipRegistered") {
160
255
  let eventsPerBlock = toInsertTable.get(evt.blockNumber);
161
256
  if (eventsPerBlock == undefined) {
162
257
  eventsPerBlock = [];
@@ -177,18 +272,20 @@ export class RLNContract {
177
272
  ): void {
178
273
  toInsert.forEach((events: ethers.Event[], blockNumber: number) => {
179
274
  events.forEach((evt) => {
180
- const _idCommitment = evt?.args?.idCommitment;
181
- const index: ethers.BigNumber = evt?.args?.index;
275
+ if (!evt.args) return;
276
+
277
+ const _idCommitment = evt.args.idCommitment as string;
278
+ const index = evt.args.index as ethers.BigNumber;
182
279
 
183
280
  if (!_idCommitment || !index) {
184
281
  return;
185
282
  }
186
283
 
187
- const idCommitment = zeroPadLE(hexToBytes(_idCommitment?._hex), 32);
284
+ const idCommitment = zeroPadLE(hexToBytes(_idCommitment), 32);
188
285
  rlnInstance.zerokit.insertMember(idCommitment);
189
286
  this._members.set(index.toNumber(), {
190
287
  index,
191
- idCommitment: _idCommitment?._hex
288
+ idCommitment: _idCommitment
192
289
  });
193
290
  });
194
291
 
@@ -201,7 +298,7 @@ export class RLNContract {
201
298
  rlnInstance: RLNInstance,
202
299
  toRemove: Map<number, number[]>
203
300
  ): void {
204
- const removeDescending = new Map([...toRemove].sort().reverse());
301
+ const removeDescending = new Map([...toRemove].reverse());
205
302
  removeDescending.forEach((indexes: number[], blockNumber: number) => {
206
303
  indexes.forEach((index) => {
207
304
  if (this._members.has(index)) {
@@ -215,63 +312,291 @@ export class RLNContract {
215
312
  }
216
313
 
217
314
  public subscribeToMembers(rlnInstance: RLNInstance): void {
218
- this.contract.on(this.membersFilter, (_pubkey, _index, event) =>
219
- this.processEvents(rlnInstance, [event])
315
+ this.contract.on(
316
+ this.membersFilter,
317
+ (
318
+ _idCommitment: string,
319
+ _rateLimit: number,
320
+ _index: ethers.BigNumber,
321
+ event: ethers.Event
322
+ ) => {
323
+ this.processEvents(rlnInstance, [event]);
324
+ }
325
+ );
326
+
327
+ this.contract.on(
328
+ this.membersRemovedFilter,
329
+ (
330
+ _idCommitment: string,
331
+ _rateLimit: number,
332
+ _index: ethers.BigNumber,
333
+ event: ethers.Event
334
+ ) => {
335
+ this.processEvents(rlnInstance, [event]);
336
+ }
220
337
  );
221
338
  }
222
339
 
223
340
  public async registerWithIdentity(
224
341
  identity: IdentityCredential
225
342
  ): Promise<DecryptedCredentials | undefined> {
226
- if (this.storageIndex === undefined) {
227
- throw Error(
228
- "Cannot register credential, no storage contract index found."
343
+ try {
344
+ log.info(
345
+ `Registering identity with rate limit: ${this.rateLimit} messages/epoch`
229
346
  );
230
- }
231
- const txRegisterResponse: ethers.ContractTransaction =
232
- await this.registryContract["register(uint16,uint256)"](
233
- this.storageIndex,
234
- identity.IDCommitmentBigInt,
235
- { gasLimit: 100000 }
347
+
348
+ const txRegisterResponse: ethers.ContractTransaction =
349
+ await this.contract.register(
350
+ identity.IDCommitmentBigInt,
351
+ this.rateLimit,
352
+ [],
353
+ { gasLimit: 300000 }
354
+ );
355
+ const txRegisterReceipt = await txRegisterResponse.wait();
356
+
357
+ const memberRegistered = txRegisterReceipt.events?.find(
358
+ (event) => event.event === "MembershipRegistered"
236
359
  );
237
- const txRegisterReceipt = await txRegisterResponse.wait();
238
360
 
239
- // assumption: register(uint16,uint256) emits one event
240
- const memberRegistered = txRegisterReceipt?.events?.[0];
361
+ if (!memberRegistered || !memberRegistered.args) {
362
+ log.error(
363
+ "Failed to register membership: No MembershipRegistered event found"
364
+ );
365
+ return undefined;
366
+ }
367
+
368
+ const decodedData: MembershipRegisteredEvent = {
369
+ idCommitment: memberRegistered.args.idCommitment,
370
+ rateLimit: memberRegistered.args.rateLimit,
371
+ index: memberRegistered.args.index
372
+ };
373
+
374
+ log.info(
375
+ `Successfully registered membership with index ${decodedData.index} ` +
376
+ `and rate limit ${decodedData.rateLimit}`
377
+ );
241
378
 
242
- if (!memberRegistered) {
379
+ const network = await this.contract.provider.getNetwork();
380
+ const address = this.contract.address;
381
+ const membershipId = decodedData.index.toNumber();
382
+
383
+ return {
384
+ identity,
385
+ membership: {
386
+ address,
387
+ treeIndex: membershipId,
388
+ chainId: network.chainId
389
+ }
390
+ };
391
+ } catch (error) {
392
+ log.error(`Error in registerWithIdentity: ${(error as Error).message}`);
243
393
  return undefined;
244
394
  }
395
+ }
245
396
 
246
- const decodedData = this.contract.interface.decodeEventLog(
247
- "MemberRegistered",
248
- memberRegistered.data
249
- );
397
+ /**
398
+ * Helper method to get remaining messages in current epoch
399
+ * @param membershipId The ID of the membership to check
400
+ * @returns number of remaining messages allowed in current epoch
401
+ */
402
+ public async getRemainingMessages(membershipId: number): Promise<number> {
403
+ try {
404
+ const [startTime, , rateLimit] =
405
+ await this.contract.getMembershipInfo(membershipId);
406
+
407
+ // Calculate current epoch
408
+ const currentTime = Math.floor(Date.now() / 1000);
409
+ const epochsPassed = Math.floor(
410
+ (currentTime - startTime) / RATE_LIMIT_PARAMS.EPOCH_LENGTH
411
+ );
412
+ const currentEpochStart =
413
+ startTime + epochsPassed * RATE_LIMIT_PARAMS.EPOCH_LENGTH;
414
+
415
+ // Get message count in current epoch using contract's function
416
+ const messageCount = await this.contract.getMessageCount(
417
+ membershipId,
418
+ currentEpochStart
419
+ );
420
+ return Math.max(0, rateLimit.sub(messageCount).toNumber());
421
+ } catch (error) {
422
+ log.error(
423
+ `Error getting remaining messages: ${(error as Error).message}`
424
+ );
425
+ return 0; // Fail safe: assume no messages remaining on error
426
+ }
427
+ }
250
428
 
251
- const network = await this.registryContract.provider.getNetwork();
252
- const address = this.registryContract.address;
253
- const membershipId = decodedData.index.toNumber();
429
+ public async registerWithPermitAndErase(
430
+ identity: IdentityCredential,
431
+ permit: {
432
+ owner: string;
433
+ deadline: number;
434
+ v: number;
435
+ r: string;
436
+ s: string;
437
+ },
438
+ idCommitmentsToErase: string[]
439
+ ): Promise<DecryptedCredentials | undefined> {
440
+ try {
441
+ log.info(
442
+ `Registering identity with permit and rate limit: ${this.rateLimit} messages/epoch`
443
+ );
444
+
445
+ const txRegisterResponse: ethers.ContractTransaction =
446
+ await this.contract.registerWithPermit(
447
+ permit.owner,
448
+ permit.deadline,
449
+ permit.v,
450
+ permit.r,
451
+ permit.s,
452
+ identity.IDCommitmentBigInt,
453
+ this.rateLimit,
454
+ idCommitmentsToErase.map((id) => ethers.BigNumber.from(id))
455
+ );
456
+ const txRegisterReceipt = await txRegisterResponse.wait();
457
+
458
+ const memberRegistered = txRegisterReceipt.events?.find(
459
+ (event) => event.event === "MembershipRegistered"
460
+ );
254
461
 
255
- return {
256
- identity,
257
- membership: {
258
- address,
259
- treeIndex: membershipId,
260
- chainId: network.chainId
462
+ if (!memberRegistered || !memberRegistered.args) {
463
+ log.error(
464
+ "Failed to register membership with permit: No MembershipRegistered event found"
465
+ );
466
+ return undefined;
261
467
  }
262
- };
468
+
469
+ const decodedData: MembershipRegisteredEvent = {
470
+ idCommitment: memberRegistered.args.idCommitment,
471
+ rateLimit: memberRegistered.args.rateLimit,
472
+ index: memberRegistered.args.index
473
+ };
474
+
475
+ log.info(
476
+ `Successfully registered membership with permit. Index: ${decodedData.index}, ` +
477
+ `Rate limit: ${decodedData.rateLimit}, Erased ${idCommitmentsToErase.length} commitments`
478
+ );
479
+
480
+ const network = await this.contract.provider.getNetwork();
481
+ const address = this.contract.address;
482
+ const membershipId = decodedData.index.toNumber();
483
+
484
+ return {
485
+ identity,
486
+ membership: {
487
+ address,
488
+ treeIndex: membershipId,
489
+ chainId: network.chainId
490
+ }
491
+ };
492
+ } catch (error) {
493
+ log.error(
494
+ `Error in registerWithPermitAndErase: ${(error as Error).message}`
495
+ );
496
+ return undefined;
497
+ }
263
498
  }
264
499
 
265
500
  public roots(): Uint8Array[] {
266
501
  return this.merkleRootTracker.roots();
267
502
  }
503
+
504
+ public async withdraw(token: string, holder: string): Promise<void> {
505
+ try {
506
+ const tx = await this.contract.withdraw(token, { from: holder });
507
+ await tx.wait();
508
+ } catch (error) {
509
+ log.error(`Error in withdraw: ${(error as Error).message}`);
510
+ }
511
+ }
512
+
513
+ public async getMembershipInfo(
514
+ idCommitment: string
515
+ ): Promise<MembershipInfo | undefined> {
516
+ try {
517
+ const [startBlock, endBlock, rateLimit] =
518
+ await this.contract.getMembershipInfo(idCommitment);
519
+ const currentBlock = await this.contract.provider.getBlockNumber();
520
+
521
+ let state: MembershipState;
522
+ if (currentBlock < startBlock) {
523
+ state = MembershipState.Active;
524
+ } else if (currentBlock < endBlock) {
525
+ state = MembershipState.GracePeriod;
526
+ } else {
527
+ state = MembershipState.Expired;
528
+ }
529
+
530
+ const index = await this.getMemberIndex(idCommitment);
531
+ if (!index) return undefined;
532
+
533
+ return {
534
+ index,
535
+ idCommitment,
536
+ rateLimit: rateLimit.toNumber(),
537
+ startBlock: startBlock.toNumber(),
538
+ endBlock: endBlock.toNumber(),
539
+ state
540
+ };
541
+ } catch (error) {
542
+ return undefined;
543
+ }
544
+ }
545
+
546
+ public async extendMembership(
547
+ idCommitment: string
548
+ ): Promise<ethers.ContractTransaction> {
549
+ return this.contract.extendMemberships([idCommitment]);
550
+ }
551
+
552
+ public async eraseMembership(
553
+ idCommitment: string,
554
+ eraseFromMembershipSet: boolean = true
555
+ ): Promise<ethers.ContractTransaction> {
556
+ return this.contract.eraseMemberships(
557
+ [idCommitment],
558
+ eraseFromMembershipSet
559
+ );
560
+ }
561
+
562
+ public async registerMembership(
563
+ idCommitment: string,
564
+ rateLimit: number = DEFAULT_RATE_LIMIT
565
+ ): Promise<ethers.ContractTransaction> {
566
+ if (
567
+ rateLimit < RATE_LIMIT_PARAMS.MIN_RATE ||
568
+ rateLimit > RATE_LIMIT_PARAMS.MAX_RATE
569
+ ) {
570
+ throw new Error(
571
+ `Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE}`
572
+ );
573
+ }
574
+ return this.contract.register(idCommitment, rateLimit, []);
575
+ }
576
+
577
+ private async getMemberIndex(
578
+ idCommitment: string
579
+ ): Promise<ethers.BigNumber | undefined> {
580
+ try {
581
+ const events = await this.contract.queryFilter(
582
+ this.contract.filters.MembershipRegistered(idCommitment)
583
+ );
584
+ if (events.length === 0) return undefined;
585
+
586
+ // Get the most recent registration event
587
+ const event = events[events.length - 1];
588
+ return event.args?.index;
589
+ } catch (error) {
590
+ return undefined;
591
+ }
592
+ }
268
593
  }
269
594
 
270
- type CustomQueryOptions = FetchMembersOptions & {
595
+ interface CustomQueryOptions extends FetchMembersOptions {
271
596
  membersFilter: ethers.EventFilter;
272
- };
597
+ }
273
598
 
274
- // these value should be tested on other networks
599
+ // These values should be tested on other networks
275
600
  const FETCH_CHUNK = 5;
276
601
  const BLOCK_RANGE = 3000;
277
602
 
@@ -286,18 +611,18 @@ async function queryFilter(
286
611
  fetchChunks = FETCH_CHUNK
287
612
  } = options;
288
613
 
289
- if (!fromBlock) {
614
+ if (fromBlock === undefined) {
290
615
  return contract.queryFilter(membersFilter);
291
616
  }
292
617
 
293
- if (!contract.signer.provider) {
294
- throw Error("No provider found on the contract's signer.");
618
+ if (!contract.provider) {
619
+ throw Error("No provider found on the contract.");
295
620
  }
296
621
 
297
- const toBlock = await contract.signer.provider.getBlockNumber();
622
+ const toBlock = await contract.provider.getBlockNumber();
298
623
 
299
624
  if (toBlock - fromBlock < fetchRange) {
300
- return contract.queryFilter(membersFilter);
625
+ return contract.queryFilter(membersFilter, fromBlock, toBlock);
301
626
  }
302
627
 
303
628
  const events: ethers.Event[][] = [];
@@ -319,7 +644,7 @@ function splitToChunks(
319
644
  to: number,
320
645
  step: number
321
646
  ): Array<[number, number]> {
322
- const chunks = [];
647
+ const chunks: Array<[number, number]> = [];
323
648
 
324
649
  let left = from;
325
650
  while (left < to) {
@@ -345,9 +670,18 @@ function* takeN<T>(array: T[], size: number): Iterable<T[]> {
345
670
  }
346
671
  }
347
672
 
348
- function ignoreErrors<T>(promise: Promise<T>, defaultValue: T): Promise<T> {
349
- return promise.catch((err) => {
350
- log.info(`Ignoring an error during query: ${err?.message}`);
673
+ async function ignoreErrors<T>(
674
+ promise: Promise<T>,
675
+ defaultValue: T
676
+ ): Promise<T> {
677
+ try {
678
+ return await promise;
679
+ } catch (err: unknown) {
680
+ if (err instanceof Error) {
681
+ log.info(`Ignoring an error during query: ${err.message}`);
682
+ } else {
683
+ log.info(`Ignoring an unknown error during query`);
684
+ }
351
685
  return defaultValue;
352
- });
686
+ }
353
687
  }