@waku/rln 0.1.5-053bb95.0 → 0.1.5-4adf870.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 (49) hide show
  1. package/bundle/_virtual/utils.js +2 -2
  2. package/bundle/_virtual/utils2.js +2 -2
  3. package/bundle/packages/rln/dist/contract/constants.js +5 -2
  4. package/bundle/packages/rln/dist/contract/rln_base_contract.js +246 -116
  5. package/bundle/packages/rln/dist/contract/rln_contract.js +74 -89
  6. package/bundle/packages/rln/dist/credentials_manager.js +2 -2
  7. package/bundle/packages/rln/dist/identity.js +0 -8
  8. package/bundle/packages/rln/dist/keystore/keystore.js +28 -19
  9. package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/node_modules/ethereum-cryptography/random.js +1 -1
  10. package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/node_modules/ethereum-cryptography/utils.js +2 -2
  11. package/bundle/packages/rln/node_modules/@noble/hashes/_sha2.js +1 -1
  12. package/bundle/packages/rln/node_modules/@noble/hashes/hmac.js +1 -1
  13. package/bundle/packages/rln/node_modules/@noble/hashes/pbkdf2.js +1 -1
  14. package/bundle/packages/rln/node_modules/@noble/hashes/scrypt.js +1 -1
  15. package/bundle/packages/rln/node_modules/@noble/hashes/sha256.js +1 -1
  16. package/bundle/packages/rln/node_modules/@noble/hashes/sha512.js +1 -1
  17. package/bundle/packages/rln/node_modules/@noble/hashes/utils.js +1 -1
  18. package/dist/.tsbuildinfo +1 -1
  19. package/dist/contract/constants.d.ts +1 -1
  20. package/dist/contract/constants.js +1 -1
  21. package/dist/contract/constants.js.map +1 -1
  22. package/dist/contract/rln_base_contract.d.ts +30 -22
  23. package/dist/contract/rln_base_contract.js +246 -116
  24. package/dist/contract/rln_base_contract.js.map +1 -1
  25. package/dist/contract/rln_contract.d.ts +4 -24
  26. package/dist/contract/rln_contract.js +74 -89
  27. package/dist/contract/rln_contract.js.map +1 -1
  28. package/dist/credentials_manager.js +2 -2
  29. package/dist/credentials_manager.js.map +1 -1
  30. package/dist/identity.d.ts +0 -1
  31. package/dist/identity.js +0 -8
  32. package/dist/identity.js.map +1 -1
  33. package/dist/keystore/keystore.d.ts +1 -0
  34. package/dist/keystore/keystore.js +28 -19
  35. package/dist/keystore/keystore.js.map +1 -1
  36. package/dist/keystore/types.d.ts +1 -1
  37. package/package.json +1 -1
  38. package/src/contract/constants.ts +1 -1
  39. package/src/contract/rln_base_contract.ts +392 -185
  40. package/src/contract/rln_contract.ts +95 -120
  41. package/src/credentials_manager.ts +2 -2
  42. package/src/identity.ts +0 -9
  43. package/src/keystore/keystore.ts +46 -31
  44. package/src/keystore/types.ts +1 -1
  45. package/bundle/packages/rln/dist/contract/errors.js +0 -62
  46. package/dist/contract/errors.d.ts +0 -30
  47. package/dist/contract/errors.js +0 -61
  48. package/dist/contract/errors.js.map +0 -1
  49. package/src/contract/errors.ts +0 -75
@@ -1,3 +1,3 @@
1
- var utils = {exports: {}};
1
+ var utils = {};
2
2
 
3
- export { utils as __module };
3
+ export { utils as __exports };
@@ -1,3 +1,3 @@
1
- var utils = {};
1
+ var utils = {exports: {}};
2
2
 
3
- export { utils as __exports };
3
+ export { utils as __module };
@@ -1,7 +1,7 @@
1
1
  import { RLN_ABI } from './abi.js';
2
2
 
3
3
  const LINEA_CONTRACT = {
4
- chainId: "59141",
4
+ chainId: 59141,
5
5
  address: "0xb9cd878c90e49f797b4431fbf4fb333108cb90e6",
6
6
  abi: RLN_ABI
7
7
  };
@@ -17,7 +17,10 @@ const RATE_LIMIT_TIERS = {
17
17
  // Global rate limit parameters
18
18
  const RATE_LIMIT_PARAMS = {
19
19
  MIN_RATE: RATE_LIMIT_TIERS.LOW,
20
- MAX_RATE: RATE_LIMIT_TIERS.HIGH};
20
+ MAX_RATE: RATE_LIMIT_TIERS.HIGH,
21
+ MAX_TOTAL_RATE: 160_000, // Maximum total rate limit across all memberships
22
+ EPOCH_LENGTH: 600 // Epoch length in seconds (10 minutes)
23
+ };
21
24
  const DEFAULT_RATE_LIMIT = RATE_LIMIT_PARAMS.MAX_RATE;
22
25
 
23
26
  export { DEFAULT_RATE_LIMIT, LINEA_CONTRACT, RATE_LIMIT_PARAMS, RATE_LIMIT_TIERS };
@@ -15,7 +15,6 @@ import '../../../../node_modules/multiformats/dist/src/codecs/json.js';
15
15
  import { Logger } from '../../../utils/dist/logger/index.js';
16
16
  import { RLN_ABI } from './abi.js';
17
17
  import { DEFAULT_RATE_LIMIT, RATE_LIMIT_PARAMS } from './constants.js';
18
- import { RLNContractError, InvalidMembershipError, MembershipNotFoundError, MembershipExistsError, RateLimitExceededError, InvalidRateLimitError, TransactionError } from './errors.js';
19
18
  import { MembershipState } from './types.js';
20
19
  import { Contract } from '../../../../node_modules/@ethersproject/contracts/lib.esm/index.js';
21
20
  import { BigNumber } from '../../../../node_modules/@ethersproject/bignumber/lib.esm/bignumber.js';
@@ -23,12 +22,33 @@ import { BigNumber } from '../../../../node_modules/@ethersproject/bignumber/lib
23
22
  const log = new Logger("waku:rln:contract:base");
24
23
  class RLNBaseContract {
25
24
  contract;
25
+ deployBlock;
26
26
  rateLimit;
27
+ _members = new Map();
28
+ _membersFilter;
29
+ _membershipErasedFilter;
30
+ _membersExpiredFilter;
31
+ /**
32
+ * Constructor for RLNBaseContract.
33
+ * Allows injecting a mocked contract for testing purposes.
34
+ */
27
35
  constructor(options) {
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
+ });
28
44
  const { address, signer, rateLimit = DEFAULT_RATE_LIMIT, contract } = options;
29
45
  this.validateRateLimit(rateLimit);
30
46
  this.contract = contract || new Contract(address, RLN_ABI, signer);
31
47
  this.rateLimit = rateLimit;
48
+ // Initialize event filters
49
+ this._membersFilter = this.contract.filters.MembershipRegistered();
50
+ this._membershipErasedFilter = this.contract.filters.MembershipErased();
51
+ this._membersExpiredFilter = this.contract.filters.MembershipExpired();
32
52
  }
33
53
  /**
34
54
  * Gets the current rate limit for this contract instance
@@ -99,60 +119,155 @@ class RLNBaseContract {
99
119
  this.validateRateLimit(newRateLimit);
100
120
  this.rateLimit = newRateLimit;
101
121
  }
102
- /**
103
- * Gets all members in the given range
104
- * @param startIndex Start index (inclusive)
105
- * @param endIndex End index (exclusive)
106
- */
107
- async getMembersInRange(startIndex, endIndex) {
122
+ get members() {
123
+ const sortedMembers = Array.from(this._members.values()).sort((left, right) => left.index.toNumber() - right.index.toNumber());
124
+ return sortedMembers;
125
+ }
126
+ async fetchMembers(options = {}) {
127
+ const registeredMemberEvents = await RLNBaseContract.queryFilter(this.contract, {
128
+ fromBlock: this.deployBlock,
129
+ ...options,
130
+ membersFilter: this.membersFilter
131
+ });
132
+ const removedMemberEvents = await RLNBaseContract.queryFilter(this.contract, {
133
+ fromBlock: this.deployBlock,
134
+ ...options,
135
+ membersFilter: this.membershipErasedFilter
136
+ });
137
+ const expiredMemberEvents = await RLNBaseContract.queryFilter(this.contract, {
138
+ fromBlock: this.deployBlock,
139
+ ...options,
140
+ membersFilter: this.membersExpiredFilter
141
+ });
142
+ const events = [
143
+ ...registeredMemberEvents,
144
+ ...removedMemberEvents,
145
+ ...expiredMemberEvents
146
+ ];
147
+ this.processEvents(events);
148
+ }
149
+ static async queryFilter(contract, options) {
150
+ const FETCH_CHUNK = 5;
151
+ const BLOCK_RANGE = 3000;
152
+ const { fromBlock, membersFilter, fetchRange = BLOCK_RANGE, fetchChunks = FETCH_CHUNK } = options;
153
+ if (fromBlock === undefined) {
154
+ return contract.queryFilter(membersFilter);
155
+ }
156
+ if (!contract.provider) {
157
+ throw Error("No provider found on the contract.");
158
+ }
159
+ const toBlock = await contract.provider.getBlockNumber();
160
+ if (toBlock - fromBlock < fetchRange) {
161
+ return contract.queryFilter(membersFilter, fromBlock, toBlock);
162
+ }
163
+ const events = [];
164
+ const chunks = RLNBaseContract.splitToChunks(fromBlock, toBlock, fetchRange);
165
+ for (const portion of RLNBaseContract.takeN(chunks, fetchChunks)) {
166
+ const promises = portion.map(([left, right]) => RLNBaseContract.ignoreErrors(contract.queryFilter(membersFilter, left, right), []));
167
+ const fetchedEvents = await Promise.all(promises);
168
+ events.push(fetchedEvents.flatMap((v) => v));
169
+ }
170
+ return events.flatMap((v) => v);
171
+ }
172
+ processEvents(events) {
173
+ const toRemoveTable = new Map();
174
+ const toInsertTable = new Map();
175
+ events.forEach((evt) => {
176
+ if (!evt.args) {
177
+ return;
178
+ }
179
+ if (evt.event === "MembershipErased" ||
180
+ evt.event === "MembershipExpired") {
181
+ let index = evt.args.index;
182
+ if (!index) {
183
+ return;
184
+ }
185
+ if (typeof index === "number" || typeof index === "string") {
186
+ index = BigNumber.from(index);
187
+ }
188
+ const toRemoveVal = toRemoveTable.get(evt.blockNumber);
189
+ if (toRemoveVal != undefined) {
190
+ toRemoveVal.push(index.toNumber());
191
+ toRemoveTable.set(evt.blockNumber, toRemoveVal);
192
+ }
193
+ else {
194
+ toRemoveTable.set(evt.blockNumber, [index.toNumber()]);
195
+ }
196
+ }
197
+ else if (evt.event === "MembershipRegistered") {
198
+ let eventsPerBlock = toInsertTable.get(evt.blockNumber);
199
+ if (eventsPerBlock == undefined) {
200
+ eventsPerBlock = [];
201
+ }
202
+ eventsPerBlock.push(evt);
203
+ toInsertTable.set(evt.blockNumber, eventsPerBlock);
204
+ }
205
+ });
206
+ }
207
+ static splitToChunks(from, to, step) {
208
+ const chunks = [];
209
+ let left = from;
210
+ while (left < to) {
211
+ const right = left + step < to ? left + step : to;
212
+ chunks.push([left, right]);
213
+ left = right;
214
+ }
215
+ return chunks;
216
+ }
217
+ static *takeN(array, size) {
218
+ let start = 0;
219
+ while (start < array.length) {
220
+ const portion = array.slice(start, start + size);
221
+ yield portion;
222
+ start += size;
223
+ }
224
+ }
225
+ static async ignoreErrors(promise, defaultValue) {
108
226
  try {
109
- // Get all commitments in one call
110
- const idCommitments = await this.contract.getRateCommitmentsInRangeBoundsInclusive(startIndex, endIndex - 1 // -1 because getRateCommitmentsInRangeBoundsInclusive is inclusive
111
- );
112
- // Get membership info for each commitment in a single batch
113
- const membershipPromises = idCommitments.map((idCommitment) => this.contract
114
- .memberships(idCommitment)
115
- .then((info) => ({
116
- idCommitment: idCommitment.toString(),
117
- index: BigNumber.from(info.index)
118
- }))
119
- .catch(() => null) // Skip invalid members
120
- );
121
- // Wait for all promises to resolve
122
- const members = (await Promise.all(membershipPromises)).filter((m) => m !== null);
123
- return members;
227
+ return await promise;
124
228
  }
125
- catch (error) {
126
- if (error instanceof Error &&
127
- error.message.includes("InvalidPaginationQuery")) {
128
- throw new RLNContractError(`Invalid pagination range: start=${startIndex}, end=${endIndex}`);
229
+ catch (err) {
230
+ if (err instanceof Error) {
231
+ log.info(`Ignoring an error during query: ${err.message}`);
232
+ }
233
+ else {
234
+ log.info(`Ignoring an unknown error during query`);
129
235
  }
130
- throw error;
236
+ return defaultValue;
131
237
  }
132
238
  }
133
- /**
134
- * Gets all current members
135
- */
136
- async getAllMembers() {
137
- const nextIndex = (await this.contract.nextFreeIndex()).toNumber();
138
- return this.getMembersInRange(0, nextIndex);
239
+ subscribeToMembers() {
240
+ this.contract.on(this.membersFilter, (_idCommitment, _membershipRateLimit, _index, event) => {
241
+ this.processEvents([event]);
242
+ });
243
+ this.contract.on(this.membershipErasedFilter, (_idCommitment, _membershipRateLimit, _index, event) => {
244
+ this.processEvents([event]);
245
+ });
246
+ this.contract.on(this.membersExpiredFilter, (_idCommitment, _membershipRateLimit, _index, event) => {
247
+ this.processEvents([event]);
248
+ });
139
249
  }
140
250
  /**
141
- * Gets the member index if it exists, or null if it doesn't
142
- * Throws only on actual errors (invalid input, network issues, etc)
251
+ * Helper method to get remaining messages in current epoch
252
+ * @param membershipId The ID of the membership to check
253
+ * @returns number of remaining messages allowed in current epoch
143
254
  */
144
- async getMemberIndex(idCommitment) {
255
+ async getRemainingMessages(membershipId) {
145
256
  try {
146
- const isValid = await this.contract.isInMembershipSet(idCommitment);
147
- if (!isValid) {
148
- return null;
149
- }
150
- const membershipInfo = await this.contract.memberships(idCommitment);
151
- return BigNumber.from(membershipInfo.index);
257
+ const [startTime, , rateLimit] = await this.contract.getMembershipInfo(membershipId);
258
+ // Calculate current epoch
259
+ const currentTime = Math.floor(Date.now() / 1000);
260
+ const epochsPassed = Math.floor((currentTime - startTime) / RATE_LIMIT_PARAMS.EPOCH_LENGTH);
261
+ const currentEpochStart = startTime + epochsPassed * RATE_LIMIT_PARAMS.EPOCH_LENGTH;
262
+ // Get message count in current epoch using contract's function
263
+ const messageCount = await this.contract.getMessageCount(membershipId, currentEpochStart);
264
+ return Math.max(0, BigNumber.from(rateLimit)
265
+ .sub(BigNumber.from(messageCount))
266
+ .toNumber());
152
267
  }
153
268
  catch (error) {
154
- log.error(`Error getting member index: ${error.message}`);
155
- throw new InvalidMembershipError(idCommitment);
269
+ log.error(`Error getting remaining messages: ${error.message}`);
270
+ return 0; // Fail safe: assume no messages remaining on error
156
271
  }
157
272
  }
158
273
  async getMembershipInfo(idCommitment) {
@@ -170,9 +285,8 @@ class RLNBaseContract {
170
285
  state = MembershipState.Expired;
171
286
  }
172
287
  const index = await this.getMemberIndex(idCommitment);
173
- if (index === null) {
174
- throw new MembershipNotFoundError(idCommitment);
175
- }
288
+ if (!index)
289
+ return undefined;
176
290
  return {
177
291
  index,
178
292
  idCommitment,
@@ -183,51 +297,29 @@ class RLNBaseContract {
183
297
  };
184
298
  }
185
299
  catch (error) {
186
- if (error instanceof RLNContractError) {
187
- throw error;
188
- }
189
- log.error(`Error getting membership info: ${error.message}`);
190
- throw new InvalidMembershipError(idCommitment);
300
+ return undefined;
191
301
  }
192
302
  }
193
303
  async extendMembership(idCommitment) {
194
- const tx = await this.contract.extendMemberships([idCommitment]);
195
- await this.confirmTransaction(tx, "MembershipExtended", (event) => ({
196
- idCommitment: event.args.idCommitment,
197
- endBlock: event.args.endBlock
198
- }));
304
+ return this.contract.extendMemberships([idCommitment]);
199
305
  }
200
306
  async eraseMembership(idCommitment, eraseFromMembershipSet = true) {
201
- const tx = await this.contract.eraseMemberships([idCommitment], eraseFromMembershipSet);
202
- await this.confirmTransaction(tx, "MembershipErased", (event) => ({
203
- idCommitment: event.args.idCommitment,
204
- index: event.args.index
205
- }));
307
+ return this.contract.eraseMemberships([idCommitment], eraseFromMembershipSet);
206
308
  }
207
309
  async registerMembership(idCommitment, rateLimit = DEFAULT_RATE_LIMIT) {
208
310
  if (rateLimit < RATE_LIMIT_PARAMS.MIN_RATE ||
209
311
  rateLimit > RATE_LIMIT_PARAMS.MAX_RATE) {
210
312
  throw new Error(`Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE}`);
211
313
  }
212
- const tx = await this.contract.register(idCommitment, rateLimit, []);
213
- await this.confirmTransaction(tx, "MembershipRegistered", (event) => ({
214
- idCommitment: event.args.idCommitment,
215
- membershipRateLimit: event.args.membershipRateLimit,
216
- index: event.args.index
217
- }));
314
+ return this.contract.register(idCommitment, rateLimit, []);
218
315
  }
219
316
  async withdraw(token, holder) {
220
317
  try {
221
318
  const tx = await this.contract.withdraw(token, { from: holder });
222
- await this.confirmTransaction(tx, "TokenWithdrawn", (event) => ({
223
- token: event.args.token,
224
- holder: event.args.holder,
225
- amount: event.args.amount
226
- }));
319
+ await tx.wait();
227
320
  }
228
321
  catch (error) {
229
322
  log.error(`Error in withdraw: ${error.message}`);
230
- throw error;
231
323
  }
232
324
  }
233
325
  async registerWithIdentity(identity) {
@@ -235,90 +327,108 @@ class RLNBaseContract {
235
327
  log.info(`Registering identity with rate limit: ${this.rateLimit} messages/epoch`);
236
328
  // Check if the ID commitment is already registered
237
329
  const existingIndex = await this.getMemberIndex(identity.IDCommitmentBigInt.toString());
238
- if (existingIndex !== null) {
239
- throw new MembershipExistsError(identity.IDCommitmentBigInt.toString(), existingIndex.toString());
330
+ if (existingIndex) {
331
+ throw new Error(`ID commitment is already registered with index ${existingIndex}`);
240
332
  }
241
333
  // Check if there's enough remaining rate limit
242
334
  const remainingRateLimit = await this.getRemainingTotalRateLimit();
243
335
  if (remainingRateLimit < this.rateLimit) {
244
- throw new RateLimitExceededError(this.rateLimit, remainingRateLimit);
336
+ throw new Error(`Not enough remaining rate limit. Requested: ${this.rateLimit}, Available: ${remainingRateLimit}`);
245
337
  }
246
338
  const estimatedGas = await this.contract.estimateGas.register(identity.IDCommitmentBigInt, this.rateLimit, []);
247
339
  const gasLimit = estimatedGas.add(10000);
248
- const tx = await this.contract.register(identity.IDCommitmentBigInt, this.rateLimit, [], { gasLimit });
249
- const decodedData = await this.confirmTransaction(tx, "MembershipRegistered", (event) => ({
250
- idCommitment: event.args.idCommitment,
251
- membershipRateLimit: event.args.membershipRateLimit,
252
- index: event.args.index
253
- }));
340
+ const txRegisterResponse = await this.contract.register(identity.IDCommitmentBigInt, this.rateLimit, [], { gasLimit });
341
+ const txRegisterReceipt = await txRegisterResponse.wait();
342
+ if (txRegisterReceipt.status === 0) {
343
+ throw new Error("Transaction failed on-chain");
344
+ }
345
+ const memberRegistered = txRegisterReceipt.events?.find((event) => event.event === "MembershipRegistered");
346
+ if (!memberRegistered || !memberRegistered.args) {
347
+ log.error("Failed to register membership: No MembershipRegistered event found");
348
+ return undefined;
349
+ }
350
+ const decodedData = {
351
+ idCommitment: memberRegistered.args.idCommitment,
352
+ membershipRateLimit: memberRegistered.args.membershipRateLimit,
353
+ index: memberRegistered.args.index
354
+ };
254
355
  log.info(`Successfully registered membership with index ${decodedData.index} ` +
255
356
  `and rate limit ${decodedData.membershipRateLimit}`);
256
357
  const network = await this.contract.provider.getNetwork();
257
358
  const address = this.contract.address;
258
- const membershipId = decodedData.index.toString();
359
+ const membershipId = Number(decodedData.index);
259
360
  return {
260
361
  identity,
261
362
  membership: {
262
363
  address,
263
- treeIndex: parseInt(membershipId),
264
- chainId: network.chainId.toString(),
364
+ treeIndex: membershipId,
365
+ chainId: network.chainId,
265
366
  rateLimit: decodedData.membershipRateLimit.toNumber()
266
367
  }
267
368
  };
268
369
  }
269
370
  catch (error) {
270
- if (error instanceof RLNContractError) {
271
- throw error;
272
- }
273
371
  if (error instanceof Error) {
274
372
  const errorMessage = error.message;
275
373
  log.error("registerWithIdentity - error message:", errorMessage);
276
374
  log.error("registerWithIdentity - error stack:", error.stack);
277
- // Map contract errors to our custom errors
375
+ // Try to extract more specific error information
278
376
  if (errorMessage.includes("CannotExceedMaxTotalRateLimit")) {
279
- throw new RateLimitExceededError(this.rateLimit, await this.getRemainingTotalRateLimit());
377
+ throw new Error("Registration failed: Cannot exceed maximum total rate limit");
280
378
  }
281
379
  else if (errorMessage.includes("InvalidIdCommitment")) {
282
- throw new InvalidMembershipError(identity.IDCommitmentBigInt.toString());
380
+ throw new Error("Registration failed: Invalid ID commitment");
283
381
  }
284
382
  else if (errorMessage.includes("InvalidMembershipRateLimit")) {
285
- throw new InvalidRateLimitError(this.rateLimit, RATE_LIMIT_PARAMS.MIN_RATE, RATE_LIMIT_PARAMS.MAX_RATE);
383
+ throw new Error("Registration failed: Invalid membership rate limit");
286
384
  }
287
385
  else if (errorMessage.includes("execution reverted")) {
288
- throw new TransactionError("Contract execution reverted. Check contract requirements.");
386
+ throw new Error("Contract execution reverted. Check contract requirements.");
387
+ }
388
+ else {
389
+ throw new Error(`Error in registerWithIdentity: ${errorMessage}`);
289
390
  }
290
- throw new RLNContractError(`Error in registerWithIdentity: ${errorMessage}`);
291
391
  }
292
- throw new RLNContractError("Unknown error in registerWithIdentity");
392
+ else {
393
+ throw new Error("Unknown error in registerWithIdentity", {
394
+ cause: error
395
+ });
396
+ }
293
397
  }
294
398
  }
295
399
  async registerWithPermitAndErase(identity, permit, idCommitmentsToErase) {
296
400
  try {
297
401
  log.info(`Registering identity with permit and rate limit: ${this.rateLimit} messages/epoch`);
298
- const tx = await this.contract.registerWithPermit(permit.owner, permit.deadline, permit.v, permit.r, permit.s, identity.IDCommitmentBigInt, this.rateLimit, idCommitmentsToErase.map((id) => BigNumber.from(id)));
299
- const decodedData = await this.confirmTransaction(tx, "MembershipRegistered", (event) => ({
300
- idCommitment: event.args.idCommitment,
301
- membershipRateLimit: event.args.membershipRateLimit,
302
- index: event.args.index
303
- }));
402
+ const txRegisterResponse = await this.contract.registerWithPermit(permit.owner, permit.deadline, permit.v, permit.r, permit.s, identity.IDCommitmentBigInt, this.rateLimit, idCommitmentsToErase.map((id) => BigNumber.from(id)));
403
+ const txRegisterReceipt = await txRegisterResponse.wait();
404
+ const memberRegistered = txRegisterReceipt.events?.find((event) => event.event === "MembershipRegistered");
405
+ if (!memberRegistered || !memberRegistered.args) {
406
+ log.error("Failed to register membership with permit: No MembershipRegistered event found");
407
+ return undefined;
408
+ }
409
+ const decodedData = {
410
+ idCommitment: memberRegistered.args.idCommitment,
411
+ membershipRateLimit: memberRegistered.args.membershipRateLimit,
412
+ index: memberRegistered.args.index
413
+ };
304
414
  log.info(`Successfully registered membership with permit. Index: ${decodedData.index}, ` +
305
415
  `Rate limit: ${decodedData.membershipRateLimit}, Erased ${idCommitmentsToErase.length} commitments`);
306
416
  const network = await this.contract.provider.getNetwork();
307
417
  const address = this.contract.address;
308
- const membershipId = decodedData.index.toString();
418
+ const membershipId = Number(decodedData.index);
309
419
  return {
310
420
  identity,
311
421
  membership: {
312
422
  address,
313
- treeIndex: parseInt(membershipId),
314
- chainId: network.chainId.toString(),
423
+ treeIndex: membershipId,
424
+ chainId: network.chainId,
315
425
  rateLimit: decodedData.membershipRateLimit.toNumber()
316
426
  }
317
427
  };
318
428
  }
319
429
  catch (error) {
320
430
  log.error(`Error in registerWithPermitAndErase: ${error.message}`);
321
- throw error;
431
+ return undefined;
322
432
  }
323
433
  }
324
434
  /**
@@ -328,19 +438,39 @@ class RLNBaseContract {
328
438
  validateRateLimit(rateLimit) {
329
439
  if (rateLimit < RATE_LIMIT_PARAMS.MIN_RATE ||
330
440
  rateLimit > RATE_LIMIT_PARAMS.MAX_RATE) {
331
- throw new InvalidRateLimitError(rateLimit, RATE_LIMIT_PARAMS.MIN_RATE, RATE_LIMIT_PARAMS.MAX_RATE);
441
+ throw new Error(`Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE} messages per epoch`);
332
442
  }
333
443
  }
334
- /**
335
- * Helper to confirm a transaction and extract event data
336
- */
337
- async confirmTransaction(tx, expectedEvent, transform) {
338
- const receipt = await tx.wait();
339
- const event = receipt.events?.find((e) => e.event === expectedEvent);
340
- if (!event || !event.args) {
341
- throw new TransactionError(`Expected event ${expectedEvent} not found`);
444
+ get membersFilter() {
445
+ if (!this._membersFilter) {
446
+ throw Error("Members filter was not initialized.");
447
+ }
448
+ return this._membersFilter;
449
+ }
450
+ get membershipErasedFilter() {
451
+ if (!this._membershipErasedFilter) {
452
+ throw Error("MembershipErased filter was not initialized.");
453
+ }
454
+ return this._membershipErasedFilter;
455
+ }
456
+ get membersExpiredFilter() {
457
+ if (!this._membersExpiredFilter) {
458
+ throw Error("MembersExpired filter was not initialized.");
459
+ }
460
+ return this._membersExpiredFilter;
461
+ }
462
+ async getMemberIndex(idCommitment) {
463
+ try {
464
+ const events = await this.contract.queryFilter(this.contract.filters.MembershipRegistered(idCommitment));
465
+ if (events.length === 0)
466
+ return undefined;
467
+ // Get the most recent registration event
468
+ const event = events[events.length - 1];
469
+ return event.args?.index;
470
+ }
471
+ catch (error) {
472
+ return undefined;
342
473
  }
343
- return transform(event);
344
474
  }
345
475
  }
346
476