@waku/rln 0.1.5-053bb95.0 → 0.1.5-197ba46.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 (158) hide show
  1. package/bundle/_virtual/utils.js +2 -2
  2. package/bundle/_virtual/utils2.js +2 -2
  3. package/bundle/index.js +2 -1
  4. package/bundle/packages/rln/dist/contract/constants.js +6 -2
  5. package/bundle/packages/rln/dist/contract/rln_base_contract.js +274 -141
  6. package/bundle/packages/rln/dist/contract/rln_contract.js +74 -89
  7. package/bundle/packages/rln/dist/credentials_manager.js +1 -1
  8. package/bundle/packages/rln/dist/identity.js +0 -8
  9. package/bundle/packages/rln/dist/keystore/keystore.js +28 -15
  10. package/bundle/packages/rln/dist/utils/bytes.js +8 -2
  11. package/bundle/packages/rln/dist/utils/metamask.js +2 -2
  12. package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/node_modules/ethereum-cryptography/random.js +1 -1
  13. package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/node_modules/ethereum-cryptography/utils.js +2 -2
  14. package/bundle/packages/rln/node_modules/@noble/hashes/_sha2.js +1 -1
  15. package/bundle/packages/rln/node_modules/@noble/hashes/hmac.js +1 -1
  16. package/bundle/packages/rln/node_modules/@noble/hashes/pbkdf2.js +1 -1
  17. package/bundle/packages/rln/node_modules/@noble/hashes/scrypt.js +1 -1
  18. package/bundle/packages/rln/node_modules/@noble/hashes/sha256.js +1 -1
  19. package/bundle/packages/rln/node_modules/@noble/hashes/sha512.js +1 -1
  20. package/bundle/packages/rln/node_modules/@noble/hashes/utils.js +1 -1
  21. package/dist/.tsbuildinfo +1 -1
  22. package/dist/contract/constants.d.ts +1 -1
  23. package/dist/contract/constants.js +1 -1
  24. package/dist/contract/constants.js.map +1 -1
  25. package/dist/contract/index.d.ts +1 -0
  26. package/dist/contract/index.js +1 -0
  27. package/dist/contract/index.js.map +1 -1
  28. package/dist/contract/rln_base_contract.d.ts +27 -25
  29. package/dist/contract/rln_base_contract.js +272 -139
  30. package/dist/contract/rln_base_contract.js.map +1 -1
  31. package/dist/contract/rln_contract.d.ts +4 -24
  32. package/dist/contract/rln_contract.js +74 -89
  33. package/dist/contract/rln_contract.js.map +1 -1
  34. package/dist/contract/types.d.ts +5 -0
  35. package/dist/contract/types.js.map +1 -1
  36. package/dist/credentials_manager.js +1 -1
  37. package/dist/credentials_manager.js.map +1 -1
  38. package/dist/identity.d.ts +0 -1
  39. package/dist/identity.js +0 -8
  40. package/dist/identity.js.map +1 -1
  41. package/dist/index.d.ts +2 -1
  42. package/dist/index.js +1 -0
  43. package/dist/index.js.map +1 -1
  44. package/dist/keystore/keystore.d.ts +1 -0
  45. package/dist/keystore/keystore.js +28 -15
  46. package/dist/keystore/keystore.js.map +1 -1
  47. package/dist/keystore/types.d.ts +2 -2
  48. package/dist/utils/bytes.js +8 -2
  49. package/dist/utils/bytes.js.map +1 -1
  50. package/package.json +1 -1
  51. package/src/contract/constants.ts +1 -1
  52. package/src/contract/index.ts +1 -0
  53. package/src/contract/rln_base_contract.ts +419 -211
  54. package/src/contract/rln_contract.ts +95 -120
  55. package/src/contract/types.ts +5 -0
  56. package/src/credentials_manager.ts +1 -1
  57. package/src/identity.ts +0 -9
  58. package/src/index.ts +3 -1
  59. package/src/keystore/keystore.ts +54 -29
  60. package/src/keystore/types.ts +2 -2
  61. package/src/utils/bytes.ts +10 -2
  62. package/bundle/_virtual/__node-resolve_empty.js +0 -6
  63. package/bundle/_virtual/_node-resolve_empty.js +0 -3
  64. package/bundle/_virtual/bn.js +0 -3
  65. package/bundle/_virtual/common.js +0 -3
  66. package/bundle/_virtual/common2.js +0 -3
  67. package/bundle/_virtual/hash.js +0 -3
  68. package/bundle/_virtual/inherits_browser.js +0 -3
  69. package/bundle/_virtual/ripemd.js +0 -3
  70. package/bundle/_virtual/sha.js +0 -3
  71. package/bundle/_virtual/sha3.js +0 -3
  72. package/bundle/_virtual/utils3.js +0 -3
  73. package/bundle/node_modules/@ethersproject/abi/lib.esm/_version.js +0 -3
  74. package/bundle/node_modules/@ethersproject/abi/lib.esm/abi-coder.js +0 -96
  75. package/bundle/node_modules/@ethersproject/abi/lib.esm/coders/abstract-coder.js +0 -148
  76. package/bundle/node_modules/@ethersproject/abi/lib.esm/coders/address.js +0 -26
  77. package/bundle/node_modules/@ethersproject/abi/lib.esm/coders/anonymous.js +0 -20
  78. package/bundle/node_modules/@ethersproject/abi/lib.esm/coders/array.js +0 -210
  79. package/bundle/node_modules/@ethersproject/abi/lib.esm/coders/boolean.js +0 -18
  80. package/bundle/node_modules/@ethersproject/abi/lib.esm/coders/bytes.js +0 -30
  81. package/bundle/node_modules/@ethersproject/abi/lib.esm/coders/fixed-bytes.js +0 -26
  82. package/bundle/node_modules/@ethersproject/abi/lib.esm/coders/null.js +0 -22
  83. package/bundle/node_modules/@ethersproject/abi/lib.esm/coders/number.js +0 -43
  84. package/bundle/node_modules/@ethersproject/abi/lib.esm/coders/string.js +0 -19
  85. package/bundle/node_modules/@ethersproject/abi/lib.esm/coders/tuple.js +0 -58
  86. package/bundle/node_modules/@ethersproject/abi/lib.esm/fragments.js +0 -854
  87. package/bundle/node_modules/@ethersproject/abi/lib.esm/interface.js +0 -609
  88. package/bundle/node_modules/@ethersproject/abstract-provider/lib.esm/_version.js +0 -3
  89. package/bundle/node_modules/@ethersproject/abstract-provider/lib.esm/index.js +0 -66
  90. package/bundle/node_modules/@ethersproject/abstract-signer/lib.esm/_version.js +0 -3
  91. package/bundle/node_modules/@ethersproject/abstract-signer/lib.esm/index.js +0 -302
  92. package/bundle/node_modules/@ethersproject/address/lib.esm/_version.js +0 -3
  93. package/bundle/node_modules/@ethersproject/address/lib.esm/index.js +0 -110
  94. package/bundle/node_modules/@ethersproject/base64/lib.esm/base64.js +0 -20
  95. package/bundle/node_modules/@ethersproject/basex/lib.esm/index.js +0 -120
  96. package/bundle/node_modules/@ethersproject/bignumber/lib.esm/_version.js +0 -3
  97. package/bundle/node_modules/@ethersproject/bignumber/lib.esm/bignumber.js +0 -287
  98. package/bundle/node_modules/@ethersproject/bytes/lib.esm/_version.js +0 -3
  99. package/bundle/node_modules/@ethersproject/bytes/lib.esm/index.js +0 -402
  100. package/bundle/node_modules/@ethersproject/constants/lib.esm/addresses.js +0 -3
  101. package/bundle/node_modules/@ethersproject/constants/lib.esm/bignumbers.js +0 -8
  102. package/bundle/node_modules/@ethersproject/constants/lib.esm/hashes.js +0 -3
  103. package/bundle/node_modules/@ethersproject/contracts/lib.esm/_version.js +0 -3
  104. package/bundle/node_modules/@ethersproject/contracts/lib.esm/index.js +0 -893
  105. package/bundle/node_modules/@ethersproject/hash/lib.esm/_version.js +0 -3
  106. package/bundle/node_modules/@ethersproject/hash/lib.esm/ens-normalize/decoder.js +0 -256
  107. package/bundle/node_modules/@ethersproject/hash/lib.esm/ens-normalize/include.js +0 -36
  108. package/bundle/node_modules/@ethersproject/hash/lib.esm/ens-normalize/lib.js +0 -135
  109. package/bundle/node_modules/@ethersproject/hash/lib.esm/id.js +0 -8
  110. package/bundle/node_modules/@ethersproject/hash/lib.esm/namehash.js +0 -64
  111. package/bundle/node_modules/@ethersproject/hash/lib.esm/typed-data.js +0 -443
  112. package/bundle/node_modules/@ethersproject/keccak256/lib.esm/index.js +0 -8
  113. package/bundle/node_modules/@ethersproject/keccak256/node_modules/js-sha3/src/sha3.js +0 -660
  114. package/bundle/node_modules/@ethersproject/logger/lib.esm/_version.js +0 -3
  115. package/bundle/node_modules/@ethersproject/logger/lib.esm/index.js +0 -352
  116. package/bundle/node_modules/@ethersproject/networks/lib.esm/_version.js +0 -3
  117. package/bundle/node_modules/@ethersproject/networks/lib.esm/index.js +0 -248
  118. package/bundle/node_modules/@ethersproject/properties/lib.esm/_version.js +0 -3
  119. package/bundle/node_modules/@ethersproject/properties/lib.esm/index.js +0 -127
  120. package/bundle/node_modules/@ethersproject/providers/lib.esm/_version.js +0 -3
  121. package/bundle/node_modules/@ethersproject/providers/lib.esm/base-provider.js +0 -2007
  122. package/bundle/node_modules/@ethersproject/providers/lib.esm/formatter.js +0 -422
  123. package/bundle/node_modules/@ethersproject/providers/lib.esm/json-rpc-provider.js +0 -674
  124. package/bundle/node_modules/@ethersproject/providers/lib.esm/web3-provider.js +0 -132
  125. package/bundle/node_modules/@ethersproject/rlp/lib.esm/_version.js +0 -3
  126. package/bundle/node_modules/@ethersproject/rlp/lib.esm/index.js +0 -120
  127. package/bundle/node_modules/@ethersproject/sha2/lib.esm/sha2.js +0 -8
  128. package/bundle/node_modules/@ethersproject/signing-key/lib.esm/_version.js +0 -3
  129. package/bundle/node_modules/@ethersproject/signing-key/lib.esm/elliptic.js +0 -2430
  130. package/bundle/node_modules/@ethersproject/signing-key/lib.esm/index.js +0 -76
  131. package/bundle/node_modules/@ethersproject/strings/lib.esm/_version.js +0 -3
  132. package/bundle/node_modules/@ethersproject/strings/lib.esm/utf8.js +0 -219
  133. package/bundle/node_modules/@ethersproject/transactions/lib.esm/_version.js +0 -3
  134. package/bundle/node_modules/@ethersproject/transactions/lib.esm/index.js +0 -279
  135. package/bundle/node_modules/@ethersproject/web/lib.esm/_version.js +0 -3
  136. package/bundle/node_modules/@ethersproject/web/lib.esm/geturl.js +0 -69
  137. package/bundle/node_modules/@ethersproject/web/lib.esm/index.js +0 -404
  138. package/bundle/node_modules/bech32/index.js +0 -187
  139. package/bundle/node_modules/bn.js/lib/bn.js +0 -3361
  140. package/bundle/node_modules/hash.js/lib/hash/common.js +0 -97
  141. package/bundle/node_modules/hash.js/lib/hash/hmac.js +0 -51
  142. package/bundle/node_modules/hash.js/lib/hash/ripemd.js +0 -152
  143. package/bundle/node_modules/hash.js/lib/hash/sha/1.js +0 -81
  144. package/bundle/node_modules/hash.js/lib/hash/sha/224.js +0 -33
  145. package/bundle/node_modules/hash.js/lib/hash/sha/256.js +0 -113
  146. package/bundle/node_modules/hash.js/lib/hash/sha/384.js +0 -39
  147. package/bundle/node_modules/hash.js/lib/hash/sha/512.js +0 -336
  148. package/bundle/node_modules/hash.js/lib/hash/sha/common.js +0 -53
  149. package/bundle/node_modules/hash.js/lib/hash/sha.js +0 -14
  150. package/bundle/node_modules/hash.js/lib/hash/utils.js +0 -282
  151. package/bundle/node_modules/hash.js/lib/hash.js +0 -33
  152. package/bundle/node_modules/inherits/inherits_browser.js +0 -33
  153. package/bundle/node_modules/minimalistic-assert/index.js +0 -13
  154. package/bundle/packages/rln/dist/contract/errors.js +0 -62
  155. package/dist/contract/errors.d.ts +0 -30
  156. package/dist/contract/errors.js +0 -61
  157. package/dist/contract/errors.js.map +0 -1
  158. package/src/contract/errors.ts +0 -75
@@ -7,15 +7,8 @@ import { DecryptedCredentials } from "../keystore/types.js";
7
7
  import { RLN_ABI } from "./abi.js";
8
8
  import { DEFAULT_RATE_LIMIT, RATE_LIMIT_PARAMS } from "./constants.js";
9
9
  import {
10
- InvalidMembershipError,
11
- InvalidRateLimitError,
12
- MembershipExistsError,
13
- MembershipNotFoundError,
14
- RateLimitExceededError,
15
- RLNContractError,
16
- TransactionError
17
- } from "./errors.js";
18
- import {
10
+ CustomQueryOptions,
11
+ FetchMembersOptions,
19
12
  Member,
20
13
  MembershipInfo,
21
14
  MembershipRegisteredEvent,
@@ -27,8 +20,18 @@ const log = new Logger("waku:rln:contract:base");
27
20
 
28
21
  export class RLNBaseContract {
29
22
  public contract: ethers.Contract;
23
+ private deployBlock: undefined | number;
30
24
  private rateLimit: number;
31
25
 
26
+ protected _members: Map<number, Member> = new Map();
27
+ private _membersFilter: ethers.EventFilter;
28
+ private _membershipErasedFilter: ethers.EventFilter;
29
+ private _membersExpiredFilter: ethers.EventFilter;
30
+
31
+ /**
32
+ * Constructor for RLNBaseContract.
33
+ * Allows injecting a mocked contract for testing purposes.
34
+ */
32
35
  public constructor(options: RLNContractInitOptions) {
33
36
  const {
34
37
  address,
@@ -37,9 +40,38 @@ export class RLNBaseContract {
37
40
  contract
38
41
  } = options;
39
42
 
40
- this.validateRateLimit(rateLimit);
43
+ log.info("Initializing RLNBaseContract", { address, rateLimit });
44
+
41
45
  this.contract = contract || new ethers.Contract(address, RLN_ABI, signer);
42
46
  this.rateLimit = rateLimit;
47
+
48
+ try {
49
+ log.info("Setting up event filters");
50
+ // Initialize event filters
51
+ this._membersFilter = this.contract.filters.MembershipRegistered();
52
+ this._membershipErasedFilter = this.contract.filters.MembershipErased();
53
+ this._membersExpiredFilter = this.contract.filters.MembershipExpired();
54
+ log.info("Event filters initialized successfully");
55
+ } catch (error) {
56
+ log.error("Failed to initialize event filters", { error });
57
+ throw new Error(
58
+ "Failed to initialize event filters: " + (error as Error).message
59
+ );
60
+ }
61
+
62
+ // Initialize members and subscriptions
63
+ this.fetchMembers()
64
+ .then(() => {
65
+ this.subscribeToMembers();
66
+ })
67
+ .catch((error) => {
68
+ log.error("Failed to initialize members", { error });
69
+ });
70
+
71
+ // Validate rate limit asynchronously
72
+ this.validateRateLimit(rateLimit).catch((error) => {
73
+ log.error("Failed to validate initial rate limit", { error });
74
+ });
43
75
  }
44
76
 
45
77
  /**
@@ -116,151 +148,302 @@ export class RLNBaseContract {
116
148
  * @param newRateLimit The new rate limit to use
117
149
  */
118
150
  public async setRateLimit(newRateLimit: number): Promise<void> {
119
- this.validateRateLimit(newRateLimit);
151
+ await this.validateRateLimit(newRateLimit);
120
152
  this.rateLimit = newRateLimit;
121
153
  }
122
154
 
123
- /**
124
- * Gets all members in the given range
125
- * @param startIndex Start index (inclusive)
126
- * @param endIndex End index (exclusive)
127
- */
128
- public async getMembersInRange(
129
- startIndex: number,
130
- endIndex: number
131
- ): Promise<Member[]> {
132
- try {
133
- // Get all commitments in one call
134
- const idCommitments =
135
- await this.contract.getRateCommitmentsInRangeBoundsInclusive(
136
- startIndex,
137
- endIndex - 1 // -1 because getRateCommitmentsInRangeBoundsInclusive is inclusive
138
- );
155
+ public get members(): Member[] {
156
+ const sortedMembers = Array.from(this._members.values()).sort(
157
+ (left, right) => left.index.toNumber() - right.index.toNumber()
158
+ );
159
+ return sortedMembers;
160
+ }
139
161
 
140
- // Get membership info for each commitment in a single batch
141
- const membershipPromises = idCommitments.map(
142
- (idCommitment: ethers.BigNumber) =>
143
- this.contract
144
- .memberships(idCommitment)
145
- .then((info: { index: number | ethers.BigNumber }) => ({
146
- idCommitment: idCommitment.toString(),
147
- index: ethers.BigNumber.from(info.index)
148
- }))
149
- .catch(() => null) // Skip invalid members
150
- );
162
+ public async fetchMembers(options: FetchMembersOptions = {}): Promise<void> {
163
+ const registeredMemberEvents = await RLNBaseContract.queryFilter(
164
+ this.contract,
165
+ {
166
+ fromBlock: this.deployBlock,
167
+ ...options,
168
+ membersFilter: this.membersFilter
169
+ }
170
+ );
171
+ const removedMemberEvents = await RLNBaseContract.queryFilter(
172
+ this.contract,
173
+ {
174
+ fromBlock: this.deployBlock,
175
+ ...options,
176
+ membersFilter: this.membershipErasedFilter
177
+ }
178
+ );
179
+ const expiredMemberEvents = await RLNBaseContract.queryFilter(
180
+ this.contract,
181
+ {
182
+ fromBlock: this.deployBlock,
183
+ ...options,
184
+ membersFilter: this.membersExpiredFilter
185
+ }
186
+ );
187
+
188
+ const events = [
189
+ ...registeredMemberEvents,
190
+ ...removedMemberEvents,
191
+ ...expiredMemberEvents
192
+ ];
193
+ this.processEvents(events);
194
+ }
195
+
196
+ public static async queryFilter(
197
+ contract: ethers.Contract,
198
+ options: CustomQueryOptions
199
+ ): Promise<ethers.Event[]> {
200
+ const FETCH_CHUNK = 5;
201
+ const BLOCK_RANGE = 3000;
202
+
203
+ const {
204
+ fromBlock,
205
+ membersFilter,
206
+ fetchRange = BLOCK_RANGE,
207
+ fetchChunks = FETCH_CHUNK
208
+ } = options;
209
+
210
+ if (fromBlock === undefined) {
211
+ return contract.queryFilter(membersFilter);
212
+ }
213
+
214
+ if (!contract.provider) {
215
+ throw Error("No provider found on the contract.");
216
+ }
217
+
218
+ const toBlock = await contract.provider.getBlockNumber();
219
+
220
+ if (toBlock - fromBlock < fetchRange) {
221
+ return contract.queryFilter(membersFilter, fromBlock, toBlock);
222
+ }
223
+
224
+ const events: ethers.Event[][] = [];
225
+ const chunks = RLNBaseContract.splitToChunks(
226
+ fromBlock,
227
+ toBlock,
228
+ fetchRange
229
+ );
151
230
 
152
- // Wait for all promises to resolve
153
- const members = (await Promise.all(membershipPromises)).filter(
154
- (m): m is Member => m !== null
231
+ for (const portion of RLNBaseContract.takeN<[number, number]>(
232
+ chunks,
233
+ fetchChunks
234
+ )) {
235
+ const promises = portion.map(([left, right]) =>
236
+ RLNBaseContract.ignoreErrors(
237
+ contract.queryFilter(membersFilter, left, right),
238
+ []
239
+ )
155
240
  );
156
- return members;
157
- } catch (error) {
241
+ const fetchedEvents = await Promise.all(promises);
242
+ events.push(fetchedEvents.flatMap((v) => v));
243
+ }
244
+
245
+ return events.flatMap((v) => v);
246
+ }
247
+
248
+ public processEvents(events: ethers.Event[]): void {
249
+ const toRemoveTable = new Map<number, number[]>();
250
+ const toInsertTable = new Map<number, ethers.Event[]>();
251
+
252
+ events.forEach((evt) => {
253
+ if (!evt.args) {
254
+ return;
255
+ }
256
+
158
257
  if (
159
- error instanceof Error &&
160
- error.message.includes("InvalidPaginationQuery")
258
+ evt.event === "MembershipErased" ||
259
+ evt.event === "MembershipExpired"
161
260
  ) {
162
- throw new RLNContractError(
163
- `Invalid pagination range: start=${startIndex}, end=${endIndex}`
164
- );
261
+ let index = evt.args.index;
262
+
263
+ if (!index) {
264
+ return;
265
+ }
266
+
267
+ if (typeof index === "number" || typeof index === "string") {
268
+ index = ethers.BigNumber.from(index);
269
+ }
270
+
271
+ const toRemoveVal = toRemoveTable.get(evt.blockNumber);
272
+ if (toRemoveVal != undefined) {
273
+ toRemoveVal.push(index.toNumber());
274
+ toRemoveTable.set(evt.blockNumber, toRemoveVal);
275
+ } else {
276
+ toRemoveTable.set(evt.blockNumber, [index.toNumber()]);
277
+ }
278
+ } else if (evt.event === "MembershipRegistered") {
279
+ let eventsPerBlock = toInsertTable.get(evt.blockNumber);
280
+ if (eventsPerBlock == undefined) {
281
+ eventsPerBlock = [];
282
+ }
283
+
284
+ eventsPerBlock.push(evt);
285
+ toInsertTable.set(evt.blockNumber, eventsPerBlock);
165
286
  }
166
- throw error;
287
+ });
288
+ }
289
+
290
+ public static splitToChunks(
291
+ from: number,
292
+ to: number,
293
+ step: number
294
+ ): Array<[number, number]> {
295
+ const chunks: Array<[number, number]> = [];
296
+
297
+ let left = from;
298
+ while (left < to) {
299
+ const right = left + step < to ? left + step : to;
300
+
301
+ chunks.push([left, right] as [number, number]);
302
+
303
+ left = right;
167
304
  }
305
+
306
+ return chunks;
168
307
  }
169
308
 
170
- /**
171
- * Gets all current members
172
- */
173
- public async getAllMembers(): Promise<Member[]> {
174
- const nextIndex = (await this.contract.nextFreeIndex()).toNumber();
175
- return this.getMembersInRange(0, nextIndex);
309
+ public static *takeN<T>(array: T[], size: number): Iterable<T[]> {
310
+ let start = 0;
311
+
312
+ while (start < array.length) {
313
+ const portion = array.slice(start, start + size);
314
+
315
+ yield portion;
316
+
317
+ start += size;
318
+ }
176
319
  }
177
320
 
178
- /**
179
- * Gets the member index if it exists, or null if it doesn't
180
- * Throws only on actual errors (invalid input, network issues, etc)
181
- */
182
- private async getMemberIndex(
183
- idCommitment: string
184
- ): Promise<ethers.BigNumber | null> {
321
+ public static async ignoreErrors<T>(
322
+ promise: Promise<T>,
323
+ defaultValue: T
324
+ ): Promise<T> {
185
325
  try {
186
- const isValid = await this.contract.isInMembershipSet(idCommitment);
187
- if (!isValid) {
188
- return null;
326
+ return await promise;
327
+ } catch (err: unknown) {
328
+ if (err instanceof Error) {
329
+ log.info(`Ignoring an error during query: ${err.message}`);
330
+ } else {
331
+ log.info(`Ignoring an unknown error during query`);
189
332
  }
190
-
191
- const membershipInfo = await this.contract.memberships(idCommitment);
192
- return ethers.BigNumber.from(membershipInfo.index);
193
- } catch (error) {
194
- log.error(`Error getting member index: ${(error as Error).message}`);
195
- throw new InvalidMembershipError(idCommitment);
333
+ return defaultValue;
196
334
  }
197
335
  }
198
336
 
337
+ public subscribeToMembers(): void {
338
+ this.contract.on(
339
+ this.membersFilter,
340
+ (
341
+ _idCommitment: bigint,
342
+ _membershipRateLimit: ethers.BigNumber,
343
+ _index: ethers.BigNumber,
344
+ event: ethers.Event
345
+ ) => {
346
+ this.processEvents([event]);
347
+ }
348
+ );
349
+
350
+ this.contract.on(
351
+ this.membershipErasedFilter,
352
+ (
353
+ _idCommitment: bigint,
354
+ _membershipRateLimit: ethers.BigNumber,
355
+ _index: ethers.BigNumber,
356
+ event: ethers.Event
357
+ ) => {
358
+ this.processEvents([event]);
359
+ }
360
+ );
361
+
362
+ this.contract.on(
363
+ this.membersExpiredFilter,
364
+ (
365
+ _idCommitment: bigint,
366
+ _membershipRateLimit: ethers.BigNumber,
367
+ _index: ethers.BigNumber,
368
+ event: ethers.Event
369
+ ) => {
370
+ this.processEvents([event]);
371
+ }
372
+ );
373
+ }
374
+
199
375
  public async getMembershipInfo(
200
- idCommitment: string
201
- ): Promise<MembershipInfo> {
376
+ idCommitmentBigInt: bigint
377
+ ): Promise<MembershipInfo | undefined> {
202
378
  try {
203
- const [startBlock, endBlock, rateLimit] =
204
- await this.contract.getMembershipInfo(idCommitment);
379
+ const membershipData =
380
+ await this.contract.memberships(idCommitmentBigInt);
205
381
  const currentBlock = await this.contract.provider.getBlockNumber();
382
+ const [
383
+ depositAmount,
384
+ activeDuration,
385
+ gracePeriodStartTimestamp,
386
+ gracePeriodDuration,
387
+ rateLimit,
388
+ index,
389
+ holder,
390
+ token
391
+ ] = membershipData;
392
+
393
+ const gracePeriodEnd = gracePeriodStartTimestamp.add(gracePeriodDuration);
206
394
 
207
395
  let state: MembershipState;
208
- if (currentBlock < startBlock) {
396
+ if (currentBlock < gracePeriodStartTimestamp.toNumber()) {
209
397
  state = MembershipState.Active;
210
- } else if (currentBlock < endBlock) {
398
+ } else if (currentBlock < gracePeriodEnd.toNumber()) {
211
399
  state = MembershipState.GracePeriod;
212
400
  } else {
213
401
  state = MembershipState.Expired;
214
402
  }
215
403
 
216
- const index = await this.getMemberIndex(idCommitment);
217
- if (index === null) {
218
- throw new MembershipNotFoundError(idCommitment);
219
- }
220
-
221
404
  return {
222
405
  index,
223
- idCommitment,
224
- rateLimit: rateLimit.toNumber(),
225
- startBlock: startBlock.toNumber(),
226
- endBlock: endBlock.toNumber(),
227
- state
406
+ idCommitment: idCommitmentBigInt.toString(),
407
+ rateLimit: Number(rateLimit),
408
+ startBlock: gracePeriodStartTimestamp.toNumber(),
409
+ endBlock: gracePeriodEnd.toNumber(),
410
+ state,
411
+ depositAmount,
412
+ activeDuration,
413
+ gracePeriodDuration,
414
+ holder,
415
+ token
228
416
  };
229
417
  } catch (error) {
230
- if (error instanceof RLNContractError) {
231
- throw error;
232
- }
233
- log.error(`Error getting membership info: ${(error as Error).message}`);
234
- throw new InvalidMembershipError(idCommitment);
418
+ log.error("Error in getMembershipInfo:", error);
419
+ return undefined;
235
420
  }
236
421
  }
237
422
 
238
- public async extendMembership(idCommitment: string): Promise<void> {
239
- const tx = await this.contract.extendMemberships([idCommitment]);
240
- await this.confirmTransaction(tx, "MembershipExtended", (event) => ({
241
- idCommitment: event.args!.idCommitment,
242
- endBlock: event.args!.endBlock
243
- }));
423
+ public async extendMembership(
424
+ idCommitmentBigInt: bigint
425
+ ): Promise<ethers.ContractTransaction> {
426
+ const tx = await this.contract.extendMemberships([idCommitmentBigInt]);
427
+ await tx.wait();
428
+ return tx;
244
429
  }
245
430
 
246
431
  public async eraseMembership(
247
- idCommitment: string,
432
+ idCommitmentBigInt: bigint,
248
433
  eraseFromMembershipSet: boolean = true
249
- ): Promise<void> {
250
- const tx = await this.contract.eraseMemberships(
251
- [idCommitment],
434
+ ): Promise<ethers.ContractTransaction> {
435
+ const tx = await this.contract.functions.eraseMemberships(
436
+ [idCommitmentBigInt],
252
437
  eraseFromMembershipSet
253
438
  );
254
- await this.confirmTransaction(tx, "MembershipErased", (event) => ({
255
- idCommitment: event.args!.idCommitment,
256
- index: event.args!.index
257
- }));
439
+ await tx.wait();
440
+ return tx;
258
441
  }
259
442
 
260
443
  public async registerMembership(
261
- idCommitment: string,
444
+ idCommitmentBigInt: bigint,
262
445
  rateLimit: number = DEFAULT_RATE_LIMIT
263
- ): Promise<void> {
446
+ ): Promise<ethers.ContractTransaction> {
264
447
  if (
265
448
  rateLimit < RATE_LIMIT_PARAMS.MIN_RATE ||
266
449
  rateLimit > RATE_LIMIT_PARAMS.MAX_RATE
@@ -269,25 +452,15 @@ export class RLNBaseContract {
269
452
  `Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE}`
270
453
  );
271
454
  }
272
- const tx = await this.contract.register(idCommitment, rateLimit, []);
273
- await this.confirmTransaction(tx, "MembershipRegistered", (event) => ({
274
- idCommitment: event.args!.idCommitment,
275
- membershipRateLimit: event.args!.membershipRateLimit,
276
- index: event.args!.index
277
- }));
455
+ return this.contract.register(idCommitmentBigInt, rateLimit, []);
278
456
  }
279
457
 
280
- public async withdraw(token: string, holder: string): Promise<void> {
458
+ public async withdraw(token: string, from: string): Promise<void> {
281
459
  try {
282
- const tx = await this.contract.withdraw(token, { from: holder });
283
- await this.confirmTransaction(tx, "TokenWithdrawn", (event) => ({
284
- token: event.args!.token,
285
- holder: event.args!.holder,
286
- amount: event.args!.amount
287
- }));
460
+ const tx = await this.contract.withdraw(token, from);
461
+ await tx.wait();
288
462
  } catch (error) {
289
463
  log.error(`Error in withdraw: ${(error as Error).message}`);
290
- throw error;
291
464
  }
292
465
  }
293
466
 
@@ -301,20 +474,20 @@ export class RLNBaseContract {
301
474
 
302
475
  // Check if the ID commitment is already registered
303
476
  const existingIndex = await this.getMemberIndex(
304
- identity.IDCommitmentBigInt.toString()
477
+ identity.IDCommitmentBigInt
305
478
  );
306
-
307
- if (existingIndex !== null) {
308
- throw new MembershipExistsError(
309
- identity.IDCommitmentBigInt.toString(),
310
- existingIndex.toString()
479
+ if (existingIndex) {
480
+ throw new Error(
481
+ `ID commitment is already registered with index ${existingIndex}`
311
482
  );
312
483
  }
313
484
 
314
485
  // Check if there's enough remaining rate limit
315
486
  const remainingRateLimit = await this.getRemainingTotalRateLimit();
316
487
  if (remainingRateLimit < this.rateLimit) {
317
- throw new RateLimitExceededError(this.rateLimit, remainingRateLimit);
488
+ throw new Error(
489
+ `Not enough remaining rate limit. Requested: ${this.rateLimit}, Available: ${remainingRateLimit}`
490
+ );
318
491
  }
319
492
 
320
493
  const estimatedGas = await this.contract.estimateGas.register(
@@ -324,23 +497,37 @@ export class RLNBaseContract {
324
497
  );
325
498
  const gasLimit = estimatedGas.add(10000);
326
499
 
327
- const tx = await this.contract.register(
328
- identity.IDCommitmentBigInt,
329
- this.rateLimit,
330
- [],
331
- { gasLimit }
332
- );
500
+ const txRegisterResponse: ethers.ContractTransaction =
501
+ await this.contract.register(
502
+ identity.IDCommitmentBigInt,
503
+ this.rateLimit,
504
+ [],
505
+ { gasLimit }
506
+ );
333
507
 
334
- const decodedData = await this.confirmTransaction(
335
- tx,
336
- "MembershipRegistered",
337
- (event): MembershipRegisteredEvent => ({
338
- idCommitment: event.args!.idCommitment,
339
- membershipRateLimit: event.args!.membershipRateLimit,
340
- index: event.args!.index
341
- })
508
+ const txRegisterReceipt = await txRegisterResponse.wait();
509
+
510
+ if (txRegisterReceipt.status === 0) {
511
+ throw new Error("Transaction failed on-chain");
512
+ }
513
+
514
+ const memberRegistered = txRegisterReceipt.events?.find(
515
+ (event: ethers.Event) => event.event === "MembershipRegistered"
342
516
  );
343
517
 
518
+ if (!memberRegistered || !memberRegistered.args) {
519
+ log.error(
520
+ "Failed to register membership: No MembershipRegistered event found"
521
+ );
522
+ return undefined;
523
+ }
524
+
525
+ const decodedData: MembershipRegisteredEvent = {
526
+ idCommitment: memberRegistered.args.idCommitment,
527
+ membershipRateLimit: memberRegistered.args.membershipRateLimit,
528
+ index: memberRegistered.args.index
529
+ };
530
+
344
531
  log.info(
345
532
  `Successfully registered membership with index ${decodedData.index} ` +
346
533
  `and rate limit ${decodedData.membershipRateLimit}`
@@ -348,55 +535,44 @@ export class RLNBaseContract {
348
535
 
349
536
  const network = await this.contract.provider.getNetwork();
350
537
  const address = this.contract.address;
351
- const membershipId = decodedData.index.toString();
538
+ const membershipId = Number(decodedData.index);
352
539
 
353
540
  return {
354
541
  identity,
355
542
  membership: {
356
543
  address,
357
- treeIndex: parseInt(membershipId),
544
+ treeIndex: membershipId,
358
545
  chainId: network.chainId.toString(),
359
546
  rateLimit: decodedData.membershipRateLimit.toNumber()
360
547
  }
361
548
  };
362
549
  } catch (error) {
363
- if (error instanceof RLNContractError) {
364
- throw error;
365
- }
366
-
367
550
  if (error instanceof Error) {
368
551
  const errorMessage = error.message;
369
552
  log.error("registerWithIdentity - error message:", errorMessage);
370
553
  log.error("registerWithIdentity - error stack:", error.stack);
371
554
 
372
- // Map contract errors to our custom errors
555
+ // Try to extract more specific error information
373
556
  if (errorMessage.includes("CannotExceedMaxTotalRateLimit")) {
374
- throw new RateLimitExceededError(
375
- this.rateLimit,
376
- await this.getRemainingTotalRateLimit()
557
+ throw new Error(
558
+ "Registration failed: Cannot exceed maximum total rate limit"
377
559
  );
378
560
  } else if (errorMessage.includes("InvalidIdCommitment")) {
379
- throw new InvalidMembershipError(
380
- identity.IDCommitmentBigInt.toString()
381
- );
561
+ throw new Error("Registration failed: Invalid ID commitment");
382
562
  } else if (errorMessage.includes("InvalidMembershipRateLimit")) {
383
- throw new InvalidRateLimitError(
384
- this.rateLimit,
385
- RATE_LIMIT_PARAMS.MIN_RATE,
386
- RATE_LIMIT_PARAMS.MAX_RATE
387
- );
563
+ throw new Error("Registration failed: Invalid membership rate limit");
388
564
  } else if (errorMessage.includes("execution reverted")) {
389
- throw new TransactionError(
565
+ throw new Error(
390
566
  "Contract execution reverted. Check contract requirements."
391
567
  );
568
+ } else {
569
+ throw new Error(`Error in registerWithIdentity: ${errorMessage}`);
392
570
  }
393
-
394
- throw new RLNContractError(
395
- `Error in registerWithIdentity: ${errorMessage}`
396
- );
571
+ } else {
572
+ throw new Error("Unknown error in registerWithIdentity", {
573
+ cause: error
574
+ });
397
575
  }
398
-
399
- throw new RLNContractError("Unknown error in registerWithIdentity");
400
576
  }
401
577
  }
402
578
 
@@ -416,27 +592,36 @@ export class RLNBaseContract {
416
592
  `Registering identity with permit and rate limit: ${this.rateLimit} messages/epoch`
417
593
  );
418
594
 
419
- const tx = await this.contract.registerWithPermit(
420
- permit.owner,
421
- permit.deadline,
422
- permit.v,
423
- permit.r,
424
- permit.s,
425
- identity.IDCommitmentBigInt,
426
- this.rateLimit,
427
- idCommitmentsToErase.map((id) => ethers.BigNumber.from(id))
428
- );
595
+ const txRegisterResponse: ethers.ContractTransaction =
596
+ await this.contract.registerWithPermit(
597
+ permit.owner,
598
+ permit.deadline,
599
+ permit.v,
600
+ permit.r,
601
+ permit.s,
602
+ identity.IDCommitmentBigInt,
603
+ this.rateLimit,
604
+ idCommitmentsToErase.map((id) => ethers.BigNumber.from(id))
605
+ );
606
+ const txRegisterReceipt = await txRegisterResponse.wait();
429
607
 
430
- const decodedData = await this.confirmTransaction(
431
- tx,
432
- "MembershipRegistered",
433
- (event): MembershipRegisteredEvent => ({
434
- idCommitment: event.args!.idCommitment,
435
- membershipRateLimit: event.args!.membershipRateLimit,
436
- index: event.args!.index
437
- })
608
+ const memberRegistered = txRegisterReceipt.events?.find(
609
+ (event: ethers.Event) => event.event === "MembershipRegistered"
438
610
  );
439
611
 
612
+ if (!memberRegistered || !memberRegistered.args) {
613
+ log.error(
614
+ "Failed to register membership with permit: No MembershipRegistered event found"
615
+ );
616
+ return undefined;
617
+ }
618
+
619
+ const decodedData: MembershipRegisteredEvent = {
620
+ idCommitment: memberRegistered.args.idCommitment,
621
+ membershipRateLimit: memberRegistered.args.membershipRateLimit,
622
+ index: memberRegistered.args.index
623
+ };
624
+
440
625
  log.info(
441
626
  `Successfully registered membership with permit. Index: ${decodedData.index}, ` +
442
627
  `Rate limit: ${decodedData.membershipRateLimit}, Erased ${idCommitmentsToErase.length} commitments`
@@ -444,13 +629,13 @@ export class RLNBaseContract {
444
629
 
445
630
  const network = await this.contract.provider.getNetwork();
446
631
  const address = this.contract.address;
447
- const membershipId = decodedData.index.toString();
632
+ const membershipId = Number(decodedData.index);
448
633
 
449
634
  return {
450
635
  identity,
451
636
  membership: {
452
637
  address,
453
- treeIndex: parseInt(membershipId),
638
+ treeIndex: membershipId,
454
639
  chainId: network.chainId.toString(),
455
640
  rateLimit: decodedData.membershipRateLimit.toNumber()
456
641
  }
@@ -459,7 +644,7 @@ export class RLNBaseContract {
459
644
  log.error(
460
645
  `Error in registerWithPermitAndErase: ${(error as Error).message}`
461
646
  );
462
- throw error;
647
+ return undefined;
463
648
  }
464
649
  }
465
650
 
@@ -467,34 +652,57 @@ export class RLNBaseContract {
467
652
  * Validates that the rate limit is within the allowed range
468
653
  * @throws Error if the rate limit is outside the allowed range
469
654
  */
470
- private validateRateLimit(rateLimit: number): void {
471
- if (
472
- rateLimit < RATE_LIMIT_PARAMS.MIN_RATE ||
473
- rateLimit > RATE_LIMIT_PARAMS.MAX_RATE
474
- ) {
475
- throw new InvalidRateLimitError(
476
- rateLimit,
477
- RATE_LIMIT_PARAMS.MIN_RATE,
478
- RATE_LIMIT_PARAMS.MAX_RATE
655
+ private async validateRateLimit(rateLimit: number): Promise<void> {
656
+ const [minRate, maxRate] = await Promise.all([
657
+ this.contract.minMembershipRateLimit(),
658
+ this.contract.maxMembershipRateLimit()
659
+ ]);
660
+
661
+ const minRateNum = ethers.BigNumber.from(minRate).toNumber();
662
+ const maxRateNum = ethers.BigNumber.from(maxRate).toNumber();
663
+
664
+ if (rateLimit < minRateNum || rateLimit > maxRateNum) {
665
+ throw new Error(
666
+ `Rate limit must be between ${minRateNum} and ${maxRateNum} messages per epoch`
479
667
  );
480
668
  }
481
669
  }
482
670
 
483
- /**
484
- * Helper to confirm a transaction and extract event data
485
- */
486
- private async confirmTransaction<T>(
487
- tx: ethers.ContractTransaction,
488
- expectedEvent: string,
489
- transform: (event: ethers.Event) => T
490
- ): Promise<T> {
491
- const receipt = await tx.wait();
492
- const event = receipt.events?.find((e) => e.event === expectedEvent);
671
+ private get membersFilter(): ethers.EventFilter {
672
+ if (!this._membersFilter) {
673
+ throw Error("Members filter was not initialized.");
674
+ }
675
+ return this._membersFilter;
676
+ }
677
+
678
+ private get membershipErasedFilter(): ethers.EventFilter {
679
+ if (!this._membershipErasedFilter) {
680
+ throw Error("MembershipErased filter was not initialized.");
681
+ }
682
+ return this._membershipErasedFilter;
683
+ }
493
684
 
494
- if (!event || !event.args) {
495
- throw new TransactionError(`Expected event ${expectedEvent} not found`);
685
+ private get membersExpiredFilter(): ethers.EventFilter {
686
+ if (!this._membersExpiredFilter) {
687
+ throw Error("MembersExpired filter was not initialized.");
496
688
  }
689
+ return this._membersExpiredFilter;
690
+ }
497
691
 
498
- return transform(event);
692
+ private async getMemberIndex(
693
+ idCommitmentBigInt: bigint
694
+ ): Promise<ethers.BigNumber | undefined> {
695
+ try {
696
+ const events = await this.contract.queryFilter(
697
+ this.contract.filters.MembershipRegistered(idCommitmentBigInt)
698
+ );
699
+ if (events.length === 0) return undefined;
700
+
701
+ // Get the most recent registration event
702
+ const event = events[events.length - 1];
703
+ return event.args?.index;
704
+ } catch (error) {
705
+ return undefined;
706
+ }
499
707
  }
500
708
  }