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