@waku/rln 0.1.4-d27db21.0 → 0.1.5-053bb95.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 (138) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +5 -0
  3. package/bundle/_virtual/utils.js +2 -2
  4. package/bundle/_virtual/utils2.js +2 -2
  5. package/bundle/index.js +3 -1
  6. package/bundle/node_modules/@ethersproject/abi/lib.esm/coders/abstract-coder.js +1 -1
  7. package/bundle/node_modules/@ethersproject/abi/lib.esm/interface.js +1 -1
  8. package/bundle/node_modules/@ethersproject/abstract-provider/lib.esm/index.js +1 -1
  9. package/bundle/node_modules/@ethersproject/abstract-signer/lib.esm/index.js +11 -11
  10. package/bundle/node_modules/@ethersproject/bytes/lib.esm/_version.js +1 -1
  11. package/bundle/node_modules/@ethersproject/contracts/lib.esm/index.js +7 -7
  12. package/bundle/node_modules/@ethersproject/hash/lib.esm/ens-normalize/lib.js +2 -2
  13. package/bundle/node_modules/@ethersproject/hash/lib.esm/typed-data.js +2 -2
  14. package/bundle/node_modules/@ethersproject/keccak256/node_modules/js-sha3/src/sha3.js +1 -1
  15. package/bundle/node_modules/@ethersproject/logger/lib.esm/_version.js +1 -1
  16. package/bundle/node_modules/@ethersproject/properties/lib.esm/index.js +1 -1
  17. package/bundle/node_modules/@ethersproject/providers/lib.esm/base-provider.js +44 -44
  18. package/bundle/node_modules/@ethersproject/providers/lib.esm/json-rpc-provider.js +8 -8
  19. package/bundle/node_modules/@ethersproject/rlp/lib.esm/_version.js +1 -1
  20. package/bundle/node_modules/@ethersproject/transactions/lib.esm/index.js +1 -1
  21. package/bundle/node_modules/@ethersproject/web/lib.esm/geturl.js +1 -1
  22. package/bundle/node_modules/@ethersproject/web/lib.esm/index.js +2 -2
  23. package/bundle/node_modules/@multiformats/multiaddr/dist/src/multiaddr.js +1 -0
  24. package/bundle/node_modules/@noble/hashes/esm/sha3.js +1 -1
  25. package/bundle/node_modules/bn.js/lib/bn.js +1 -1
  26. package/bundle/packages/core/dist/lib/connection_manager/connection_manager.js +6 -6
  27. package/bundle/packages/rln/dist/contract/abi.js +502 -248
  28. package/bundle/packages/rln/dist/contract/constants.js +5 -9
  29. package/bundle/packages/rln/dist/contract/errors.js +62 -0
  30. package/bundle/packages/rln/dist/contract/rln_base_contract.js +347 -0
  31. package/bundle/packages/rln/dist/contract/rln_contract.js +81 -392
  32. package/bundle/packages/rln/dist/contract/types.js +9 -0
  33. package/bundle/packages/rln/dist/create.js +1 -1
  34. package/bundle/packages/rln/dist/credentials_manager.js +215 -0
  35. package/bundle/packages/rln/dist/identity.js +8 -0
  36. package/bundle/packages/rln/dist/keystore/keystore.js +20 -28
  37. package/bundle/packages/rln/dist/rln.js +56 -166
  38. package/bundle/packages/rln/dist/zerokit.js +5 -5
  39. package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/lib/checksum.js +2 -2
  40. package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/lib/cipher.js +3 -3
  41. package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/lib/class.js +4 -4
  42. package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/lib/functional.js +4 -4
  43. package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/lib/kdf.js +4 -4
  44. package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/lib/password.js +1 -1
  45. package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/lib/schema-validation-generated.js +1 -1
  46. package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/lib/schema-validation.js +1 -1
  47. package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/node_modules/ethereum-cryptography/aes.js +1 -1
  48. package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/node_modules/ethereum-cryptography/pbkdf2.js +1 -1
  49. package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/node_modules/ethereum-cryptography/random.js +2 -2
  50. package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/node_modules/ethereum-cryptography/scrypt.js +1 -1
  51. package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/node_modules/ethereum-cryptography/sha256.js +1 -1
  52. package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/node_modules/ethereum-cryptography/utils.js +3 -3
  53. package/bundle/packages/rln/node_modules/@noble/hashes/_assert.js +1 -1
  54. package/bundle/packages/rln/node_modules/@noble/hashes/_sha2.js +2 -2
  55. package/bundle/packages/rln/node_modules/@noble/hashes/_u64.js +1 -1
  56. package/bundle/packages/rln/node_modules/@noble/hashes/cryptoBrowser.js +1 -1
  57. package/bundle/packages/rln/node_modules/@noble/hashes/esm/_assert.js +43 -0
  58. package/bundle/packages/rln/node_modules/@noble/hashes/esm/_sha2.js +116 -0
  59. package/bundle/packages/rln/node_modules/@noble/hashes/esm/hmac.js +79 -0
  60. package/bundle/packages/rln/node_modules/@noble/hashes/esm/sha256.js +126 -0
  61. package/bundle/packages/rln/node_modules/@noble/hashes/esm/utils.js +43 -0
  62. package/bundle/packages/rln/node_modules/@noble/hashes/hmac.js +2 -2
  63. package/bundle/packages/rln/node_modules/@noble/hashes/pbkdf2.js +2 -2
  64. package/bundle/packages/rln/node_modules/@noble/hashes/scrypt.js +3 -3
  65. package/bundle/packages/rln/node_modules/@noble/hashes/sha256.js +2 -2
  66. package/bundle/packages/rln/node_modules/@noble/hashes/sha512.js +3 -3
  67. package/bundle/packages/rln/node_modules/@noble/hashes/utils.js +2 -2
  68. package/dist/.tsbuildinfo +1 -1
  69. package/dist/codec.test-utils.d.ts +36 -0
  70. package/dist/codec.test-utils.js +56 -0
  71. package/dist/codec.test-utils.js.map +1 -0
  72. package/dist/contract/abi.d.ts +21 -17
  73. package/dist/contract/abi.js +502 -248
  74. package/dist/contract/abi.js.map +1 -1
  75. package/dist/contract/constants.d.ts +23 -19
  76. package/dist/contract/constants.js +3 -3
  77. package/dist/contract/constants.js.map +1 -1
  78. package/dist/contract/errors.d.ts +30 -0
  79. package/dist/contract/errors.js +61 -0
  80. package/dist/contract/errors.js.map +1 -0
  81. package/dist/contract/rln_base_contract.d.ts +88 -0
  82. package/dist/contract/rln_base_contract.js +330 -0
  83. package/dist/contract/rln_base_contract.js.map +1 -0
  84. package/dist/contract/rln_contract.d.ts +19 -109
  85. package/dist/contract/rln_contract.js +80 -390
  86. package/dist/contract/rln_contract.js.map +1 -1
  87. package/dist/contract/test-setup.d.ts +26 -0
  88. package/dist/contract/test-setup.js +56 -0
  89. package/dist/contract/test-setup.js.map +1 -0
  90. package/dist/contract/test-utils.d.ts +39 -0
  91. package/dist/contract/test-utils.js +118 -0
  92. package/dist/contract/test-utils.js.map +1 -0
  93. package/dist/contract/types.d.ts +40 -0
  94. package/dist/contract/types.js +8 -0
  95. package/dist/contract/types.js.map +1 -0
  96. package/dist/create.js +1 -1
  97. package/dist/create.js.map +1 -1
  98. package/dist/credentials_manager.d.ts +44 -0
  99. package/dist/credentials_manager.js +197 -0
  100. package/dist/credentials_manager.js.map +1 -0
  101. package/dist/identity.d.ts +1 -0
  102. package/dist/identity.js +8 -0
  103. package/dist/identity.js.map +1 -1
  104. package/dist/index.d.ts +5 -2
  105. package/dist/index.js +4 -2
  106. package/dist/index.js.map +1 -1
  107. package/dist/keystore/keystore.d.ts +0 -1
  108. package/dist/keystore/keystore.js +20 -28
  109. package/dist/keystore/keystore.js.map +1 -1
  110. package/dist/keystore/types.d.ts +2 -1
  111. package/dist/rln.d.ts +9 -52
  112. package/dist/rln.js +54 -163
  113. package/dist/rln.js.map +1 -1
  114. package/dist/types.d.ts +27 -0
  115. package/dist/types.js +2 -0
  116. package/dist/types.js.map +1 -0
  117. package/dist/zerokit.d.ts +3 -3
  118. package/dist/zerokit.js +5 -5
  119. package/dist/zerokit.js.map +1 -1
  120. package/package.json +1 -1
  121. package/src/codec.test-utils.ts +80 -0
  122. package/src/contract/abi.ts +502 -248
  123. package/src/contract/constants.ts +3 -3
  124. package/src/contract/errors.ts +75 -0
  125. package/src/contract/rln_base_contract.ts +500 -0
  126. package/src/contract/rln_contract.ts +102 -619
  127. package/src/contract/test-setup.ts +86 -0
  128. package/src/contract/test-utils.ts +179 -0
  129. package/src/contract/types.ts +48 -0
  130. package/src/create.ts +1 -1
  131. package/src/credentials_manager.ts +282 -0
  132. package/src/identity.ts +9 -0
  133. package/src/index.ts +17 -2
  134. package/src/keystore/keystore.ts +32 -46
  135. package/src/keystore/types.ts +2 -1
  136. package/src/rln.ts +67 -258
  137. package/src/types.ts +31 -0
  138. package/src/zerokit.ts +3 -3
@@ -1,8 +1,8 @@
1
1
  import { RLN_ABI } from "./abi.js";
2
2
 
3
- export const SEPOLIA_CONTRACT = {
4
- chainId: 11155111,
5
- address: "0xCB33Aa5B38d79E3D9Fa8B10afF38AA201399a7e3",
3
+ export const LINEA_CONTRACT = {
4
+ chainId: "59141",
5
+ address: "0xb9cd878c90e49f797b4431fbf4fb333108cb90e6",
6
6
  abi: RLN_ABI
7
7
  };
8
8
 
@@ -0,0 +1,75 @@
1
+ export class RLNContractError extends Error {
2
+ public constructor(message: string) {
3
+ super(message);
4
+ this.name = "RLNContractError";
5
+ }
6
+ }
7
+
8
+ export class MembershipError extends RLNContractError {
9
+ public constructor(message: string) {
10
+ super(message);
11
+ this.name = "MembershipError";
12
+ }
13
+ }
14
+
15
+ export class RateLimitError extends RLNContractError {
16
+ public constructor(message: string) {
17
+ super(message);
18
+ this.name = "RateLimitError";
19
+ }
20
+ }
21
+
22
+ export class InvalidMembershipError extends MembershipError {
23
+ public constructor(idCommitment: string) {
24
+ super(`Invalid membership ID commitment: ${idCommitment}`);
25
+ this.name = "InvalidMembershipError";
26
+ }
27
+ }
28
+
29
+ export class MembershipNotFoundError extends MembershipError {
30
+ public constructor(idCommitment: string) {
31
+ super(`Membership not found for ID commitment: ${idCommitment}`);
32
+ this.name = "MembershipNotFoundError";
33
+ }
34
+ }
35
+
36
+ export class MembershipExistsError extends MembershipError {
37
+ public constructor(idCommitment: string, index: string) {
38
+ super(
39
+ `Membership already exists for ID commitment: ${idCommitment} at index ${index}`
40
+ );
41
+ this.name = "MembershipExistsError";
42
+ }
43
+ }
44
+
45
+ export class RateLimitExceededError extends RateLimitError {
46
+ public constructor(requested: number, available: number) {
47
+ super(
48
+ `Rate limit exceeded. Requested: ${requested}, Available: ${available}`
49
+ );
50
+ this.name = "RateLimitExceededError";
51
+ }
52
+ }
53
+
54
+ export class InvalidRateLimitError extends RateLimitError {
55
+ public constructor(rateLimit: number, minRate: number, maxRate: number) {
56
+ super(
57
+ `Invalid rate limit: ${rateLimit}. Must be between ${minRate} and ${maxRate}`
58
+ );
59
+ this.name = "InvalidRateLimitError";
60
+ }
61
+ }
62
+
63
+ export class ContractStateError extends RLNContractError {
64
+ public constructor(message: string) {
65
+ super(`Contract state error: ${message}`);
66
+ this.name = "ContractStateError";
67
+ }
68
+ }
69
+
70
+ export class TransactionError extends RLNContractError {
71
+ public constructor(message: string) {
72
+ super(`Transaction failed: ${message}`);
73
+ this.name = "TransactionError";
74
+ }
75
+ }
@@ -0,0 +1,500 @@
1
+ import { Logger } from "@waku/utils";
2
+ import { ethers } from "ethers";
3
+
4
+ import { IdentityCredential } from "../identity.js";
5
+ import { DecryptedCredentials } from "../keystore/types.js";
6
+
7
+ import { RLN_ABI } from "./abi.js";
8
+ import { DEFAULT_RATE_LIMIT, RATE_LIMIT_PARAMS } from "./constants.js";
9
+ import {
10
+ InvalidMembershipError,
11
+ InvalidRateLimitError,
12
+ MembershipExistsError,
13
+ MembershipNotFoundError,
14
+ RateLimitExceededError,
15
+ RLNContractError,
16
+ TransactionError
17
+ } from "./errors.js";
18
+ import {
19
+ Member,
20
+ MembershipInfo,
21
+ MembershipRegisteredEvent,
22
+ MembershipState,
23
+ RLNContractInitOptions
24
+ } from "./types.js";
25
+
26
+ const log = new Logger("waku:rln:contract:base");
27
+
28
+ export class RLNBaseContract {
29
+ public contract: ethers.Contract;
30
+ private rateLimit: number;
31
+
32
+ public constructor(options: RLNContractInitOptions) {
33
+ const {
34
+ address,
35
+ signer,
36
+ rateLimit = DEFAULT_RATE_LIMIT,
37
+ contract
38
+ } = options;
39
+
40
+ this.validateRateLimit(rateLimit);
41
+ this.contract = contract || new ethers.Contract(address, RLN_ABI, signer);
42
+ this.rateLimit = rateLimit;
43
+ }
44
+
45
+ /**
46
+ * Gets the current rate limit for this contract instance
47
+ */
48
+ public getRateLimit(): number {
49
+ return this.rateLimit;
50
+ }
51
+
52
+ /**
53
+ * Gets the contract address
54
+ */
55
+ public get address(): string {
56
+ return this.contract.address;
57
+ }
58
+
59
+ /**
60
+ * Gets the contract provider
61
+ */
62
+ public get provider(): ethers.providers.Provider {
63
+ return this.contract.provider;
64
+ }
65
+
66
+ /**
67
+ * Gets the minimum allowed rate limit from the contract
68
+ * @returns Promise<number> The minimum rate limit in messages per epoch
69
+ */
70
+ public async getMinRateLimit(): Promise<number> {
71
+ const minRate = await this.contract.minMembershipRateLimit();
72
+ return ethers.BigNumber.from(minRate).toNumber();
73
+ }
74
+
75
+ /**
76
+ * Gets the maximum allowed rate limit from the contract
77
+ * @returns Promise<number> The maximum rate limit in messages per epoch
78
+ */
79
+ public async getMaxRateLimit(): Promise<number> {
80
+ const maxRate = await this.contract.maxMembershipRateLimit();
81
+ return ethers.BigNumber.from(maxRate).toNumber();
82
+ }
83
+
84
+ /**
85
+ * Gets the maximum total rate limit across all memberships
86
+ * @returns Promise<number> The maximum total rate limit in messages per epoch
87
+ */
88
+ public async getMaxTotalRateLimit(): Promise<number> {
89
+ const maxTotalRate = await this.contract.maxTotalRateLimit();
90
+ return maxTotalRate.toNumber();
91
+ }
92
+
93
+ /**
94
+ * Gets the current total rate limit usage across all memberships
95
+ * @returns Promise<number> The current total rate limit usage in messages per epoch
96
+ */
97
+ public async getCurrentTotalRateLimit(): Promise<number> {
98
+ const currentTotal = await this.contract.currentTotalRateLimit();
99
+ return currentTotal.toNumber();
100
+ }
101
+
102
+ /**
103
+ * Gets the remaining available total rate limit that can be allocated
104
+ * @returns Promise<number> The remaining rate limit that can be allocated
105
+ */
106
+ public async getRemainingTotalRateLimit(): Promise<number> {
107
+ const [maxTotal, currentTotal] = await Promise.all([
108
+ this.contract.maxTotalRateLimit(),
109
+ this.contract.currentTotalRateLimit()
110
+ ]);
111
+ return Number(maxTotal) - Number(currentTotal);
112
+ }
113
+
114
+ /**
115
+ * Updates the rate limit for future registrations
116
+ * @param newRateLimit The new rate limit to use
117
+ */
118
+ public async setRateLimit(newRateLimit: number): Promise<void> {
119
+ this.validateRateLimit(newRateLimit);
120
+ this.rateLimit = newRateLimit;
121
+ }
122
+
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
+ );
139
+
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
+ );
151
+
152
+ // Wait for all promises to resolve
153
+ const members = (await Promise.all(membershipPromises)).filter(
154
+ (m): m is Member => m !== null
155
+ );
156
+ return members;
157
+ } catch (error) {
158
+ if (
159
+ error instanceof Error &&
160
+ error.message.includes("InvalidPaginationQuery")
161
+ ) {
162
+ throw new RLNContractError(
163
+ `Invalid pagination range: start=${startIndex}, end=${endIndex}`
164
+ );
165
+ }
166
+ throw error;
167
+ }
168
+ }
169
+
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);
176
+ }
177
+
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> {
185
+ try {
186
+ const isValid = await this.contract.isInMembershipSet(idCommitment);
187
+ if (!isValid) {
188
+ return null;
189
+ }
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);
196
+ }
197
+ }
198
+
199
+ public async getMembershipInfo(
200
+ idCommitment: string
201
+ ): Promise<MembershipInfo> {
202
+ try {
203
+ const [startBlock, endBlock, rateLimit] =
204
+ await this.contract.getMembershipInfo(idCommitment);
205
+ const currentBlock = await this.contract.provider.getBlockNumber();
206
+
207
+ let state: MembershipState;
208
+ if (currentBlock < startBlock) {
209
+ state = MembershipState.Active;
210
+ } else if (currentBlock < endBlock) {
211
+ state = MembershipState.GracePeriod;
212
+ } else {
213
+ state = MembershipState.Expired;
214
+ }
215
+
216
+ const index = await this.getMemberIndex(idCommitment);
217
+ if (index === null) {
218
+ throw new MembershipNotFoundError(idCommitment);
219
+ }
220
+
221
+ return {
222
+ index,
223
+ idCommitment,
224
+ rateLimit: rateLimit.toNumber(),
225
+ startBlock: startBlock.toNumber(),
226
+ endBlock: endBlock.toNumber(),
227
+ state
228
+ };
229
+ } 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);
235
+ }
236
+ }
237
+
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
+ }));
244
+ }
245
+
246
+ public async eraseMembership(
247
+ idCommitment: string,
248
+ eraseFromMembershipSet: boolean = true
249
+ ): Promise<void> {
250
+ const tx = await this.contract.eraseMemberships(
251
+ [idCommitment],
252
+ eraseFromMembershipSet
253
+ );
254
+ await this.confirmTransaction(tx, "MembershipErased", (event) => ({
255
+ idCommitment: event.args!.idCommitment,
256
+ index: event.args!.index
257
+ }));
258
+ }
259
+
260
+ public async registerMembership(
261
+ idCommitment: string,
262
+ rateLimit: number = DEFAULT_RATE_LIMIT
263
+ ): Promise<void> {
264
+ if (
265
+ rateLimit < RATE_LIMIT_PARAMS.MIN_RATE ||
266
+ rateLimit > RATE_LIMIT_PARAMS.MAX_RATE
267
+ ) {
268
+ throw new Error(
269
+ `Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE}`
270
+ );
271
+ }
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
+ }
292
+ }
293
+
294
+ public async registerWithIdentity(
295
+ identity: IdentityCredential
296
+ ): Promise<DecryptedCredentials | undefined> {
297
+ try {
298
+ log.info(
299
+ `Registering identity with rate limit: ${this.rateLimit} messages/epoch`
300
+ );
301
+
302
+ // Check if the ID commitment is already registered
303
+ const existingIndex = await this.getMemberIndex(
304
+ identity.IDCommitmentBigInt.toString()
305
+ );
306
+
307
+ if (existingIndex !== null) {
308
+ throw new MembershipExistsError(
309
+ identity.IDCommitmentBigInt.toString(),
310
+ existingIndex.toString()
311
+ );
312
+ }
313
+
314
+ // Check if there's enough remaining rate limit
315
+ const remainingRateLimit = await this.getRemainingTotalRateLimit();
316
+ if (remainingRateLimit < this.rateLimit) {
317
+ throw new RateLimitExceededError(this.rateLimit, remainingRateLimit);
318
+ }
319
+
320
+ const estimatedGas = await this.contract.estimateGas.register(
321
+ identity.IDCommitmentBigInt,
322
+ this.rateLimit,
323
+ []
324
+ );
325
+ const gasLimit = estimatedGas.add(10000);
326
+
327
+ const tx = await this.contract.register(
328
+ identity.IDCommitmentBigInt,
329
+ this.rateLimit,
330
+ [],
331
+ { gasLimit }
332
+ );
333
+
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
+ })
342
+ );
343
+
344
+ log.info(
345
+ `Successfully registered membership with index ${decodedData.index} ` +
346
+ `and rate limit ${decodedData.membershipRateLimit}`
347
+ );
348
+
349
+ const network = await this.contract.provider.getNetwork();
350
+ const address = this.contract.address;
351
+ const membershipId = decodedData.index.toString();
352
+
353
+ return {
354
+ identity,
355
+ membership: {
356
+ address,
357
+ treeIndex: parseInt(membershipId),
358
+ chainId: network.chainId.toString(),
359
+ rateLimit: decodedData.membershipRateLimit.toNumber()
360
+ }
361
+ };
362
+ } catch (error) {
363
+ if (error instanceof RLNContractError) {
364
+ throw error;
365
+ }
366
+
367
+ if (error instanceof Error) {
368
+ const errorMessage = error.message;
369
+ log.error("registerWithIdentity - error message:", errorMessage);
370
+ log.error("registerWithIdentity - error stack:", error.stack);
371
+
372
+ // Map contract errors to our custom errors
373
+ if (errorMessage.includes("CannotExceedMaxTotalRateLimit")) {
374
+ throw new RateLimitExceededError(
375
+ this.rateLimit,
376
+ await this.getRemainingTotalRateLimit()
377
+ );
378
+ } else if (errorMessage.includes("InvalidIdCommitment")) {
379
+ throw new InvalidMembershipError(
380
+ identity.IDCommitmentBigInt.toString()
381
+ );
382
+ } 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
+ );
388
+ } else if (errorMessage.includes("execution reverted")) {
389
+ throw new TransactionError(
390
+ "Contract execution reverted. Check contract requirements."
391
+ );
392
+ }
393
+
394
+ throw new RLNContractError(
395
+ `Error in registerWithIdentity: ${errorMessage}`
396
+ );
397
+ }
398
+
399
+ throw new RLNContractError("Unknown error in registerWithIdentity");
400
+ }
401
+ }
402
+
403
+ public async registerWithPermitAndErase(
404
+ identity: IdentityCredential,
405
+ permit: {
406
+ owner: string;
407
+ deadline: number;
408
+ v: number;
409
+ r: string;
410
+ s: string;
411
+ },
412
+ idCommitmentsToErase: string[]
413
+ ): Promise<DecryptedCredentials | undefined> {
414
+ try {
415
+ log.info(
416
+ `Registering identity with permit and rate limit: ${this.rateLimit} messages/epoch`
417
+ );
418
+
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
+ );
429
+
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
+ })
438
+ );
439
+
440
+ log.info(
441
+ `Successfully registered membership with permit. Index: ${decodedData.index}, ` +
442
+ `Rate limit: ${decodedData.membershipRateLimit}, Erased ${idCommitmentsToErase.length} commitments`
443
+ );
444
+
445
+ const network = await this.contract.provider.getNetwork();
446
+ const address = this.contract.address;
447
+ const membershipId = decodedData.index.toString();
448
+
449
+ return {
450
+ identity,
451
+ membership: {
452
+ address,
453
+ treeIndex: parseInt(membershipId),
454
+ chainId: network.chainId.toString(),
455
+ rateLimit: decodedData.membershipRateLimit.toNumber()
456
+ }
457
+ };
458
+ } catch (error) {
459
+ log.error(
460
+ `Error in registerWithPermitAndErase: ${(error as Error).message}`
461
+ );
462
+ throw error;
463
+ }
464
+ }
465
+
466
+ /**
467
+ * Validates that the rate limit is within the allowed range
468
+ * @throws Error if the rate limit is outside the allowed range
469
+ */
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
479
+ );
480
+ }
481
+ }
482
+
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);
493
+
494
+ if (!event || !event.args) {
495
+ throw new TransactionError(`Expected event ${expectedEvent} not found`);
496
+ }
497
+
498
+ return transform(event);
499
+ }
500
+ }