@waku/rln 0.1.5-053bb95.0 → 0.1.5-164df63.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 +282 -146
  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 +280 -144
  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 +428 -216
  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
@@ -1,3 +1,4 @@
1
+ /* eslint-disable no-console */
1
2
  import { Logger } from "@waku/utils";
2
3
  import { ethers } from "ethers";
3
4
 
@@ -7,15 +8,8 @@ import { DecryptedCredentials } from "../keystore/types.js";
7
8
  import { RLN_ABI } from "./abi.js";
8
9
  import { DEFAULT_RATE_LIMIT, RATE_LIMIT_PARAMS } from "./constants.js";
9
10
  import {
10
- InvalidMembershipError,
11
- InvalidRateLimitError,
12
- MembershipExistsError,
13
- MembershipNotFoundError,
14
- RateLimitExceededError,
15
- RLNContractError,
16
- TransactionError
17
- } from "./errors.js";
18
- import {
11
+ CustomQueryOptions,
12
+ FetchMembersOptions,
19
13
  Member,
20
14
  MembershipInfo,
21
15
  MembershipRegisteredEvent,
@@ -27,8 +21,18 @@ const log = new Logger("waku:rln:contract:base");
27
21
 
28
22
  export class RLNBaseContract {
29
23
  public contract: ethers.Contract;
24
+ private deployBlock: undefined | number;
30
25
  private rateLimit: number;
31
26
 
27
+ protected _members: Map<number, Member> = new Map();
28
+ private _membersFilter: ethers.EventFilter;
29
+ private _membershipErasedFilter: ethers.EventFilter;
30
+ private _membersExpiredFilter: ethers.EventFilter;
31
+
32
+ /**
33
+ * Constructor for RLNBaseContract.
34
+ * Allows injecting a mocked contract for testing purposes.
35
+ */
32
36
  public constructor(options: RLNContractInitOptions) {
33
37
  const {
34
38
  address,
@@ -37,9 +41,38 @@ export class RLNBaseContract {
37
41
  contract
38
42
  } = options;
39
43
 
40
- this.validateRateLimit(rateLimit);
44
+ log.info("Initializing RLNBaseContract", { address, rateLimit });
45
+
41
46
  this.contract = contract || new ethers.Contract(address, RLN_ABI, signer);
42
47
  this.rateLimit = rateLimit;
48
+
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
+ }
62
+
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
+ });
43
76
  }
44
77
 
45
78
  /**
@@ -116,151 +149,314 @@ export class RLNBaseContract {
116
149
  * @param newRateLimit The new rate limit to use
117
150
  */
118
151
  public async setRateLimit(newRateLimit: number): Promise<void> {
119
- this.validateRateLimit(newRateLimit);
152
+ await this.validateRateLimit(newRateLimit);
120
153
  this.rateLimit = newRateLimit;
121
154
  }
122
155
 
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
- );
156
+ public get members(): Member[] {
157
+ const sortedMembers = Array.from(this._members.values()).sort(
158
+ (left, right) => left.index.toNumber() - right.index.toNumber()
159
+ );
160
+ return sortedMembers;
161
+ }
139
162
 
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
- );
163
+ public async fetchMembers(options: FetchMembersOptions = {}): Promise<void> {
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
+ );
188
+
189
+ const events = [
190
+ ...registeredMemberEvents,
191
+ ...removedMemberEvents,
192
+ ...expiredMemberEvents
193
+ ];
194
+ this.processEvents(events);
195
+ }
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
+ );
151
231
 
152
- // Wait for all promises to resolve
153
- const members = (await Promise.all(membershipPromises)).filter(
154
- (m): m is Member => m !== null
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
+ )
155
241
  );
156
- return members;
157
- } catch (error) {
242
+ const fetchedEvents = await Promise.all(promises);
243
+ events.push(fetchedEvents.flatMap((v) => v));
244
+ }
245
+
246
+ return events.flatMap((v) => v);
247
+ }
248
+
249
+ public processEvents(events: ethers.Event[]): void {
250
+ const toRemoveTable = new Map<number, number[]>();
251
+ const toInsertTable = new Map<number, ethers.Event[]>();
252
+
253
+ events.forEach((evt) => {
254
+ if (!evt.args) {
255
+ return;
256
+ }
257
+
158
258
  if (
159
- error instanceof Error &&
160
- error.message.includes("InvalidPaginationQuery")
259
+ evt.event === "MembershipErased" ||
260
+ evt.event === "MembershipExpired"
161
261
  ) {
162
- throw new RLNContractError(
163
- `Invalid pagination range: start=${startIndex}, end=${endIndex}`
164
- );
262
+ let index = evt.args.index;
263
+
264
+ if (!index) {
265
+ return;
266
+ }
267
+
268
+ if (typeof index === "number" || typeof index === "string") {
269
+ index = ethers.BigNumber.from(index);
270
+ }
271
+
272
+ const toRemoveVal = toRemoveTable.get(evt.blockNumber);
273
+ if (toRemoveVal != undefined) {
274
+ toRemoveVal.push(index.toNumber());
275
+ toRemoveTable.set(evt.blockNumber, toRemoveVal);
276
+ } else {
277
+ toRemoveTable.set(evt.blockNumber, [index.toNumber()]);
278
+ }
279
+ } else if (evt.event === "MembershipRegistered") {
280
+ let eventsPerBlock = toInsertTable.get(evt.blockNumber);
281
+ if (eventsPerBlock == undefined) {
282
+ eventsPerBlock = [];
283
+ }
284
+
285
+ eventsPerBlock.push(evt);
286
+ toInsertTable.set(evt.blockNumber, eventsPerBlock);
165
287
  }
166
- throw error;
288
+ });
289
+ }
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;
167
305
  }
306
+
307
+ return chunks;
168
308
  }
169
309
 
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);
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
+ }
176
320
  }
177
321
 
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> {
322
+ public static async ignoreErrors<T>(
323
+ promise: Promise<T>,
324
+ defaultValue: T
325
+ ): Promise<T> {
185
326
  try {
186
- const isValid = await this.contract.isInMembershipSet(idCommitment);
187
- if (!isValid) {
188
- return null;
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`);
189
333
  }
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);
334
+ return defaultValue;
196
335
  }
197
336
  }
198
337
 
338
+ public subscribeToMembers(): void {
339
+ this.contract.on(
340
+ this.membersFilter,
341
+ (
342
+ _idCommitment: bigint,
343
+ _membershipRateLimit: ethers.BigNumber,
344
+ _index: ethers.BigNumber,
345
+ event: ethers.Event
346
+ ) => {
347
+ this.processEvents([event]);
348
+ }
349
+ );
350
+
351
+ this.contract.on(
352
+ this.membershipErasedFilter,
353
+ (
354
+ _idCommitment: bigint,
355
+ _membershipRateLimit: ethers.BigNumber,
356
+ _index: ethers.BigNumber,
357
+ event: ethers.Event
358
+ ) => {
359
+ this.processEvents([event]);
360
+ }
361
+ );
362
+
363
+ this.contract.on(
364
+ this.membersExpiredFilter,
365
+ (
366
+ _idCommitment: bigint,
367
+ _membershipRateLimit: ethers.BigNumber,
368
+ _index: ethers.BigNumber,
369
+ event: ethers.Event
370
+ ) => {
371
+ this.processEvents([event]);
372
+ }
373
+ );
374
+ }
375
+
199
376
  public async getMembershipInfo(
200
- idCommitment: string
201
- ): Promise<MembershipInfo> {
377
+ idCommitmentBigInt: bigint
378
+ ): Promise<MembershipInfo | undefined> {
202
379
  try {
203
- const [startBlock, endBlock, rateLimit] =
204
- await this.contract.getMembershipInfo(idCommitment);
380
+ const membershipData =
381
+ await this.contract.memberships(idCommitmentBigInt);
205
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);
206
397
 
207
398
  let state: MembershipState;
208
- if (currentBlock < startBlock) {
399
+ if (currentBlock < gracePeriodStartTimestamp.toNumber()) {
209
400
  state = MembershipState.Active;
210
- } else if (currentBlock < endBlock) {
401
+ } else if (currentBlock < gracePeriodEnd.toNumber()) {
211
402
  state = MembershipState.GracePeriod;
212
403
  } else {
213
404
  state = MembershipState.Expired;
214
405
  }
215
406
 
216
- const index = await this.getMemberIndex(idCommitment);
217
- if (index === null) {
218
- throw new MembershipNotFoundError(idCommitment);
219
- }
220
-
221
407
  return {
222
408
  index,
223
- idCommitment,
224
- rateLimit: rateLimit.toNumber(),
225
- startBlock: startBlock.toNumber(),
226
- endBlock: endBlock.toNumber(),
227
- state
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
228
419
  };
229
420
  } 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);
421
+ log.error("Error in getMembershipInfo:", error);
422
+ return undefined;
235
423
  }
236
424
  }
237
425
 
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
- }));
426
+ public async extendMembership(
427
+ idCommitmentBigInt: bigint
428
+ ): Promise<ethers.ContractTransaction> {
429
+ const tx = await this.contract.extendMemberships([idCommitmentBigInt]);
430
+ await tx.wait();
431
+ return tx;
244
432
  }
245
433
 
246
434
  public async eraseMembership(
247
- idCommitment: string,
435
+ idCommitmentBigInt: bigint,
248
436
  eraseFromMembershipSet: boolean = true
249
- ): Promise<void> {
437
+ ): Promise<ethers.ContractTransaction> {
438
+ console.log(Object.keys(this.contract));
250
439
  const tx = await this.contract.eraseMemberships(
251
- [idCommitment],
440
+ [idCommitmentBigInt],
252
441
  eraseFromMembershipSet
253
442
  );
254
- await this.confirmTransaction(tx, "MembershipErased", (event) => ({
255
- idCommitment: event.args!.idCommitment,
256
- index: event.args!.index
257
- }));
443
+ await tx.wait();
444
+ return tx;
445
+ }
446
+
447
+ public async withdraw(token: string, from: string): Promise<void> {
448
+ try {
449
+ const tx = await this.contract.withdraw(token, from);
450
+ await tx.wait();
451
+ } catch (error) {
452
+ log.error(`Error in withdraw: ${(error as Error).message}`);
453
+ }
258
454
  }
259
455
 
260
456
  public async registerMembership(
261
- idCommitment: string,
457
+ idCommitmentBigInt: bigint,
262
458
  rateLimit: number = DEFAULT_RATE_LIMIT
263
- ): Promise<void> {
459
+ ): Promise<ethers.ContractTransaction> {
264
460
  if (
265
461
  rateLimit < RATE_LIMIT_PARAMS.MIN_RATE ||
266
462
  rateLimit > RATE_LIMIT_PARAMS.MAX_RATE
@@ -269,26 +465,7 @@ export class RLNBaseContract {
269
465
  `Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE}`
270
466
  );
271
467
  }
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
- }));
278
- }
279
-
280
- public async withdraw(token: string, holder: string): Promise<void> {
281
- 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
- }));
288
- } catch (error) {
289
- log.error(`Error in withdraw: ${(error as Error).message}`);
290
- throw error;
291
- }
468
+ return this.contract.register(idCommitmentBigInt, rateLimit, []);
292
469
  }
293
470
 
294
471
  public async registerWithIdentity(
@@ -301,20 +478,20 @@ export class RLNBaseContract {
301
478
 
302
479
  // Check if the ID commitment is already registered
303
480
  const existingIndex = await this.getMemberIndex(
304
- identity.IDCommitmentBigInt.toString()
481
+ identity.IDCommitmentBigInt
305
482
  );
306
-
307
- if (existingIndex !== null) {
308
- throw new MembershipExistsError(
309
- identity.IDCommitmentBigInt.toString(),
310
- existingIndex.toString()
483
+ if (existingIndex) {
484
+ throw new Error(
485
+ `ID commitment is already registered with index ${existingIndex}`
311
486
  );
312
487
  }
313
488
 
314
489
  // Check if there's enough remaining rate limit
315
490
  const remainingRateLimit = await this.getRemainingTotalRateLimit();
316
491
  if (remainingRateLimit < this.rateLimit) {
317
- throw new RateLimitExceededError(this.rateLimit, remainingRateLimit);
492
+ throw new Error(
493
+ `Not enough remaining rate limit. Requested: ${this.rateLimit}, Available: ${remainingRateLimit}`
494
+ );
318
495
  }
319
496
 
320
497
  const estimatedGas = await this.contract.estimateGas.register(
@@ -324,23 +501,37 @@ export class RLNBaseContract {
324
501
  );
325
502
  const gasLimit = estimatedGas.add(10000);
326
503
 
327
- const tx = await this.contract.register(
328
- identity.IDCommitmentBigInt,
329
- this.rateLimit,
330
- [],
331
- { gasLimit }
332
- );
504
+ const txRegisterResponse: ethers.ContractTransaction =
505
+ await this.contract.register(
506
+ identity.IDCommitmentBigInt,
507
+ this.rateLimit,
508
+ [],
509
+ { gasLimit }
510
+ );
511
+
512
+ const txRegisterReceipt = await txRegisterResponse.wait();
333
513
 
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
- })
514
+ if (txRegisterReceipt.status === 0) {
515
+ throw new Error("Transaction failed on-chain");
516
+ }
517
+
518
+ const memberRegistered = txRegisterReceipt.events?.find(
519
+ (event: ethers.Event) => event.event === "MembershipRegistered"
342
520
  );
343
521
 
522
+ if (!memberRegistered || !memberRegistered.args) {
523
+ log.error(
524
+ "Failed to register membership: No MembershipRegistered event found"
525
+ );
526
+ return undefined;
527
+ }
528
+
529
+ const decodedData: MembershipRegisteredEvent = {
530
+ idCommitment: memberRegistered.args.idCommitment,
531
+ membershipRateLimit: memberRegistered.args.membershipRateLimit,
532
+ index: memberRegistered.args.index
533
+ };
534
+
344
535
  log.info(
345
536
  `Successfully registered membership with index ${decodedData.index} ` +
346
537
  `and rate limit ${decodedData.membershipRateLimit}`
@@ -348,55 +539,44 @@ export class RLNBaseContract {
348
539
 
349
540
  const network = await this.contract.provider.getNetwork();
350
541
  const address = this.contract.address;
351
- const membershipId = decodedData.index.toString();
542
+ const membershipId = Number(decodedData.index);
352
543
 
353
544
  return {
354
545
  identity,
355
546
  membership: {
356
547
  address,
357
- treeIndex: parseInt(membershipId),
548
+ treeIndex: membershipId,
358
549
  chainId: network.chainId.toString(),
359
550
  rateLimit: decodedData.membershipRateLimit.toNumber()
360
551
  }
361
552
  };
362
553
  } catch (error) {
363
- if (error instanceof RLNContractError) {
364
- throw error;
365
- }
366
-
367
554
  if (error instanceof Error) {
368
555
  const errorMessage = error.message;
369
556
  log.error("registerWithIdentity - error message:", errorMessage);
370
557
  log.error("registerWithIdentity - error stack:", error.stack);
371
558
 
372
- // Map contract errors to our custom errors
559
+ // Try to extract more specific error information
373
560
  if (errorMessage.includes("CannotExceedMaxTotalRateLimit")) {
374
- throw new RateLimitExceededError(
375
- this.rateLimit,
376
- await this.getRemainingTotalRateLimit()
561
+ throw new Error(
562
+ "Registration failed: Cannot exceed maximum total rate limit"
377
563
  );
378
564
  } else if (errorMessage.includes("InvalidIdCommitment")) {
379
- throw new InvalidMembershipError(
380
- identity.IDCommitmentBigInt.toString()
381
- );
565
+ throw new Error("Registration failed: Invalid ID commitment");
382
566
  } 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
- );
567
+ throw new Error("Registration failed: Invalid membership rate limit");
388
568
  } else if (errorMessage.includes("execution reverted")) {
389
- throw new TransactionError(
569
+ throw new Error(
390
570
  "Contract execution reverted. Check contract requirements."
391
571
  );
572
+ } else {
573
+ throw new Error(`Error in registerWithIdentity: ${errorMessage}`);
392
574
  }
393
-
394
- throw new RLNContractError(
395
- `Error in registerWithIdentity: ${errorMessage}`
396
- );
575
+ } else {
576
+ throw new Error("Unknown error in registerWithIdentity", {
577
+ cause: error
578
+ });
397
579
  }
398
-
399
- throw new RLNContractError("Unknown error in registerWithIdentity");
400
580
  }
401
581
  }
402
582
 
@@ -416,27 +596,36 @@ export class RLNBaseContract {
416
596
  `Registering identity with permit and rate limit: ${this.rateLimit} messages/epoch`
417
597
  );
418
598
 
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
- );
599
+ const txRegisterResponse: ethers.ContractTransaction =
600
+ await this.contract.registerWithPermit(
601
+ permit.owner,
602
+ permit.deadline,
603
+ permit.v,
604
+ permit.r,
605
+ permit.s,
606
+ identity.IDCommitmentBigInt,
607
+ this.rateLimit,
608
+ idCommitmentsToErase.map((id) => ethers.BigNumber.from(id))
609
+ );
610
+ const txRegisterReceipt = await txRegisterResponse.wait();
429
611
 
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
- })
612
+ const memberRegistered = txRegisterReceipt.events?.find(
613
+ (event: ethers.Event) => event.event === "MembershipRegistered"
438
614
  );
439
615
 
616
+ if (!memberRegistered || !memberRegistered.args) {
617
+ log.error(
618
+ "Failed to register membership with permit: No MembershipRegistered event found"
619
+ );
620
+ return undefined;
621
+ }
622
+
623
+ const decodedData: MembershipRegisteredEvent = {
624
+ idCommitment: memberRegistered.args.idCommitment,
625
+ membershipRateLimit: memberRegistered.args.membershipRateLimit,
626
+ index: memberRegistered.args.index
627
+ };
628
+
440
629
  log.info(
441
630
  `Successfully registered membership with permit. Index: ${decodedData.index}, ` +
442
631
  `Rate limit: ${decodedData.membershipRateLimit}, Erased ${idCommitmentsToErase.length} commitments`
@@ -444,13 +633,13 @@ export class RLNBaseContract {
444
633
 
445
634
  const network = await this.contract.provider.getNetwork();
446
635
  const address = this.contract.address;
447
- const membershipId = decodedData.index.toString();
636
+ const membershipId = Number(decodedData.index);
448
637
 
449
638
  return {
450
639
  identity,
451
640
  membership: {
452
641
  address,
453
- treeIndex: parseInt(membershipId),
642
+ treeIndex: membershipId,
454
643
  chainId: network.chainId.toString(),
455
644
  rateLimit: decodedData.membershipRateLimit.toNumber()
456
645
  }
@@ -459,7 +648,7 @@ export class RLNBaseContract {
459
648
  log.error(
460
649
  `Error in registerWithPermitAndErase: ${(error as Error).message}`
461
650
  );
462
- throw error;
651
+ return undefined;
463
652
  }
464
653
  }
465
654
 
@@ -467,34 +656,57 @@ export class RLNBaseContract {
467
656
  * Validates that the rate limit is within the allowed range
468
657
  * @throws Error if the rate limit is outside the allowed range
469
658
  */
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
659
+ private async validateRateLimit(rateLimit: number): Promise<void> {
660
+ const [minRate, maxRate] = await Promise.all([
661
+ this.contract.minMembershipRateLimit(),
662
+ this.contract.maxMembershipRateLimit()
663
+ ]);
664
+
665
+ const minRateNum = ethers.BigNumber.from(minRate).toNumber();
666
+ const maxRateNum = ethers.BigNumber.from(maxRate).toNumber();
667
+
668
+ if (rateLimit < minRateNum || rateLimit > maxRateNum) {
669
+ throw new Error(
670
+ `Rate limit must be between ${minRateNum} and ${maxRateNum} messages per epoch`
479
671
  );
480
672
  }
481
673
  }
482
674
 
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);
675
+ private get membersFilter(): ethers.EventFilter {
676
+ if (!this._membersFilter) {
677
+ throw Error("Members filter was not initialized.");
678
+ }
679
+ return this._membersFilter;
680
+ }
493
681
 
494
- if (!event || !event.args) {
495
- throw new TransactionError(`Expected event ${expectedEvent} not found`);
682
+ private get membershipErasedFilter(): ethers.EventFilter {
683
+ if (!this._membershipErasedFilter) {
684
+ throw Error("MembershipErased filter was not initialized.");
496
685
  }
686
+ return this._membershipErasedFilter;
687
+ }
688
+
689
+ private get membersExpiredFilter(): ethers.EventFilter {
690
+ if (!this._membersExpiredFilter) {
691
+ throw Error("MembersExpired filter was not initialized.");
692
+ }
693
+ return this._membersExpiredFilter;
694
+ }
695
+
696
+ private async getMemberIndex(
697
+ idCommitmentBigInt: bigint
698
+ ): Promise<ethers.BigNumber | undefined> {
699
+ try {
700
+ const events = await this.contract.queryFilter(
701
+ this.contract.filters.MembershipRegistered(idCommitmentBigInt)
702
+ );
703
+ if (events.length === 0) return undefined;
497
704
 
498
- return transform(event);
705
+ // Get the most recent registration event
706
+ const event = events[events.length - 1];
707
+ return event.args?.index;
708
+ } catch (error) {
709
+ return undefined;
710
+ }
499
711
  }
500
712
  }