@waku/rln 0.1.5-053bb95.0 → 0.1.5-4adf870.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundle/_virtual/utils.js +2 -2
- package/bundle/_virtual/utils2.js +2 -2
- package/bundle/packages/rln/dist/contract/constants.js +5 -2
- package/bundle/packages/rln/dist/contract/rln_base_contract.js +246 -116
- package/bundle/packages/rln/dist/contract/rln_contract.js +74 -89
- package/bundle/packages/rln/dist/credentials_manager.js +2 -2
- package/bundle/packages/rln/dist/identity.js +0 -8
- package/bundle/packages/rln/dist/keystore/keystore.js +28 -19
- package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/node_modules/ethereum-cryptography/random.js +1 -1
- package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/node_modules/ethereum-cryptography/utils.js +2 -2
- package/bundle/packages/rln/node_modules/@noble/hashes/_sha2.js +1 -1
- package/bundle/packages/rln/node_modules/@noble/hashes/hmac.js +1 -1
- package/bundle/packages/rln/node_modules/@noble/hashes/pbkdf2.js +1 -1
- package/bundle/packages/rln/node_modules/@noble/hashes/scrypt.js +1 -1
- package/bundle/packages/rln/node_modules/@noble/hashes/sha256.js +1 -1
- package/bundle/packages/rln/node_modules/@noble/hashes/sha512.js +1 -1
- package/bundle/packages/rln/node_modules/@noble/hashes/utils.js +1 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/contract/constants.d.ts +1 -1
- package/dist/contract/constants.js +1 -1
- package/dist/contract/constants.js.map +1 -1
- package/dist/contract/rln_base_contract.d.ts +30 -22
- package/dist/contract/rln_base_contract.js +246 -116
- package/dist/contract/rln_base_contract.js.map +1 -1
- package/dist/contract/rln_contract.d.ts +4 -24
- package/dist/contract/rln_contract.js +74 -89
- package/dist/contract/rln_contract.js.map +1 -1
- package/dist/credentials_manager.js +2 -2
- package/dist/credentials_manager.js.map +1 -1
- package/dist/identity.d.ts +0 -1
- package/dist/identity.js +0 -8
- package/dist/identity.js.map +1 -1
- package/dist/keystore/keystore.d.ts +1 -0
- package/dist/keystore/keystore.js +28 -19
- package/dist/keystore/keystore.js.map +1 -1
- package/dist/keystore/types.d.ts +1 -1
- package/package.json +1 -1
- package/src/contract/constants.ts +1 -1
- package/src/contract/rln_base_contract.ts +392 -185
- package/src/contract/rln_contract.ts +95 -120
- package/src/credentials_manager.ts +2 -2
- package/src/identity.ts +0 -9
- package/src/keystore/keystore.ts +46 -31
- package/src/keystore/types.ts +1 -1
- package/bundle/packages/rln/dist/contract/errors.js +0 -62
- package/dist/contract/errors.d.ts +0 -30
- package/dist/contract/errors.js +0 -61
- package/dist/contract/errors.js.map +0 -1
- 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
|
-
|
11
|
-
|
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,9 +20,28 @@ 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) {
|
36
|
+
// Initialize members and subscriptions
|
37
|
+
this.fetchMembers()
|
38
|
+
.then(() => {
|
39
|
+
this.subscribeToMembers();
|
40
|
+
})
|
41
|
+
.catch((error) => {
|
42
|
+
log.error("Failed to initialize members", { error });
|
43
|
+
});
|
44
|
+
|
33
45
|
const {
|
34
46
|
address,
|
35
47
|
signer,
|
@@ -38,8 +50,14 @@ export class RLNBaseContract {
|
|
38
50
|
} = options;
|
39
51
|
|
40
52
|
this.validateRateLimit(rateLimit);
|
53
|
+
|
41
54
|
this.contract = contract || new ethers.Contract(address, RLN_ABI, signer);
|
42
55
|
this.rateLimit = rateLimit;
|
56
|
+
|
57
|
+
// Initialize event filters
|
58
|
+
this._membersFilter = this.contract.filters.MembershipRegistered();
|
59
|
+
this._membershipErasedFilter = this.contract.filters.MembershipErased();
|
60
|
+
this._membersExpiredFilter = this.contract.filters.MembershipExpired();
|
43
61
|
}
|
44
62
|
|
45
63
|
/**
|
@@ -120,85 +138,266 @@ export class RLNBaseContract {
|
|
120
138
|
this.rateLimit = newRateLimit;
|
121
139
|
}
|
122
140
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
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
|
-
);
|
141
|
+
public get members(): Member[] {
|
142
|
+
const sortedMembers = Array.from(this._members.values()).sort(
|
143
|
+
(left, right) => left.index.toNumber() - right.index.toNumber()
|
144
|
+
);
|
145
|
+
return sortedMembers;
|
146
|
+
}
|
139
147
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
148
|
+
public async fetchMembers(options: FetchMembersOptions = {}): Promise<void> {
|
149
|
+
const registeredMemberEvents = await RLNBaseContract.queryFilter(
|
150
|
+
this.contract,
|
151
|
+
{
|
152
|
+
fromBlock: this.deployBlock,
|
153
|
+
...options,
|
154
|
+
membersFilter: this.membersFilter
|
155
|
+
}
|
156
|
+
);
|
157
|
+
const removedMemberEvents = await RLNBaseContract.queryFilter(
|
158
|
+
this.contract,
|
159
|
+
{
|
160
|
+
fromBlock: this.deployBlock,
|
161
|
+
...options,
|
162
|
+
membersFilter: this.membershipErasedFilter
|
163
|
+
}
|
164
|
+
);
|
165
|
+
const expiredMemberEvents = await RLNBaseContract.queryFilter(
|
166
|
+
this.contract,
|
167
|
+
{
|
168
|
+
fromBlock: this.deployBlock,
|
169
|
+
...options,
|
170
|
+
membersFilter: this.membersExpiredFilter
|
171
|
+
}
|
172
|
+
);
|
173
|
+
|
174
|
+
const events = [
|
175
|
+
...registeredMemberEvents,
|
176
|
+
...removedMemberEvents,
|
177
|
+
...expiredMemberEvents
|
178
|
+
];
|
179
|
+
this.processEvents(events);
|
180
|
+
}
|
181
|
+
|
182
|
+
public static async queryFilter(
|
183
|
+
contract: ethers.Contract,
|
184
|
+
options: CustomQueryOptions
|
185
|
+
): Promise<ethers.Event[]> {
|
186
|
+
const FETCH_CHUNK = 5;
|
187
|
+
const BLOCK_RANGE = 3000;
|
188
|
+
|
189
|
+
const {
|
190
|
+
fromBlock,
|
191
|
+
membersFilter,
|
192
|
+
fetchRange = BLOCK_RANGE,
|
193
|
+
fetchChunks = FETCH_CHUNK
|
194
|
+
} = options;
|
195
|
+
|
196
|
+
if (fromBlock === undefined) {
|
197
|
+
return contract.queryFilter(membersFilter);
|
198
|
+
}
|
199
|
+
|
200
|
+
if (!contract.provider) {
|
201
|
+
throw Error("No provider found on the contract.");
|
202
|
+
}
|
151
203
|
|
152
|
-
|
153
|
-
|
154
|
-
|
204
|
+
const toBlock = await contract.provider.getBlockNumber();
|
205
|
+
|
206
|
+
if (toBlock - fromBlock < fetchRange) {
|
207
|
+
return contract.queryFilter(membersFilter, fromBlock, toBlock);
|
208
|
+
}
|
209
|
+
|
210
|
+
const events: ethers.Event[][] = [];
|
211
|
+
const chunks = RLNBaseContract.splitToChunks(
|
212
|
+
fromBlock,
|
213
|
+
toBlock,
|
214
|
+
fetchRange
|
215
|
+
);
|
216
|
+
|
217
|
+
for (const portion of RLNBaseContract.takeN<[number, number]>(
|
218
|
+
chunks,
|
219
|
+
fetchChunks
|
220
|
+
)) {
|
221
|
+
const promises = portion.map(([left, right]) =>
|
222
|
+
RLNBaseContract.ignoreErrors(
|
223
|
+
contract.queryFilter(membersFilter, left, right),
|
224
|
+
[]
|
225
|
+
)
|
155
226
|
);
|
156
|
-
|
157
|
-
|
227
|
+
const fetchedEvents = await Promise.all(promises);
|
228
|
+
events.push(fetchedEvents.flatMap((v) => v));
|
229
|
+
}
|
230
|
+
|
231
|
+
return events.flatMap((v) => v);
|
232
|
+
}
|
233
|
+
|
234
|
+
public processEvents(events: ethers.Event[]): void {
|
235
|
+
const toRemoveTable = new Map<number, number[]>();
|
236
|
+
const toInsertTable = new Map<number, ethers.Event[]>();
|
237
|
+
|
238
|
+
events.forEach((evt) => {
|
239
|
+
if (!evt.args) {
|
240
|
+
return;
|
241
|
+
}
|
242
|
+
|
158
243
|
if (
|
159
|
-
|
160
|
-
|
244
|
+
evt.event === "MembershipErased" ||
|
245
|
+
evt.event === "MembershipExpired"
|
161
246
|
) {
|
162
|
-
|
163
|
-
|
164
|
-
)
|
247
|
+
let index = evt.args.index;
|
248
|
+
|
249
|
+
if (!index) {
|
250
|
+
return;
|
251
|
+
}
|
252
|
+
|
253
|
+
if (typeof index === "number" || typeof index === "string") {
|
254
|
+
index = ethers.BigNumber.from(index);
|
255
|
+
}
|
256
|
+
|
257
|
+
const toRemoveVal = toRemoveTable.get(evt.blockNumber);
|
258
|
+
if (toRemoveVal != undefined) {
|
259
|
+
toRemoveVal.push(index.toNumber());
|
260
|
+
toRemoveTable.set(evt.blockNumber, toRemoveVal);
|
261
|
+
} else {
|
262
|
+
toRemoveTable.set(evt.blockNumber, [index.toNumber()]);
|
263
|
+
}
|
264
|
+
} else if (evt.event === "MembershipRegistered") {
|
265
|
+
let eventsPerBlock = toInsertTable.get(evt.blockNumber);
|
266
|
+
if (eventsPerBlock == undefined) {
|
267
|
+
eventsPerBlock = [];
|
268
|
+
}
|
269
|
+
|
270
|
+
eventsPerBlock.push(evt);
|
271
|
+
toInsertTable.set(evt.blockNumber, eventsPerBlock);
|
165
272
|
}
|
166
|
-
|
273
|
+
});
|
274
|
+
}
|
275
|
+
|
276
|
+
public static splitToChunks(
|
277
|
+
from: number,
|
278
|
+
to: number,
|
279
|
+
step: number
|
280
|
+
): Array<[number, number]> {
|
281
|
+
const chunks: Array<[number, number]> = [];
|
282
|
+
|
283
|
+
let left = from;
|
284
|
+
while (left < to) {
|
285
|
+
const right = left + step < to ? left + step : to;
|
286
|
+
|
287
|
+
chunks.push([left, right] as [number, number]);
|
288
|
+
|
289
|
+
left = right;
|
167
290
|
}
|
291
|
+
|
292
|
+
return chunks;
|
168
293
|
}
|
169
294
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
295
|
+
public static *takeN<T>(array: T[], size: number): Iterable<T[]> {
|
296
|
+
let start = 0;
|
297
|
+
|
298
|
+
while (start < array.length) {
|
299
|
+
const portion = array.slice(start, start + size);
|
300
|
+
|
301
|
+
yield portion;
|
302
|
+
|
303
|
+
start += size;
|
304
|
+
}
|
305
|
+
}
|
306
|
+
|
307
|
+
public static async ignoreErrors<T>(
|
308
|
+
promise: Promise<T>,
|
309
|
+
defaultValue: T
|
310
|
+
): Promise<T> {
|
311
|
+
try {
|
312
|
+
return await promise;
|
313
|
+
} catch (err: unknown) {
|
314
|
+
if (err instanceof Error) {
|
315
|
+
log.info(`Ignoring an error during query: ${err.message}`);
|
316
|
+
} else {
|
317
|
+
log.info(`Ignoring an unknown error during query`);
|
318
|
+
}
|
319
|
+
return defaultValue;
|
320
|
+
}
|
321
|
+
}
|
322
|
+
|
323
|
+
public subscribeToMembers(): void {
|
324
|
+
this.contract.on(
|
325
|
+
this.membersFilter,
|
326
|
+
(
|
327
|
+
_idCommitment: string,
|
328
|
+
_membershipRateLimit: ethers.BigNumber,
|
329
|
+
_index: ethers.BigNumber,
|
330
|
+
event: ethers.Event
|
331
|
+
) => {
|
332
|
+
this.processEvents([event]);
|
333
|
+
}
|
334
|
+
);
|
335
|
+
|
336
|
+
this.contract.on(
|
337
|
+
this.membershipErasedFilter,
|
338
|
+
(
|
339
|
+
_idCommitment: string,
|
340
|
+
_membershipRateLimit: ethers.BigNumber,
|
341
|
+
_index: ethers.BigNumber,
|
342
|
+
event: ethers.Event
|
343
|
+
) => {
|
344
|
+
this.processEvents([event]);
|
345
|
+
}
|
346
|
+
);
|
347
|
+
|
348
|
+
this.contract.on(
|
349
|
+
this.membersExpiredFilter,
|
350
|
+
(
|
351
|
+
_idCommitment: string,
|
352
|
+
_membershipRateLimit: ethers.BigNumber,
|
353
|
+
_index: ethers.BigNumber,
|
354
|
+
event: ethers.Event
|
355
|
+
) => {
|
356
|
+
this.processEvents([event]);
|
357
|
+
}
|
358
|
+
);
|
176
359
|
}
|
177
360
|
|
178
361
|
/**
|
179
|
-
*
|
180
|
-
*
|
362
|
+
* Helper method to get remaining messages in current epoch
|
363
|
+
* @param membershipId The ID of the membership to check
|
364
|
+
* @returns number of remaining messages allowed in current epoch
|
181
365
|
*/
|
182
|
-
|
183
|
-
idCommitment: string
|
184
|
-
): Promise<ethers.BigNumber | null> {
|
366
|
+
public async getRemainingMessages(membershipId: number): Promise<number> {
|
185
367
|
try {
|
186
|
-
const
|
187
|
-
|
188
|
-
return null;
|
189
|
-
}
|
368
|
+
const [startTime, , rateLimit] =
|
369
|
+
await this.contract.getMembershipInfo(membershipId);
|
190
370
|
|
191
|
-
|
192
|
-
|
371
|
+
// Calculate current epoch
|
372
|
+
const currentTime = Math.floor(Date.now() / 1000);
|
373
|
+
const epochsPassed = Math.floor(
|
374
|
+
(currentTime - startTime) / RATE_LIMIT_PARAMS.EPOCH_LENGTH
|
375
|
+
);
|
376
|
+
const currentEpochStart =
|
377
|
+
startTime + epochsPassed * RATE_LIMIT_PARAMS.EPOCH_LENGTH;
|
378
|
+
|
379
|
+
// Get message count in current epoch using contract's function
|
380
|
+
const messageCount = await this.contract.getMessageCount(
|
381
|
+
membershipId,
|
382
|
+
currentEpochStart
|
383
|
+
);
|
384
|
+
return Math.max(
|
385
|
+
0,
|
386
|
+
ethers.BigNumber.from(rateLimit)
|
387
|
+
.sub(ethers.BigNumber.from(messageCount))
|
388
|
+
.toNumber()
|
389
|
+
);
|
193
390
|
} catch (error) {
|
194
|
-
log.error(
|
195
|
-
|
391
|
+
log.error(
|
392
|
+
`Error getting remaining messages: ${(error as Error).message}`
|
393
|
+
);
|
394
|
+
return 0; // Fail safe: assume no messages remaining on error
|
196
395
|
}
|
197
396
|
}
|
198
397
|
|
199
398
|
public async getMembershipInfo(
|
200
399
|
idCommitment: string
|
201
|
-
): Promise<MembershipInfo> {
|
400
|
+
): Promise<MembershipInfo | undefined> {
|
202
401
|
try {
|
203
402
|
const [startBlock, endBlock, rateLimit] =
|
204
403
|
await this.contract.getMembershipInfo(idCommitment);
|
@@ -214,9 +413,7 @@ export class RLNBaseContract {
|
|
214
413
|
}
|
215
414
|
|
216
415
|
const index = await this.getMemberIndex(idCommitment);
|
217
|
-
if (index
|
218
|
-
throw new MembershipNotFoundError(idCommitment);
|
219
|
-
}
|
416
|
+
if (!index) return undefined;
|
220
417
|
|
221
418
|
return {
|
222
419
|
index,
|
@@ -227,40 +424,30 @@ export class RLNBaseContract {
|
|
227
424
|
state
|
228
425
|
};
|
229
426
|
} catch (error) {
|
230
|
-
|
231
|
-
throw error;
|
232
|
-
}
|
233
|
-
log.error(`Error getting membership info: ${(error as Error).message}`);
|
234
|
-
throw new InvalidMembershipError(idCommitment);
|
427
|
+
return undefined;
|
235
428
|
}
|
236
429
|
}
|
237
430
|
|
238
|
-
public async extendMembership(
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
endBlock: event.args!.endBlock
|
243
|
-
}));
|
431
|
+
public async extendMembership(
|
432
|
+
idCommitment: string
|
433
|
+
): Promise<ethers.ContractTransaction> {
|
434
|
+
return this.contract.extendMemberships([idCommitment]);
|
244
435
|
}
|
245
436
|
|
246
437
|
public async eraseMembership(
|
247
438
|
idCommitment: string,
|
248
439
|
eraseFromMembershipSet: boolean = true
|
249
|
-
): Promise<
|
250
|
-
|
440
|
+
): Promise<ethers.ContractTransaction> {
|
441
|
+
return this.contract.eraseMemberships(
|
251
442
|
[idCommitment],
|
252
443
|
eraseFromMembershipSet
|
253
444
|
);
|
254
|
-
await this.confirmTransaction(tx, "MembershipErased", (event) => ({
|
255
|
-
idCommitment: event.args!.idCommitment,
|
256
|
-
index: event.args!.index
|
257
|
-
}));
|
258
445
|
}
|
259
446
|
|
260
447
|
public async registerMembership(
|
261
448
|
idCommitment: string,
|
262
449
|
rateLimit: number = DEFAULT_RATE_LIMIT
|
263
|
-
): Promise<
|
450
|
+
): Promise<ethers.ContractTransaction> {
|
264
451
|
if (
|
265
452
|
rateLimit < RATE_LIMIT_PARAMS.MIN_RATE ||
|
266
453
|
rateLimit > RATE_LIMIT_PARAMS.MAX_RATE
|
@@ -269,25 +456,15 @@ export class RLNBaseContract {
|
|
269
456
|
`Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE}`
|
270
457
|
);
|
271
458
|
}
|
272
|
-
|
273
|
-
await this.confirmTransaction(tx, "MembershipRegistered", (event) => ({
|
274
|
-
idCommitment: event.args!.idCommitment,
|
275
|
-
membershipRateLimit: event.args!.membershipRateLimit,
|
276
|
-
index: event.args!.index
|
277
|
-
}));
|
459
|
+
return this.contract.register(idCommitment, rateLimit, []);
|
278
460
|
}
|
279
461
|
|
280
462
|
public async withdraw(token: string, holder: string): Promise<void> {
|
281
463
|
try {
|
282
464
|
const tx = await this.contract.withdraw(token, { from: holder });
|
283
|
-
await
|
284
|
-
token: event.args!.token,
|
285
|
-
holder: event.args!.holder,
|
286
|
-
amount: event.args!.amount
|
287
|
-
}));
|
465
|
+
await tx.wait();
|
288
466
|
} catch (error) {
|
289
467
|
log.error(`Error in withdraw: ${(error as Error).message}`);
|
290
|
-
throw error;
|
291
468
|
}
|
292
469
|
}
|
293
470
|
|
@@ -303,18 +480,18 @@ export class RLNBaseContract {
|
|
303
480
|
const existingIndex = await this.getMemberIndex(
|
304
481
|
identity.IDCommitmentBigInt.toString()
|
305
482
|
);
|
306
|
-
|
307
|
-
|
308
|
-
|
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
|
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
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
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();
|
513
|
+
|
514
|
+
if (txRegisterReceipt.status === 0) {
|
515
|
+
throw new Error("Transaction failed on-chain");
|
516
|
+
}
|
333
517
|
|
334
|
-
const
|
335
|
-
|
336
|
-
"MembershipRegistered",
|
337
|
-
(event): MembershipRegisteredEvent => ({
|
338
|
-
idCommitment: event.args!.idCommitment,
|
339
|
-
membershipRateLimit: event.args!.membershipRateLimit,
|
340
|
-
index: event.args!.index
|
341
|
-
})
|
518
|
+
const memberRegistered = txRegisterReceipt.events?.find(
|
519
|
+
(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
|
542
|
+
const membershipId = Number(decodedData.index);
|
352
543
|
|
353
544
|
return {
|
354
545
|
identity,
|
355
546
|
membership: {
|
356
547
|
address,
|
357
|
-
treeIndex:
|
358
|
-
chainId: network.chainId
|
548
|
+
treeIndex: membershipId,
|
549
|
+
chainId: network.chainId,
|
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
|
-
//
|
559
|
+
// Try to extract more specific error information
|
373
560
|
if (errorMessage.includes("CannotExceedMaxTotalRateLimit")) {
|
374
|
-
throw new
|
375
|
-
|
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
|
380
|
-
identity.IDCommitmentBigInt.toString()
|
381
|
-
);
|
565
|
+
throw new Error("Registration failed: Invalid ID commitment");
|
382
566
|
} else if (errorMessage.includes("InvalidMembershipRateLimit")) {
|
383
|
-
throw new
|
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
|
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
|
395
|
-
|
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
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
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
|
431
|
-
|
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) => 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,14 +633,14 @@ 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
|
636
|
+
const membershipId = Number(decodedData.index);
|
448
637
|
|
449
638
|
return {
|
450
639
|
identity,
|
451
640
|
membership: {
|
452
641
|
address,
|
453
|
-
treeIndex:
|
454
|
-
chainId: network.chainId
|
642
|
+
treeIndex: membershipId,
|
643
|
+
chainId: network.chainId,
|
455
644
|
rateLimit: decodedData.membershipRateLimit.toNumber()
|
456
645
|
}
|
457
646
|
};
|
@@ -459,7 +648,7 @@ export class RLNBaseContract {
|
|
459
648
|
log.error(
|
460
649
|
`Error in registerWithPermitAndErase: ${(error as Error).message}`
|
461
650
|
);
|
462
|
-
|
651
|
+
return undefined;
|
463
652
|
}
|
464
653
|
}
|
465
654
|
|
@@ -472,29 +661,47 @@ export class RLNBaseContract {
|
|
472
661
|
rateLimit < RATE_LIMIT_PARAMS.MIN_RATE ||
|
473
662
|
rateLimit > RATE_LIMIT_PARAMS.MAX_RATE
|
474
663
|
) {
|
475
|
-
throw new
|
476
|
-
|
477
|
-
RATE_LIMIT_PARAMS.MIN_RATE,
|
478
|
-
RATE_LIMIT_PARAMS.MAX_RATE
|
664
|
+
throw new Error(
|
665
|
+
`Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE} messages per epoch`
|
479
666
|
);
|
480
667
|
}
|
481
668
|
}
|
482
669
|
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
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);
|
670
|
+
private get membersFilter(): ethers.EventFilter {
|
671
|
+
if (!this._membersFilter) {
|
672
|
+
throw Error("Members filter was not initialized.");
|
673
|
+
}
|
674
|
+
return this._membersFilter;
|
675
|
+
}
|
493
676
|
|
494
|
-
|
495
|
-
|
677
|
+
private get membershipErasedFilter(): ethers.EventFilter {
|
678
|
+
if (!this._membershipErasedFilter) {
|
679
|
+
throw Error("MembershipErased filter was not initialized.");
|
496
680
|
}
|
681
|
+
return this._membershipErasedFilter;
|
682
|
+
}
|
497
683
|
|
498
|
-
|
684
|
+
private get membersExpiredFilter(): ethers.EventFilter {
|
685
|
+
if (!this._membersExpiredFilter) {
|
686
|
+
throw Error("MembersExpired filter was not initialized.");
|
687
|
+
}
|
688
|
+
return this._membersExpiredFilter;
|
689
|
+
}
|
690
|
+
|
691
|
+
private async getMemberIndex(
|
692
|
+
idCommitment: string
|
693
|
+
): Promise<ethers.BigNumber | undefined> {
|
694
|
+
try {
|
695
|
+
const events = await this.contract.queryFilter(
|
696
|
+
this.contract.filters.MembershipRegistered(idCommitment)
|
697
|
+
);
|
698
|
+
if (events.length === 0) return undefined;
|
699
|
+
|
700
|
+
// Get the most recent registration event
|
701
|
+
const event = events[events.length - 1];
|
702
|
+
return event.args?.index;
|
703
|
+
} catch (error) {
|
704
|
+
return undefined;
|
705
|
+
}
|
499
706
|
}
|
500
707
|
}
|