@waku/rln 0.1.5-f39d215.0 → 0.1.5-fdaf129.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 (71) hide show
  1. package/bundle/index.js +4 -3
  2. package/bundle/packages/rln/dist/contract/constants.js +2 -1
  3. package/bundle/packages/rln/dist/contract/{rln_light_contract.js → rln_base_contract.js} +185 -184
  4. package/bundle/packages/rln/dist/contract/rln_contract.js +9 -419
  5. package/bundle/packages/rln/dist/contract/types.js +9 -0
  6. package/bundle/packages/rln/dist/create.js +1 -1
  7. package/bundle/packages/rln/dist/{rln_light.js → credentials_manager.js} +114 -48
  8. package/bundle/packages/rln/dist/identity.js +0 -9
  9. package/bundle/packages/rln/dist/keystore/keystore.js +27 -13
  10. package/bundle/packages/rln/dist/rln.js +56 -166
  11. package/bundle/packages/rln/dist/zerokit.js +5 -5
  12. package/dist/.tsbuildinfo +1 -1
  13. package/dist/contract/constants.d.ts +1 -1
  14. package/dist/contract/constants.js +1 -1
  15. package/dist/contract/constants.js.map +1 -1
  16. package/dist/contract/index.d.ts +1 -0
  17. package/dist/contract/index.js +1 -0
  18. package/dist/contract/index.js.map +1 -1
  19. package/dist/contract/{rln_light_contract.d.ts → rln_base_contract.d.ts} +24 -58
  20. package/dist/contract/{rln_light_contract.js → rln_base_contract.js} +185 -184
  21. package/dist/contract/rln_base_contract.js.map +1 -0
  22. package/dist/contract/rln_contract.d.ts +5 -122
  23. package/dist/contract/rln_contract.js +8 -417
  24. package/dist/contract/rln_contract.js.map +1 -1
  25. package/dist/contract/types.d.ts +45 -0
  26. package/dist/contract/types.js +8 -0
  27. package/dist/contract/types.js.map +1 -0
  28. package/dist/create.js +1 -1
  29. package/dist/create.js.map +1 -1
  30. package/dist/credentials_manager.d.ts +44 -0
  31. package/dist/credentials_manager.js +197 -0
  32. package/dist/credentials_manager.js.map +1 -0
  33. package/dist/identity.d.ts +0 -1
  34. package/dist/identity.js +0 -9
  35. package/dist/identity.js.map +1 -1
  36. package/dist/index.d.ts +5 -4
  37. package/dist/index.js +4 -3
  38. package/dist/index.js.map +1 -1
  39. package/dist/keystore/keystore.d.ts +1 -0
  40. package/dist/keystore/keystore.js +27 -13
  41. package/dist/keystore/keystore.js.map +1 -1
  42. package/dist/keystore/types.d.ts +2 -2
  43. package/dist/rln.d.ts +9 -52
  44. package/dist/rln.js +54 -163
  45. package/dist/rln.js.map +1 -1
  46. package/dist/types.d.ts +27 -0
  47. package/dist/types.js +2 -0
  48. package/dist/types.js.map +1 -0
  49. package/dist/zerokit.d.ts +3 -3
  50. package/dist/zerokit.js +5 -5
  51. package/dist/zerokit.js.map +1 -1
  52. package/package.json +1 -1
  53. package/src/contract/constants.ts +1 -1
  54. package/src/contract/index.ts +1 -0
  55. package/src/contract/{rln_light_contract.ts → rln_base_contract.ts} +303 -321
  56. package/src/contract/rln_contract.ts +9 -663
  57. package/src/contract/types.ts +53 -0
  58. package/src/create.ts +1 -1
  59. package/src/credentials_manager.ts +282 -0
  60. package/src/identity.ts +0 -10
  61. package/src/index.ts +7 -5
  62. package/src/keystore/keystore.ts +53 -27
  63. package/src/keystore/types.ts +2 -2
  64. package/src/rln.ts +67 -258
  65. package/src/types.ts +31 -0
  66. package/src/zerokit.ts +3 -3
  67. package/dist/contract/rln_light_contract.js.map +0 -1
  68. package/dist/rln_light.d.ts +0 -64
  69. package/dist/rln_light.js +0 -144
  70. package/dist/rln_light.js.map +0 -1
  71. package/src/rln_light.ts +0 -235
@@ -1,84 +1,39 @@
1
+ /* eslint-disable no-console */
1
2
  import { Logger } from "@waku/utils";
2
3
  import { ethers } from "ethers";
3
4
 
4
- import type { IdentityCredential } from "../identity.js";
5
- import type { DecryptedCredentials } from "../keystore/index.js";
5
+ import { IdentityCredential } from "../identity.js";
6
+ import { DecryptedCredentials } from "../keystore/types.js";
6
7
 
7
8
  import { RLN_ABI } from "./abi.js";
8
9
  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 {
10
+ import {
11
+ CustomQueryOptions,
12
+ FetchMembersOptions,
13
+ Member,
14
+ MembershipInfo,
15
+ MembershipRegisteredEvent,
16
+ MembershipState,
17
+ RLNContractInitOptions
18
+ } from "./types.js";
19
+
20
+ const log = new Logger("waku:rln:contract:base");
21
+
22
+ export class RLNBaseContract {
56
23
  public contract: ethers.Contract;
57
-
58
24
  private deployBlock: undefined | number;
59
25
  private rateLimit: number;
60
26
 
61
- private _members: Map<number, Member> = new Map();
27
+ protected _members: Map<number, Member> = new Map();
62
28
  private _membersFilter: ethers.EventFilter;
63
29
  private _membershipErasedFilter: ethers.EventFilter;
64
30
  private _membersExpiredFilter: ethers.EventFilter;
65
31
 
66
32
  /**
67
- * Asynchronous initializer for RLNContract.
33
+ * Constructor for RLNBaseContract.
68
34
  * Allows injecting a mocked contract for testing purposes.
69
35
  */
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) {
36
+ public constructor(options: RLNContractInitOptions) {
82
37
  const {
83
38
  address,
84
39
  signer,
@@ -86,24 +41,38 @@ export class RLNLightContract {
86
41
  contract
87
42
  } = options;
88
43
 
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
- }
44
+ log.info("Initializing RLNBaseContract", { address, rateLimit });
97
45
 
46
+ this.contract = contract || new ethers.Contract(address, RLN_ABI, signer);
98
47
  this.rateLimit = rateLimit;
99
48
 
100
- // Use the injected contract if provided; otherwise, instantiate a new one.
101
- this.contract = contract || new ethers.Contract(address, RLN_ABI, signer);
49
+ try {
50
+ log.info("Setting up event filters");
51
+ // Initialize event filters
52
+ this._membersFilter = this.contract.filters.MembershipRegistered();
53
+ this._membershipErasedFilter = this.contract.filters.MembershipErased();
54
+ this._membersExpiredFilter = this.contract.filters.MembershipExpired();
55
+ log.info("Event filters initialized successfully");
56
+ } catch (error) {
57
+ log.error("Failed to initialize event filters", { error });
58
+ throw new Error(
59
+ "Failed to initialize event filters: " + (error as Error).message
60
+ );
61
+ }
102
62
 
103
- // Initialize event filters
104
- this._membersFilter = this.contract.filters.MembershipRegistered();
105
- this._membershipErasedFilter = this.contract.filters.MembershipErased();
106
- this._membersExpiredFilter = this.contract.filters.MembershipExpired();
63
+ // Initialize members and subscriptions
64
+ this.fetchMembers()
65
+ .then(() => {
66
+ this.subscribeToMembers();
67
+ })
68
+ .catch((error) => {
69
+ log.error("Failed to initialize members", { error });
70
+ });
71
+
72
+ // Validate rate limit asynchronously
73
+ this.validateRateLimit(rateLimit).catch((error) => {
74
+ log.error("Failed to validate initial rate limit", { error });
75
+ });
107
76
  }
108
77
 
109
78
  /**
@@ -151,7 +120,7 @@ export class RLNLightContract {
151
120
  */
152
121
  public async getMaxTotalRateLimit(): Promise<number> {
153
122
  const maxTotalRate = await this.contract.maxTotalRateLimit();
154
- return ethers.BigNumber.from(maxTotalRate).toNumber();
123
+ return maxTotalRate.toNumber();
155
124
  }
156
125
 
157
126
  /**
@@ -160,7 +129,7 @@ export class RLNLightContract {
160
129
  */
161
130
  public async getCurrentTotalRateLimit(): Promise<number> {
162
131
  const currentTotal = await this.contract.currentTotalRateLimit();
163
- return ethers.BigNumber.from(currentTotal).toNumber();
132
+ return currentTotal.toNumber();
164
133
  }
165
134
 
166
135
  /**
@@ -172,9 +141,7 @@ export class RLNLightContract {
172
141
  this.contract.maxTotalRateLimit(),
173
142
  this.contract.currentTotalRateLimit()
174
143
  ]);
175
- return ethers.BigNumber.from(maxTotal)
176
- .sub(ethers.BigNumber.from(currentTotal))
177
- .toNumber();
144
+ return Number(maxTotal) - Number(currentTotal);
178
145
  }
179
146
 
180
147
  /**
@@ -182,6 +149,7 @@ export class RLNLightContract {
182
149
  * @param newRateLimit The new rate limit to use
183
150
  */
184
151
  public async setRateLimit(newRateLimit: number): Promise<void> {
152
+ await this.validateRateLimit(newRateLimit);
185
153
  this.rateLimit = newRateLimit;
186
154
  }
187
155
 
@@ -192,43 +160,31 @@ export class RLNLightContract {
192
160
  return sortedMembers;
193
161
  }
194
162
 
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
163
  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
- });
164
+ const registeredMemberEvents = await RLNBaseContract.queryFilter(
165
+ this.contract,
166
+ {
167
+ fromBlock: this.deployBlock,
168
+ ...options,
169
+ membersFilter: this.membersFilter
170
+ }
171
+ );
172
+ const removedMemberEvents = await RLNBaseContract.queryFilter(
173
+ this.contract,
174
+ {
175
+ fromBlock: this.deployBlock,
176
+ ...options,
177
+ membersFilter: this.membershipErasedFilter
178
+ }
179
+ );
180
+ const expiredMemberEvents = await RLNBaseContract.queryFilter(
181
+ this.contract,
182
+ {
183
+ fromBlock: this.deployBlock,
184
+ ...options,
185
+ membersFilter: this.membersExpiredFilter
186
+ }
187
+ );
232
188
 
233
189
  const events = [
234
190
  ...registeredMemberEvents,
@@ -238,6 +194,58 @@ export class RLNLightContract {
238
194
  this.processEvents(events);
239
195
  }
240
196
 
197
+ public static async queryFilter(
198
+ contract: ethers.Contract,
199
+ options: CustomQueryOptions
200
+ ): Promise<ethers.Event[]> {
201
+ const FETCH_CHUNK = 5;
202
+ const BLOCK_RANGE = 3000;
203
+
204
+ const {
205
+ fromBlock,
206
+ membersFilter,
207
+ fetchRange = BLOCK_RANGE,
208
+ fetchChunks = FETCH_CHUNK
209
+ } = options;
210
+
211
+ if (fromBlock === undefined) {
212
+ return contract.queryFilter(membersFilter);
213
+ }
214
+
215
+ if (!contract.provider) {
216
+ throw Error("No provider found on the contract.");
217
+ }
218
+
219
+ const toBlock = await contract.provider.getBlockNumber();
220
+
221
+ if (toBlock - fromBlock < fetchRange) {
222
+ return contract.queryFilter(membersFilter, fromBlock, toBlock);
223
+ }
224
+
225
+ const events: ethers.Event[][] = [];
226
+ const chunks = RLNBaseContract.splitToChunks(
227
+ fromBlock,
228
+ toBlock,
229
+ fetchRange
230
+ );
231
+
232
+ for (const portion of RLNBaseContract.takeN<[number, number]>(
233
+ chunks,
234
+ fetchChunks
235
+ )) {
236
+ const promises = portion.map(([left, right]) =>
237
+ RLNBaseContract.ignoreErrors(
238
+ contract.queryFilter(membersFilter, left, right),
239
+ []
240
+ )
241
+ );
242
+ const fetchedEvents = await Promise.all(promises);
243
+ events.push(fetchedEvents.flatMap((v) => v));
244
+ }
245
+
246
+ return events.flatMap((v) => v);
247
+ }
248
+
241
249
  public processEvents(events: ethers.Event[]): void {
242
250
  const toRemoveTable = new Map<number, number[]>();
243
251
  const toInsertTable = new Map<number, ethers.Event[]>();
@@ -280,11 +288,58 @@ export class RLNLightContract {
280
288
  });
281
289
  }
282
290
 
291
+ public static splitToChunks(
292
+ from: number,
293
+ to: number,
294
+ step: number
295
+ ): Array<[number, number]> {
296
+ const chunks: Array<[number, number]> = [];
297
+
298
+ let left = from;
299
+ while (left < to) {
300
+ const right = left + step < to ? left + step : to;
301
+
302
+ chunks.push([left, right] as [number, number]);
303
+
304
+ left = right;
305
+ }
306
+
307
+ return chunks;
308
+ }
309
+
310
+ public static *takeN<T>(array: T[], size: number): Iterable<T[]> {
311
+ let start = 0;
312
+
313
+ while (start < array.length) {
314
+ const portion = array.slice(start, start + size);
315
+
316
+ yield portion;
317
+
318
+ start += size;
319
+ }
320
+ }
321
+
322
+ public static async ignoreErrors<T>(
323
+ promise: Promise<T>,
324
+ defaultValue: T
325
+ ): Promise<T> {
326
+ try {
327
+ return await promise;
328
+ } catch (err: unknown) {
329
+ if (err instanceof Error) {
330
+ log.info(`Ignoring an error during query: ${err.message}`);
331
+ } else {
332
+ log.info(`Ignoring an unknown error during query`);
333
+ }
334
+ return defaultValue;
335
+ }
336
+ }
337
+
283
338
  public subscribeToMembers(): void {
284
339
  this.contract.on(
285
340
  this.membersFilter,
286
341
  (
287
- _idCommitment: string,
342
+ _idCommitment: bigint,
288
343
  _membershipRateLimit: ethers.BigNumber,
289
344
  _index: ethers.BigNumber,
290
345
  event: ethers.Event
@@ -296,7 +351,7 @@ export class RLNLightContract {
296
351
  this.contract.on(
297
352
  this.membershipErasedFilter,
298
353
  (
299
- _idCommitment: string,
354
+ _idCommitment: bigint,
300
355
  _membershipRateLimit: ethers.BigNumber,
301
356
  _index: ethers.BigNumber,
302
357
  event: ethers.Event
@@ -308,7 +363,7 @@ export class RLNLightContract {
308
363
  this.contract.on(
309
364
  this.membersExpiredFilter,
310
365
  (
311
- _idCommitment: string,
366
+ _idCommitment: bigint,
312
367
  _membershipRateLimit: ethers.BigNumber,
313
368
  _index: ethers.BigNumber,
314
369
  event: ethers.Event
@@ -318,6 +373,96 @@ export class RLNLightContract {
318
373
  );
319
374
  }
320
375
 
376
+ public async getMembershipInfo(
377
+ idCommitmentBigInt: bigint
378
+ ): Promise<MembershipInfo | undefined> {
379
+ try {
380
+ const membershipData =
381
+ await this.contract.memberships(idCommitmentBigInt);
382
+ const currentBlock = await this.contract.provider.getBlockNumber();
383
+ console.log("membershipData", membershipData);
384
+
385
+ const [
386
+ depositAmount,
387
+ activeDuration,
388
+ gracePeriodStartTimestamp,
389
+ gracePeriodDuration,
390
+ rateLimit,
391
+ index,
392
+ holder,
393
+ token
394
+ ] = membershipData;
395
+
396
+ const gracePeriodEnd = gracePeriodStartTimestamp.add(gracePeriodDuration);
397
+
398
+ let state: MembershipState;
399
+ if (currentBlock < gracePeriodStartTimestamp.toNumber()) {
400
+ state = MembershipState.Active;
401
+ } else if (currentBlock < gracePeriodEnd.toNumber()) {
402
+ state = MembershipState.GracePeriod;
403
+ } else {
404
+ state = MembershipState.Expired;
405
+ }
406
+
407
+ return {
408
+ index,
409
+ idCommitment: idCommitmentBigInt.toString(),
410
+ rateLimit: Number(rateLimit),
411
+ startBlock: gracePeriodStartTimestamp.toNumber(),
412
+ endBlock: gracePeriodEnd.toNumber(),
413
+ state,
414
+ depositAmount,
415
+ activeDuration,
416
+ gracePeriodDuration,
417
+ holder,
418
+ token
419
+ };
420
+ } catch (error) {
421
+ console.error("Error in getMembershipInfo:", error);
422
+ return undefined;
423
+ }
424
+ }
425
+
426
+ public async extendMembership(
427
+ idCommitmentBigInt: bigint
428
+ ): Promise<ethers.ContractTransaction> {
429
+ return this.contract.extendMemberships([idCommitmentBigInt]);
430
+ }
431
+
432
+ public async eraseMembership(
433
+ idCommitmentBigInt: bigint,
434
+ eraseFromMembershipSet: boolean = true
435
+ ): Promise<ethers.ContractTransaction> {
436
+ return this.contract.eraseMemberships(
437
+ [idCommitmentBigInt],
438
+ eraseFromMembershipSet
439
+ );
440
+ }
441
+
442
+ public async registerMembership(
443
+ idCommitmentBigInt: bigint,
444
+ rateLimit: number = DEFAULT_RATE_LIMIT
445
+ ): Promise<ethers.ContractTransaction> {
446
+ if (
447
+ rateLimit < RATE_LIMIT_PARAMS.MIN_RATE ||
448
+ rateLimit > RATE_LIMIT_PARAMS.MAX_RATE
449
+ ) {
450
+ throw new Error(
451
+ `Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE}`
452
+ );
453
+ }
454
+ return this.contract.register(idCommitmentBigInt, rateLimit, []);
455
+ }
456
+
457
+ public async withdraw(token: string, from: string): Promise<void> {
458
+ try {
459
+ const tx = await this.contract.withdraw(token, from);
460
+ await tx.wait();
461
+ } catch (error) {
462
+ log.error(`Error in withdraw: ${(error as Error).message}`);
463
+ }
464
+ }
465
+
321
466
  public async registerWithIdentity(
322
467
  identity: IdentityCredential
323
468
  ): Promise<DecryptedCredentials | undefined> {
@@ -328,7 +473,7 @@ export class RLNLightContract {
328
473
 
329
474
  // Check if the ID commitment is already registered
330
475
  const existingIndex = await this.getMemberIndex(
331
- identity.IDCommitmentBigInt.toString()
476
+ identity.IDCommitmentBigInt
332
477
  );
333
478
  if (existingIndex) {
334
479
  throw new Error(
@@ -389,13 +534,13 @@ export class RLNLightContract {
389
534
 
390
535
  const network = await this.contract.provider.getNetwork();
391
536
  const address = this.contract.address;
392
- const membershipId = decodedData.index.toString();
537
+ const membershipId = Number(decodedData.index);
393
538
 
394
539
  return {
395
540
  identity,
396
541
  membership: {
397
542
  address,
398
- treeIndex: parseInt(membershipId),
543
+ treeIndex: membershipId,
399
544
  chainId: network.chainId.toString(),
400
545
  rateLimit: decodedData.membershipRateLimit.toNumber()
401
546
  }
@@ -430,43 +575,6 @@ export class RLNLightContract {
430
575
  }
431
576
  }
432
577
 
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
578
  public async registerWithPermitAndErase(
471
579
  identity: IdentityCredential,
472
580
  permit: {
@@ -520,13 +628,13 @@ export class RLNLightContract {
520
628
 
521
629
  const network = await this.contract.provider.getNetwork();
522
630
  const address = this.contract.address;
523
- const membershipId = decodedData.index.toString();
631
+ const membershipId = Number(decodedData.index);
524
632
 
525
633
  return {
526
634
  identity,
527
635
  membership: {
528
636
  address,
529
- treeIndex: parseInt(membershipId),
637
+ treeIndex: membershipId,
530
638
  chainId: network.chainId.toString(),
531
639
  rateLimit: decodedData.membershipRateLimit.toNumber()
532
640
  }
@@ -539,85 +647,53 @@ export class RLNLightContract {
539
647
  }
540
648
  }
541
649
 
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
- }
650
+ /**
651
+ * Validates that the rate limit is within the allowed range
652
+ * @throws Error if the rate limit is outside the allowed range
653
+ */
654
+ private async validateRateLimit(rateLimit: number): Promise<void> {
655
+ const [minRate, maxRate] = await Promise.all([
656
+ this.contract.minMembershipRateLimit(),
657
+ this.contract.maxMembershipRateLimit()
658
+ ]);
567
659
 
568
- const index = await this.getMemberIndex(idCommitment);
569
- if (!index) return undefined;
660
+ const minRateNum = ethers.BigNumber.from(minRate).toNumber();
661
+ const maxRateNum = ethers.BigNumber.from(maxRate).toNumber();
570
662
 
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;
663
+ if (rateLimit < minRateNum || rateLimit > maxRateNum) {
664
+ throw new Error(
665
+ `Rate limit must be between ${minRateNum} and ${maxRateNum} messages per epoch`
666
+ );
581
667
  }
582
668
  }
583
669
 
584
- public async extendMembership(
585
- idCommitment: string
586
- ): Promise<ethers.ContractTransaction> {
587
- return this.contract.extendMemberships([idCommitment]);
670
+ private get membersFilter(): ethers.EventFilter {
671
+ if (!this._membersFilter) {
672
+ throw Error("Members filter was not initialized.");
673
+ }
674
+ return this._membersFilter;
588
675
  }
589
676
 
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
- );
677
+ private get membershipErasedFilter(): ethers.EventFilter {
678
+ if (!this._membershipErasedFilter) {
679
+ throw Error("MembershipErased filter was not initialized.");
680
+ }
681
+ return this._membershipErasedFilter;
598
682
  }
599
683
 
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
- );
684
+ private get membersExpiredFilter(): ethers.EventFilter {
685
+ if (!this._membersExpiredFilter) {
686
+ throw Error("MembersExpired filter was not initialized.");
611
687
  }
612
- return this.contract.register(idCommitment, rateLimit, []);
688
+ return this._membersExpiredFilter;
613
689
  }
614
690
 
615
691
  private async getMemberIndex(
616
- idCommitment: string
692
+ idCommitmentBigInt: bigint
617
693
  ): Promise<ethers.BigNumber | undefined> {
618
694
  try {
619
695
  const events = await this.contract.queryFilter(
620
- this.contract.filters.MembershipRegistered(idCommitment)
696
+ this.contract.filters.MembershipRegistered(idCommitmentBigInt)
621
697
  );
622
698
  if (events.length === 0) return undefined;
623
699
 
@@ -629,97 +705,3 @@ export class RLNLightContract {
629
705
  }
630
706
  }
631
707
  }
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
- }