@waku/rln 0.1.5-5e19700.0 → 0.1.5-6997987.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 (47) hide show
  1. package/bundle/index.js +2 -2
  2. package/bundle/packages/rln/dist/contract/{rln_light_contract.js → rln_base_contract.js} +172 -168
  3. package/bundle/packages/rln/dist/contract/rln_contract.js +9 -419
  4. package/bundle/packages/rln/dist/contract/types.js +9 -0
  5. package/bundle/packages/rln/dist/create.js +1 -1
  6. package/bundle/packages/rln/dist/{rln_light.js → credentials_manager.js} +120 -36
  7. package/bundle/packages/rln/dist/rln.js +56 -166
  8. package/dist/.tsbuildinfo +1 -1
  9. package/dist/contract/{rln_light_contract.d.ts → rln_base_contract.d.ts} +25 -53
  10. package/dist/contract/{rln_light_contract.js → rln_base_contract.js} +172 -168
  11. package/dist/contract/rln_base_contract.js.map +1 -0
  12. package/dist/contract/rln_contract.d.ts +5 -122
  13. package/dist/contract/rln_contract.js +8 -417
  14. package/dist/contract/rln_contract.js.map +1 -1
  15. package/dist/contract/test-utils.js +1 -1
  16. package/dist/contract/test-utils.js.map +1 -1
  17. package/dist/contract/types.d.ts +40 -0
  18. package/dist/contract/types.js +8 -0
  19. package/dist/contract/types.js.map +1 -0
  20. package/dist/create.js +1 -1
  21. package/dist/create.js.map +1 -1
  22. package/dist/credentials_manager.d.ts +50 -0
  23. package/dist/{rln_light.js → credentials_manager.js} +118 -47
  24. package/dist/credentials_manager.js.map +1 -0
  25. package/dist/index.d.ts +3 -3
  26. package/dist/index.js +3 -3
  27. package/dist/index.js.map +1 -1
  28. package/dist/rln.d.ts +9 -52
  29. package/dist/rln.js +54 -163
  30. package/dist/rln.js.map +1 -1
  31. package/dist/types.d.ts +27 -0
  32. package/dist/types.js +2 -0
  33. package/dist/types.js.map +1 -0
  34. package/package.json +1 -1
  35. package/src/contract/{rln_light_contract.ts → rln_base_contract.ts} +289 -300
  36. package/src/contract/rln_contract.ts +9 -663
  37. package/src/contract/test-utils.ts +1 -1
  38. package/src/contract/types.ts +48 -0
  39. package/src/create.ts +1 -1
  40. package/src/credentials_manager.ts +306 -0
  41. package/src/index.ts +4 -4
  42. package/src/rln.ts +67 -259
  43. package/src/types.ts +31 -0
  44. package/dist/contract/rln_light_contract.js.map +0 -1
  45. package/dist/rln_light.d.ts +0 -64
  46. package/dist/rln_light.js.map +0 -1
  47. package/src/rln_light.ts +0 -235
@@ -1,84 +1,47 @@
1
1
  import { Logger } from "@waku/utils";
2
2
  import { ethers } from "ethers";
3
3
 
4
- import type { IdentityCredential } from "../identity.js";
5
- import type { DecryptedCredentials } from "../keystore/index.js";
4
+ import { IdentityCredential } from "../identity.js";
5
+ import { DecryptedCredentials } from "../keystore/types.js";
6
6
 
7
7
  import { RLN_ABI } from "./abi.js";
8
8
  import { DEFAULT_RATE_LIMIT, RATE_LIMIT_PARAMS } from "./constants.js";
9
-
10
- const log = new Logger("waku:rln:contract");
11
-
12
- type Member = {
13
- idCommitment: string;
14
- index: ethers.BigNumber;
15
- };
16
-
17
- interface RLNContractOptions {
18
- signer: ethers.Signer;
19
- address: string;
20
- rateLimit?: number;
21
- }
22
-
23
- interface RLNContractInitOptions extends RLNContractOptions {
24
- contract?: ethers.Contract;
25
- }
26
-
27
- export interface MembershipRegisteredEvent {
28
- idCommitment: string;
29
- membershipRateLimit: ethers.BigNumber;
30
- index: ethers.BigNumber;
31
- }
32
-
33
- type FetchMembersOptions = {
34
- fromBlock?: number;
35
- fetchRange?: number;
36
- fetchChunks?: number;
37
- };
38
-
39
- export interface MembershipInfo {
40
- index: ethers.BigNumber;
41
- idCommitment: string;
42
- rateLimit: number;
43
- startBlock: number;
44
- endBlock: number;
45
- state: MembershipState;
46
- }
47
-
48
- export enum MembershipState {
49
- Active = "Active",
50
- GracePeriod = "GracePeriod",
51
- Expired = "Expired",
52
- ErasedAwaitsWithdrawal = "ErasedAwaitsWithdrawal"
53
- }
54
-
55
- export class RLNLightContract {
9
+ import {
10
+ CustomQueryOptions,
11
+ FetchMembersOptions,
12
+ Member,
13
+ MembershipInfo,
14
+ MembershipRegisteredEvent,
15
+ MembershipState,
16
+ RLNContractInitOptions
17
+ } from "./types.js";
18
+
19
+ const log = new Logger("waku:rln:contract:base");
20
+
21
+ export class RLNBaseContract {
56
22
  public contract: ethers.Contract;
57
-
58
23
  private deployBlock: undefined | number;
59
24
  private rateLimit: number;
60
25
 
61
- private _members: Map<number, Member> = new Map();
26
+ protected _members: Map<number, Member> = new Map();
62
27
  private _membersFilter: ethers.EventFilter;
63
28
  private _membershipErasedFilter: ethers.EventFilter;
64
29
  private _membersExpiredFilter: ethers.EventFilter;
65
30
 
66
31
  /**
67
- * Asynchronous initializer for RLNContract.
32
+ * Constructor for RLNBaseContract.
68
33
  * Allows injecting a mocked contract for testing purposes.
69
34
  */
70
- public static async init(
71
- options: RLNContractInitOptions
72
- ): Promise<RLNLightContract> {
73
- const rlnContract = new RLNLightContract(options);
74
-
75
- await rlnContract.fetchMembers();
76
- rlnContract.subscribeToMembers();
77
-
78
- return rlnContract;
79
- }
35
+ public constructor(options: RLNContractInitOptions) {
36
+ // Initialize members and subscriptions
37
+ this.fetchMembers()
38
+ .then(() => {
39
+ this.subscribeToMembers();
40
+ })
41
+ .catch((error) => {
42
+ log.error("Failed to initialize members", { error });
43
+ });
80
44
 
81
- private constructor(options: RLNContractInitOptions) {
82
45
  const {
83
46
  address,
84
47
  signer,
@@ -86,19 +49,10 @@ export class RLNLightContract {
86
49
  contract
87
50
  } = options;
88
51
 
89
- if (
90
- rateLimit < RATE_LIMIT_PARAMS.MIN_RATE ||
91
- rateLimit > RATE_LIMIT_PARAMS.MAX_RATE
92
- ) {
93
- throw new Error(
94
- `Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE} messages per epoch`
95
- );
96
- }
97
-
98
- this.rateLimit = rateLimit;
52
+ this.validateRateLimit(rateLimit);
99
53
 
100
- // Use the injected contract if provided; otherwise, instantiate a new one.
101
54
  this.contract = contract || new ethers.Contract(address, RLN_ABI, signer);
55
+ this.rateLimit = rateLimit;
102
56
 
103
57
  // Initialize event filters
104
58
  this._membersFilter = this.contract.filters.MembershipRegistered();
@@ -133,7 +87,7 @@ export class RLNLightContract {
133
87
  */
134
88
  public async getMinRateLimit(): Promise<number> {
135
89
  const minRate = await this.contract.minMembershipRateLimit();
136
- return minRate.toNumber();
90
+ return ethers.BigNumber.from(minRate).toNumber();
137
91
  }
138
92
 
139
93
  /**
@@ -142,7 +96,7 @@ export class RLNLightContract {
142
96
  */
143
97
  public async getMaxRateLimit(): Promise<number> {
144
98
  const maxRate = await this.contract.maxMembershipRateLimit();
145
- return maxRate.toNumber();
99
+ return ethers.BigNumber.from(maxRate).toNumber();
146
100
  }
147
101
 
148
102
  /**
@@ -180,6 +134,7 @@ export class RLNLightContract {
180
134
  * @param newRateLimit The new rate limit to use
181
135
  */
182
136
  public async setRateLimit(newRateLimit: number): Promise<void> {
137
+ this.validateRateLimit(newRateLimit);
183
138
  this.rateLimit = newRateLimit;
184
139
  }
185
140
 
@@ -190,43 +145,31 @@ export class RLNLightContract {
190
145
  return sortedMembers;
191
146
  }
192
147
 
193
- private get membersFilter(): ethers.EventFilter {
194
- if (!this._membersFilter) {
195
- throw Error("Members filter was not initialized.");
196
- }
197
- return this._membersFilter;
198
- }
199
-
200
- private get membershipErasedFilter(): ethers.EventFilter {
201
- if (!this._membershipErasedFilter) {
202
- throw Error("MembershipErased filter was not initialized.");
203
- }
204
- return this._membershipErasedFilter;
205
- }
206
-
207
- private get membersExpiredFilter(): ethers.EventFilter {
208
- if (!this._membersExpiredFilter) {
209
- throw Error("MembersExpired filter was not initialized.");
210
- }
211
- return this._membersExpiredFilter;
212
- }
213
-
214
148
  public async fetchMembers(options: FetchMembersOptions = {}): Promise<void> {
215
- const registeredMemberEvents = await queryFilter(this.contract, {
216
- fromBlock: this.deployBlock,
217
- ...options,
218
- membersFilter: this.membersFilter
219
- });
220
- const removedMemberEvents = await queryFilter(this.contract, {
221
- fromBlock: this.deployBlock,
222
- ...options,
223
- membersFilter: this.membershipErasedFilter
224
- });
225
- const expiredMemberEvents = await queryFilter(this.contract, {
226
- fromBlock: this.deployBlock,
227
- ...options,
228
- membersFilter: this.membersExpiredFilter
229
- });
149
+ const registeredMemberEvents = await RLNBaseContract.queryFilter(
150
+ this.contract,
151
+ {
152
+ fromBlock: this.deployBlock,
153
+ ...options,
154
+ membersFilter: this.membersFilter
155
+ }
156
+ );
157
+ const removedMemberEvents = await RLNBaseContract.queryFilter(
158
+ this.contract,
159
+ {
160
+ fromBlock: this.deployBlock,
161
+ ...options,
162
+ membersFilter: this.membershipErasedFilter
163
+ }
164
+ );
165
+ const expiredMemberEvents = await RLNBaseContract.queryFilter(
166
+ this.contract,
167
+ {
168
+ fromBlock: this.deployBlock,
169
+ ...options,
170
+ membersFilter: this.membersExpiredFilter
171
+ }
172
+ );
230
173
 
231
174
  const events = [
232
175
  ...registeredMemberEvents,
@@ -236,6 +179,58 @@ export class RLNLightContract {
236
179
  this.processEvents(events);
237
180
  }
238
181
 
182
+ public static async queryFilter(
183
+ contract: ethers.Contract,
184
+ options: CustomQueryOptions
185
+ ): Promise<ethers.Event[]> {
186
+ const FETCH_CHUNK = 5;
187
+ const BLOCK_RANGE = 3000;
188
+
189
+ const {
190
+ fromBlock,
191
+ membersFilter,
192
+ fetchRange = BLOCK_RANGE,
193
+ fetchChunks = FETCH_CHUNK
194
+ } = options;
195
+
196
+ if (fromBlock === undefined) {
197
+ return contract.queryFilter(membersFilter);
198
+ }
199
+
200
+ if (!contract.provider) {
201
+ throw Error("No provider found on the contract.");
202
+ }
203
+
204
+ const toBlock = await contract.provider.getBlockNumber();
205
+
206
+ if (toBlock - fromBlock < fetchRange) {
207
+ return contract.queryFilter(membersFilter, fromBlock, toBlock);
208
+ }
209
+
210
+ const events: ethers.Event[][] = [];
211
+ const chunks = RLNBaseContract.splitToChunks(
212
+ fromBlock,
213
+ toBlock,
214
+ fetchRange
215
+ );
216
+
217
+ for (const portion of RLNBaseContract.takeN<[number, number]>(
218
+ chunks,
219
+ fetchChunks
220
+ )) {
221
+ const promises = portion.map(([left, right]) =>
222
+ RLNBaseContract.ignoreErrors(
223
+ contract.queryFilter(membersFilter, left, right),
224
+ []
225
+ )
226
+ );
227
+ const fetchedEvents = await Promise.all(promises);
228
+ events.push(fetchedEvents.flatMap((v) => v));
229
+ }
230
+
231
+ return events.flatMap((v) => v);
232
+ }
233
+
239
234
  public processEvents(events: ethers.Event[]): void {
240
235
  const toRemoveTable = new Map<number, number[]>();
241
236
  const toInsertTable = new Map<number, ethers.Event[]>();
@@ -278,6 +273,53 @@ export class RLNLightContract {
278
273
  });
279
274
  }
280
275
 
276
+ public static splitToChunks(
277
+ from: number,
278
+ to: number,
279
+ step: number
280
+ ): Array<[number, number]> {
281
+ const chunks: Array<[number, number]> = [];
282
+
283
+ let left = from;
284
+ while (left < to) {
285
+ const right = left + step < to ? left + step : to;
286
+
287
+ chunks.push([left, right] as [number, number]);
288
+
289
+ left = right;
290
+ }
291
+
292
+ return chunks;
293
+ }
294
+
295
+ public static *takeN<T>(array: T[], size: number): Iterable<T[]> {
296
+ let start = 0;
297
+
298
+ while (start < array.length) {
299
+ const portion = array.slice(start, start + size);
300
+
301
+ yield portion;
302
+
303
+ start += size;
304
+ }
305
+ }
306
+
307
+ public static async ignoreErrors<T>(
308
+ promise: Promise<T>,
309
+ defaultValue: T
310
+ ): Promise<T> {
311
+ try {
312
+ return await promise;
313
+ } catch (err: unknown) {
314
+ if (err instanceof Error) {
315
+ log.info(`Ignoring an error during query: ${err.message}`);
316
+ } else {
317
+ log.info(`Ignoring an unknown error during query`);
318
+ }
319
+ return defaultValue;
320
+ }
321
+ }
322
+
281
323
  public subscribeToMembers(): void {
282
324
  this.contract.on(
283
325
  this.membersFilter,
@@ -316,6 +358,116 @@ export class RLNLightContract {
316
358
  );
317
359
  }
318
360
 
361
+ /**
362
+ * Helper method to get remaining messages in current epoch
363
+ * @param membershipId The ID of the membership to check
364
+ * @returns number of remaining messages allowed in current epoch
365
+ */
366
+ public async getRemainingMessages(membershipId: number): Promise<number> {
367
+ try {
368
+ const [startTime, , rateLimit] =
369
+ await this.contract.getMembershipInfo(membershipId);
370
+
371
+ // Calculate current epoch
372
+ const currentTime = Math.floor(Date.now() / 1000);
373
+ const epochsPassed = Math.floor(
374
+ (currentTime - startTime) / RATE_LIMIT_PARAMS.EPOCH_LENGTH
375
+ );
376
+ const currentEpochStart =
377
+ startTime + epochsPassed * RATE_LIMIT_PARAMS.EPOCH_LENGTH;
378
+
379
+ // Get message count in current epoch using contract's function
380
+ const messageCount = await this.contract.getMessageCount(
381
+ membershipId,
382
+ currentEpochStart
383
+ );
384
+ return Math.max(
385
+ 0,
386
+ ethers.BigNumber.from(rateLimit)
387
+ .sub(ethers.BigNumber.from(messageCount))
388
+ .toNumber()
389
+ );
390
+ } catch (error) {
391
+ log.error(
392
+ `Error getting remaining messages: ${(error as Error).message}`
393
+ );
394
+ return 0; // Fail safe: assume no messages remaining on error
395
+ }
396
+ }
397
+
398
+ public async getMembershipInfo(
399
+ idCommitment: string
400
+ ): Promise<MembershipInfo | undefined> {
401
+ try {
402
+ const [startBlock, endBlock, rateLimit] =
403
+ await this.contract.getMembershipInfo(idCommitment);
404
+ const currentBlock = await this.contract.provider.getBlockNumber();
405
+
406
+ let state: MembershipState;
407
+ if (currentBlock < startBlock) {
408
+ state = MembershipState.Active;
409
+ } else if (currentBlock < endBlock) {
410
+ state = MembershipState.GracePeriod;
411
+ } else {
412
+ state = MembershipState.Expired;
413
+ }
414
+
415
+ const index = await this.getMemberIndex(idCommitment);
416
+ if (!index) return undefined;
417
+
418
+ return {
419
+ index,
420
+ idCommitment,
421
+ rateLimit: rateLimit.toNumber(),
422
+ startBlock: startBlock.toNumber(),
423
+ endBlock: endBlock.toNumber(),
424
+ state
425
+ };
426
+ } catch (error) {
427
+ return undefined;
428
+ }
429
+ }
430
+
431
+ public async extendMembership(
432
+ idCommitment: string
433
+ ): Promise<ethers.ContractTransaction> {
434
+ return this.contract.extendMemberships([idCommitment]);
435
+ }
436
+
437
+ public async eraseMembership(
438
+ idCommitment: string,
439
+ eraseFromMembershipSet: boolean = true
440
+ ): Promise<ethers.ContractTransaction> {
441
+ return this.contract.eraseMemberships(
442
+ [idCommitment],
443
+ eraseFromMembershipSet
444
+ );
445
+ }
446
+
447
+ public async registerMembership(
448
+ idCommitment: string,
449
+ rateLimit: number = DEFAULT_RATE_LIMIT
450
+ ): Promise<ethers.ContractTransaction> {
451
+ if (
452
+ rateLimit < RATE_LIMIT_PARAMS.MIN_RATE ||
453
+ rateLimit > RATE_LIMIT_PARAMS.MAX_RATE
454
+ ) {
455
+ throw new Error(
456
+ `Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE}`
457
+ );
458
+ }
459
+ return this.contract.register(idCommitment, rateLimit, []);
460
+ }
461
+
462
+ public async withdraw(token: string, holder: string): Promise<void> {
463
+ try {
464
+ const tx = await this.contract.withdraw(token, { from: holder });
465
+ await tx.wait();
466
+ } catch (error) {
467
+ log.error(`Error in withdraw: ${(error as Error).message}`);
468
+ }
469
+ }
470
+
319
471
  public async registerWithIdentity(
320
472
  identity: IdentityCredential
321
473
  ): Promise<DecryptedCredentials | undefined> {
@@ -428,38 +580,6 @@ export class RLNLightContract {
428
580
  }
429
581
  }
430
582
 
431
- /**
432
- * Helper method to get remaining messages in current epoch
433
- * @param membershipId The ID of the membership to check
434
- * @returns number of remaining messages allowed in current epoch
435
- */
436
- public async getRemainingMessages(membershipId: number): Promise<number> {
437
- try {
438
- const [startTime, , rateLimit] =
439
- await this.contract.getMembershipInfo(membershipId);
440
-
441
- // Calculate current epoch
442
- const currentTime = Math.floor(Date.now() / 1000);
443
- const epochsPassed = Math.floor(
444
- (currentTime - startTime) / RATE_LIMIT_PARAMS.EPOCH_LENGTH
445
- );
446
- const currentEpochStart =
447
- startTime + epochsPassed * RATE_LIMIT_PARAMS.EPOCH_LENGTH;
448
-
449
- // Get message count in current epoch using contract's function
450
- const messageCount = await this.contract.getMessageCount(
451
- membershipId,
452
- currentEpochStart
453
- );
454
- return Math.max(0, rateLimit.sub(messageCount).toNumber());
455
- } catch (error) {
456
- log.error(
457
- `Error getting remaining messages: ${(error as Error).message}`
458
- );
459
- return 0; // Fail safe: assume no messages remaining on error
460
- }
461
- }
462
-
463
583
  public async registerWithPermitAndErase(
464
584
  identity: IdentityCredential,
465
585
  permit: {
@@ -532,77 +652,40 @@ export class RLNLightContract {
532
652
  }
533
653
  }
534
654
 
535
- public async withdraw(token: string, holder: string): Promise<void> {
536
- try {
537
- const tx = await this.contract.withdraw(token, { from: holder });
538
- await tx.wait();
539
- } catch (error) {
540
- log.error(`Error in withdraw: ${(error as Error).message}`);
655
+ /**
656
+ * Validates that the rate limit is within the allowed range
657
+ * @throws Error if the rate limit is outside the allowed range
658
+ */
659
+ private validateRateLimit(rateLimit: number): void {
660
+ if (
661
+ rateLimit < RATE_LIMIT_PARAMS.MIN_RATE ||
662
+ rateLimit > RATE_LIMIT_PARAMS.MAX_RATE
663
+ ) {
664
+ throw new Error(
665
+ `Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE} messages per epoch`
666
+ );
541
667
  }
542
668
  }
543
669
 
544
- public async getMembershipInfo(
545
- idCommitment: string
546
- ): Promise<MembershipInfo | undefined> {
547
- try {
548
- const [startBlock, endBlock, rateLimit] =
549
- await this.contract.getMembershipInfo(idCommitment);
550
- const currentBlock = await this.contract.provider.getBlockNumber();
551
-
552
- let state: MembershipState;
553
- if (currentBlock < startBlock) {
554
- state = MembershipState.Active;
555
- } else if (currentBlock < endBlock) {
556
- state = MembershipState.GracePeriod;
557
- } else {
558
- state = MembershipState.Expired;
559
- }
560
-
561
- const index = await this.getMemberIndex(idCommitment);
562
- if (!index) return undefined;
563
-
564
- return {
565
- index,
566
- idCommitment,
567
- rateLimit: rateLimit.toNumber(),
568
- startBlock: startBlock.toNumber(),
569
- endBlock: endBlock.toNumber(),
570
- state
571
- };
572
- } catch (error) {
573
- return undefined;
670
+ private get membersFilter(): ethers.EventFilter {
671
+ if (!this._membersFilter) {
672
+ throw Error("Members filter was not initialized.");
574
673
  }
674
+ return this._membersFilter;
575
675
  }
576
676
 
577
- public async extendMembership(
578
- idCommitment: string
579
- ): Promise<ethers.ContractTransaction> {
580
- return this.contract.extendMemberships([idCommitment]);
581
- }
582
-
583
- public async eraseMembership(
584
- idCommitment: string,
585
- eraseFromMembershipSet: boolean = true
586
- ): Promise<ethers.ContractTransaction> {
587
- return this.contract.eraseMemberships(
588
- [idCommitment],
589
- eraseFromMembershipSet
590
- );
677
+ private get membershipErasedFilter(): ethers.EventFilter {
678
+ if (!this._membershipErasedFilter) {
679
+ throw Error("MembershipErased filter was not initialized.");
680
+ }
681
+ return this._membershipErasedFilter;
591
682
  }
592
683
 
593
- public async registerMembership(
594
- idCommitment: string,
595
- rateLimit: number = DEFAULT_RATE_LIMIT
596
- ): Promise<ethers.ContractTransaction> {
597
- if (
598
- rateLimit < RATE_LIMIT_PARAMS.MIN_RATE ||
599
- rateLimit > RATE_LIMIT_PARAMS.MAX_RATE
600
- ) {
601
- throw new Error(
602
- `Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE}`
603
- );
684
+ private get membersExpiredFilter(): ethers.EventFilter {
685
+ if (!this._membersExpiredFilter) {
686
+ throw Error("MembersExpired filter was not initialized.");
604
687
  }
605
- return this.contract.register(idCommitment, rateLimit, []);
688
+ return this._membersExpiredFilter;
606
689
  }
607
690
 
608
691
  private async getMemberIndex(
@@ -622,97 +705,3 @@ export class RLNLightContract {
622
705
  }
623
706
  }
624
707
  }
625
-
626
- interface CustomQueryOptions extends FetchMembersOptions {
627
- membersFilter: ethers.EventFilter;
628
- }
629
-
630
- // These values should be tested on other networks
631
- const FETCH_CHUNK = 5;
632
- const BLOCK_RANGE = 3000;
633
-
634
- async function queryFilter(
635
- contract: ethers.Contract,
636
- options: CustomQueryOptions
637
- ): Promise<ethers.Event[]> {
638
- const {
639
- fromBlock,
640
- membersFilter,
641
- fetchRange = BLOCK_RANGE,
642
- fetchChunks = FETCH_CHUNK
643
- } = options;
644
-
645
- if (fromBlock === undefined) {
646
- return contract.queryFilter(membersFilter);
647
- }
648
-
649
- if (!contract.provider) {
650
- throw Error("No provider found on the contract.");
651
- }
652
-
653
- const toBlock = await contract.provider.getBlockNumber();
654
-
655
- if (toBlock - fromBlock < fetchRange) {
656
- return contract.queryFilter(membersFilter, fromBlock, toBlock);
657
- }
658
-
659
- const events: ethers.Event[][] = [];
660
- const chunks = splitToChunks(fromBlock, toBlock, fetchRange);
661
-
662
- for (const portion of takeN<[number, number]>(chunks, fetchChunks)) {
663
- const promises = portion.map(([left, right]) =>
664
- ignoreErrors(contract.queryFilter(membersFilter, left, right), [])
665
- );
666
- const fetchedEvents = await Promise.all(promises);
667
- events.push(fetchedEvents.flatMap((v) => v));
668
- }
669
-
670
- return events.flatMap((v) => v);
671
- }
672
-
673
- function splitToChunks(
674
- from: number,
675
- to: number,
676
- step: number
677
- ): Array<[number, number]> {
678
- const chunks: Array<[number, number]> = [];
679
-
680
- let left = from;
681
- while (left < to) {
682
- const right = left + step < to ? left + step : to;
683
-
684
- chunks.push([left, right] as [number, number]);
685
-
686
- left = right;
687
- }
688
-
689
- return chunks;
690
- }
691
-
692
- function* takeN<T>(array: T[], size: number): Iterable<T[]> {
693
- let start = 0;
694
-
695
- while (start < array.length) {
696
- const portion = array.slice(start, start + size);
697
-
698
- yield portion;
699
-
700
- start += size;
701
- }
702
- }
703
-
704
- async function ignoreErrors<T>(
705
- promise: Promise<T>,
706
- defaultValue: T
707
- ): Promise<T> {
708
- try {
709
- return await promise;
710
- } catch (err: unknown) {
711
- if (err instanceof Error) {
712
- log.info(`Ignoring an error during query: ${err.message}`);
713
- } else {
714
- log.info(`Ignoring an unknown error during query`);
715
- }
716
- return defaultValue;
717
- }
718
- }