@waku/rln 0.1.5-f39d215.0 → 0.1.5-fdaf129.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/index.js +4 -3
- package/bundle/packages/rln/dist/contract/constants.js +2 -1
- package/bundle/packages/rln/dist/contract/{rln_light_contract.js → rln_base_contract.js} +185 -184
- package/bundle/packages/rln/dist/contract/rln_contract.js +9 -419
- package/bundle/packages/rln/dist/contract/types.js +9 -0
- package/bundle/packages/rln/dist/create.js +1 -1
- package/bundle/packages/rln/dist/{rln_light.js → credentials_manager.js} +114 -48
- package/bundle/packages/rln/dist/identity.js +0 -9
- package/bundle/packages/rln/dist/keystore/keystore.js +27 -13
- package/bundle/packages/rln/dist/rln.js +56 -166
- package/bundle/packages/rln/dist/zerokit.js +5 -5
- 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/index.d.ts +1 -0
- package/dist/contract/index.js +1 -0
- package/dist/contract/index.js.map +1 -1
- package/dist/contract/{rln_light_contract.d.ts → rln_base_contract.d.ts} +24 -58
- package/dist/contract/{rln_light_contract.js → rln_base_contract.js} +185 -184
- package/dist/contract/rln_base_contract.js.map +1 -0
- package/dist/contract/rln_contract.d.ts +5 -122
- package/dist/contract/rln_contract.js +8 -417
- package/dist/contract/rln_contract.js.map +1 -1
- package/dist/contract/types.d.ts +45 -0
- package/dist/contract/types.js +8 -0
- package/dist/contract/types.js.map +1 -0
- package/dist/create.js +1 -1
- package/dist/create.js.map +1 -1
- package/dist/credentials_manager.d.ts +44 -0
- package/dist/credentials_manager.js +197 -0
- package/dist/credentials_manager.js.map +1 -0
- package/dist/identity.d.ts +0 -1
- package/dist/identity.js +0 -9
- package/dist/identity.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/keystore/keystore.d.ts +1 -0
- package/dist/keystore/keystore.js +27 -13
- package/dist/keystore/keystore.js.map +1 -1
- package/dist/keystore/types.d.ts +2 -2
- package/dist/rln.d.ts +9 -52
- package/dist/rln.js +54 -163
- package/dist/rln.js.map +1 -1
- package/dist/types.d.ts +27 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/zerokit.d.ts +3 -3
- package/dist/zerokit.js +5 -5
- package/dist/zerokit.js.map +1 -1
- package/package.json +1 -1
- package/src/contract/constants.ts +1 -1
- package/src/contract/index.ts +1 -0
- package/src/contract/{rln_light_contract.ts → rln_base_contract.ts} +303 -321
- package/src/contract/rln_contract.ts +9 -663
- package/src/contract/types.ts +53 -0
- package/src/create.ts +1 -1
- package/src/credentials_manager.ts +282 -0
- package/src/identity.ts +0 -10
- package/src/index.ts +7 -5
- package/src/keystore/keystore.ts +53 -27
- package/src/keystore/types.ts +2 -2
- package/src/rln.ts +67 -258
- package/src/types.ts +31 -0
- package/src/zerokit.ts +3 -3
- package/dist/contract/rln_light_contract.js.map +0 -1
- package/dist/rln_light.d.ts +0 -64
- package/dist/rln_light.js +0 -144
- package/dist/rln_light.js.map +0 -1
- package/src/rln_light.ts +0 -235
@@ -1,84 +1,39 @@
|
|
1
|
+
/* eslint-disable no-console */
|
1
2
|
import { Logger } from "@waku/utils";
|
2
3
|
import { ethers } from "ethers";
|
3
4
|
|
4
|
-
import
|
5
|
-
import
|
5
|
+
import { IdentityCredential } from "../identity.js";
|
6
|
+
import { DecryptedCredentials } from "../keystore/types.js";
|
6
7
|
|
7
8
|
import { RLN_ABI } from "./abi.js";
|
8
9
|
import { DEFAULT_RATE_LIMIT, RATE_LIMIT_PARAMS } from "./constants.js";
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
interface RLNContractInitOptions extends RLNContractOptions {
|
24
|
-
contract?: ethers.Contract;
|
25
|
-
}
|
26
|
-
|
27
|
-
export interface MembershipRegisteredEvent {
|
28
|
-
idCommitment: string;
|
29
|
-
membershipRateLimit: ethers.BigNumber;
|
30
|
-
index: ethers.BigNumber;
|
31
|
-
}
|
32
|
-
|
33
|
-
type FetchMembersOptions = {
|
34
|
-
fromBlock?: number;
|
35
|
-
fetchRange?: number;
|
36
|
-
fetchChunks?: number;
|
37
|
-
};
|
38
|
-
|
39
|
-
export interface MembershipInfo {
|
40
|
-
index: ethers.BigNumber;
|
41
|
-
idCommitment: string;
|
42
|
-
rateLimit: number;
|
43
|
-
startBlock: number;
|
44
|
-
endBlock: number;
|
45
|
-
state: MembershipState;
|
46
|
-
}
|
47
|
-
|
48
|
-
export enum MembershipState {
|
49
|
-
Active = "Active",
|
50
|
-
GracePeriod = "GracePeriod",
|
51
|
-
Expired = "Expired",
|
52
|
-
ErasedAwaitsWithdrawal = "ErasedAwaitsWithdrawal"
|
53
|
-
}
|
54
|
-
|
55
|
-
export class RLNLightContract {
|
10
|
+
import {
|
11
|
+
CustomQueryOptions,
|
12
|
+
FetchMembersOptions,
|
13
|
+
Member,
|
14
|
+
MembershipInfo,
|
15
|
+
MembershipRegisteredEvent,
|
16
|
+
MembershipState,
|
17
|
+
RLNContractInitOptions
|
18
|
+
} from "./types.js";
|
19
|
+
|
20
|
+
const log = new Logger("waku:rln:contract:base");
|
21
|
+
|
22
|
+
export class RLNBaseContract {
|
56
23
|
public contract: ethers.Contract;
|
57
|
-
|
58
24
|
private deployBlock: undefined | number;
|
59
25
|
private rateLimit: number;
|
60
26
|
|
61
|
-
|
27
|
+
protected _members: Map<number, Member> = new Map();
|
62
28
|
private _membersFilter: ethers.EventFilter;
|
63
29
|
private _membershipErasedFilter: ethers.EventFilter;
|
64
30
|
private _membersExpiredFilter: ethers.EventFilter;
|
65
31
|
|
66
32
|
/**
|
67
|
-
*
|
33
|
+
* Constructor for RLNBaseContract.
|
68
34
|
* Allows injecting a mocked contract for testing purposes.
|
69
35
|
*/
|
70
|
-
public
|
71
|
-
options: RLNContractInitOptions
|
72
|
-
): Promise<RLNLightContract> {
|
73
|
-
const rlnContract = new RLNLightContract(options);
|
74
|
-
|
75
|
-
await rlnContract.fetchMembers();
|
76
|
-
rlnContract.subscribeToMembers();
|
77
|
-
|
78
|
-
return rlnContract;
|
79
|
-
}
|
80
|
-
|
81
|
-
private constructor(options: RLNContractInitOptions) {
|
36
|
+
public constructor(options: RLNContractInitOptions) {
|
82
37
|
const {
|
83
38
|
address,
|
84
39
|
signer,
|
@@ -86,24 +41,38 @@ export class RLNLightContract {
|
|
86
41
|
contract
|
87
42
|
} = options;
|
88
43
|
|
89
|
-
|
90
|
-
rateLimit < RATE_LIMIT_PARAMS.MIN_RATE ||
|
91
|
-
rateLimit > RATE_LIMIT_PARAMS.MAX_RATE
|
92
|
-
) {
|
93
|
-
throw new Error(
|
94
|
-
`Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE} messages per epoch`
|
95
|
-
);
|
96
|
-
}
|
44
|
+
log.info("Initializing RLNBaseContract", { address, rateLimit });
|
97
45
|
|
46
|
+
this.contract = contract || new ethers.Contract(address, RLN_ABI, signer);
|
98
47
|
this.rateLimit = rateLimit;
|
99
48
|
|
100
|
-
|
101
|
-
|
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
|
+
}
|
102
62
|
|
103
|
-
// Initialize
|
104
|
-
this.
|
105
|
-
|
106
|
-
|
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
|
+
});
|
107
76
|
}
|
108
77
|
|
109
78
|
/**
|
@@ -151,7 +120,7 @@ export class RLNLightContract {
|
|
151
120
|
*/
|
152
121
|
public async getMaxTotalRateLimit(): Promise<number> {
|
153
122
|
const maxTotalRate = await this.contract.maxTotalRateLimit();
|
154
|
-
return
|
123
|
+
return maxTotalRate.toNumber();
|
155
124
|
}
|
156
125
|
|
157
126
|
/**
|
@@ -160,7 +129,7 @@ export class RLNLightContract {
|
|
160
129
|
*/
|
161
130
|
public async getCurrentTotalRateLimit(): Promise<number> {
|
162
131
|
const currentTotal = await this.contract.currentTotalRateLimit();
|
163
|
-
return
|
132
|
+
return currentTotal.toNumber();
|
164
133
|
}
|
165
134
|
|
166
135
|
/**
|
@@ -172,9 +141,7 @@ export class RLNLightContract {
|
|
172
141
|
this.contract.maxTotalRateLimit(),
|
173
142
|
this.contract.currentTotalRateLimit()
|
174
143
|
]);
|
175
|
-
return
|
176
|
-
.sub(ethers.BigNumber.from(currentTotal))
|
177
|
-
.toNumber();
|
144
|
+
return Number(maxTotal) - Number(currentTotal);
|
178
145
|
}
|
179
146
|
|
180
147
|
/**
|
@@ -182,6 +149,7 @@ export class RLNLightContract {
|
|
182
149
|
* @param newRateLimit The new rate limit to use
|
183
150
|
*/
|
184
151
|
public async setRateLimit(newRateLimit: number): Promise<void> {
|
152
|
+
await this.validateRateLimit(newRateLimit);
|
185
153
|
this.rateLimit = newRateLimit;
|
186
154
|
}
|
187
155
|
|
@@ -192,43 +160,31 @@ export class RLNLightContract {
|
|
192
160
|
return sortedMembers;
|
193
161
|
}
|
194
162
|
|
195
|
-
private get membersFilter(): ethers.EventFilter {
|
196
|
-
if (!this._membersFilter) {
|
197
|
-
throw Error("Members filter was not initialized.");
|
198
|
-
}
|
199
|
-
return this._membersFilter;
|
200
|
-
}
|
201
|
-
|
202
|
-
private get membershipErasedFilter(): ethers.EventFilter {
|
203
|
-
if (!this._membershipErasedFilter) {
|
204
|
-
throw Error("MembershipErased filter was not initialized.");
|
205
|
-
}
|
206
|
-
return this._membershipErasedFilter;
|
207
|
-
}
|
208
|
-
|
209
|
-
private get membersExpiredFilter(): ethers.EventFilter {
|
210
|
-
if (!this._membersExpiredFilter) {
|
211
|
-
throw Error("MembersExpired filter was not initialized.");
|
212
|
-
}
|
213
|
-
return this._membersExpiredFilter;
|
214
|
-
}
|
215
|
-
|
216
163
|
public async fetchMembers(options: FetchMembersOptions = {}): Promise<void> {
|
217
|
-
const registeredMemberEvents = await queryFilter(
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
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
|
+
);
|
232
188
|
|
233
189
|
const events = [
|
234
190
|
...registeredMemberEvents,
|
@@ -238,6 +194,58 @@ export class RLNLightContract {
|
|
238
194
|
this.processEvents(events);
|
239
195
|
}
|
240
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
|
+
);
|
231
|
+
|
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
|
+
)
|
241
|
+
);
|
242
|
+
const fetchedEvents = await Promise.all(promises);
|
243
|
+
events.push(fetchedEvents.flatMap((v) => v));
|
244
|
+
}
|
245
|
+
|
246
|
+
return events.flatMap((v) => v);
|
247
|
+
}
|
248
|
+
|
241
249
|
public processEvents(events: ethers.Event[]): void {
|
242
250
|
const toRemoveTable = new Map<number, number[]>();
|
243
251
|
const toInsertTable = new Map<number, ethers.Event[]>();
|
@@ -280,11 +288,58 @@ export class RLNLightContract {
|
|
280
288
|
});
|
281
289
|
}
|
282
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;
|
305
|
+
}
|
306
|
+
|
307
|
+
return chunks;
|
308
|
+
}
|
309
|
+
|
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
|
+
}
|
320
|
+
}
|
321
|
+
|
322
|
+
public static async ignoreErrors<T>(
|
323
|
+
promise: Promise<T>,
|
324
|
+
defaultValue: T
|
325
|
+
): Promise<T> {
|
326
|
+
try {
|
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`);
|
333
|
+
}
|
334
|
+
return defaultValue;
|
335
|
+
}
|
336
|
+
}
|
337
|
+
|
283
338
|
public subscribeToMembers(): void {
|
284
339
|
this.contract.on(
|
285
340
|
this.membersFilter,
|
286
341
|
(
|
287
|
-
_idCommitment:
|
342
|
+
_idCommitment: bigint,
|
288
343
|
_membershipRateLimit: ethers.BigNumber,
|
289
344
|
_index: ethers.BigNumber,
|
290
345
|
event: ethers.Event
|
@@ -296,7 +351,7 @@ export class RLNLightContract {
|
|
296
351
|
this.contract.on(
|
297
352
|
this.membershipErasedFilter,
|
298
353
|
(
|
299
|
-
_idCommitment:
|
354
|
+
_idCommitment: bigint,
|
300
355
|
_membershipRateLimit: ethers.BigNumber,
|
301
356
|
_index: ethers.BigNumber,
|
302
357
|
event: ethers.Event
|
@@ -308,7 +363,7 @@ export class RLNLightContract {
|
|
308
363
|
this.contract.on(
|
309
364
|
this.membersExpiredFilter,
|
310
365
|
(
|
311
|
-
_idCommitment:
|
366
|
+
_idCommitment: bigint,
|
312
367
|
_membershipRateLimit: ethers.BigNumber,
|
313
368
|
_index: ethers.BigNumber,
|
314
369
|
event: ethers.Event
|
@@ -318,6 +373,96 @@ export class RLNLightContract {
|
|
318
373
|
);
|
319
374
|
}
|
320
375
|
|
376
|
+
public async getMembershipInfo(
|
377
|
+
idCommitmentBigInt: bigint
|
378
|
+
): Promise<MembershipInfo | undefined> {
|
379
|
+
try {
|
380
|
+
const membershipData =
|
381
|
+
await this.contract.memberships(idCommitmentBigInt);
|
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);
|
397
|
+
|
398
|
+
let state: MembershipState;
|
399
|
+
if (currentBlock < gracePeriodStartTimestamp.toNumber()) {
|
400
|
+
state = MembershipState.Active;
|
401
|
+
} else if (currentBlock < gracePeriodEnd.toNumber()) {
|
402
|
+
state = MembershipState.GracePeriod;
|
403
|
+
} else {
|
404
|
+
state = MembershipState.Expired;
|
405
|
+
}
|
406
|
+
|
407
|
+
return {
|
408
|
+
index,
|
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
|
419
|
+
};
|
420
|
+
} catch (error) {
|
421
|
+
console.error("Error in getMembershipInfo:", error);
|
422
|
+
return undefined;
|
423
|
+
}
|
424
|
+
}
|
425
|
+
|
426
|
+
public async extendMembership(
|
427
|
+
idCommitmentBigInt: bigint
|
428
|
+
): Promise<ethers.ContractTransaction> {
|
429
|
+
return this.contract.extendMemberships([idCommitmentBigInt]);
|
430
|
+
}
|
431
|
+
|
432
|
+
public async eraseMembership(
|
433
|
+
idCommitmentBigInt: bigint,
|
434
|
+
eraseFromMembershipSet: boolean = true
|
435
|
+
): Promise<ethers.ContractTransaction> {
|
436
|
+
return this.contract.eraseMemberships(
|
437
|
+
[idCommitmentBigInt],
|
438
|
+
eraseFromMembershipSet
|
439
|
+
);
|
440
|
+
}
|
441
|
+
|
442
|
+
public async registerMembership(
|
443
|
+
idCommitmentBigInt: bigint,
|
444
|
+
rateLimit: number = DEFAULT_RATE_LIMIT
|
445
|
+
): Promise<ethers.ContractTransaction> {
|
446
|
+
if (
|
447
|
+
rateLimit < RATE_LIMIT_PARAMS.MIN_RATE ||
|
448
|
+
rateLimit > RATE_LIMIT_PARAMS.MAX_RATE
|
449
|
+
) {
|
450
|
+
throw new Error(
|
451
|
+
`Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE}`
|
452
|
+
);
|
453
|
+
}
|
454
|
+
return this.contract.register(idCommitmentBigInt, rateLimit, []);
|
455
|
+
}
|
456
|
+
|
457
|
+
public async withdraw(token: string, from: string): Promise<void> {
|
458
|
+
try {
|
459
|
+
const tx = await this.contract.withdraw(token, from);
|
460
|
+
await tx.wait();
|
461
|
+
} catch (error) {
|
462
|
+
log.error(`Error in withdraw: ${(error as Error).message}`);
|
463
|
+
}
|
464
|
+
}
|
465
|
+
|
321
466
|
public async registerWithIdentity(
|
322
467
|
identity: IdentityCredential
|
323
468
|
): Promise<DecryptedCredentials | undefined> {
|
@@ -328,7 +473,7 @@ export class RLNLightContract {
|
|
328
473
|
|
329
474
|
// Check if the ID commitment is already registered
|
330
475
|
const existingIndex = await this.getMemberIndex(
|
331
|
-
identity.IDCommitmentBigInt
|
476
|
+
identity.IDCommitmentBigInt
|
332
477
|
);
|
333
478
|
if (existingIndex) {
|
334
479
|
throw new Error(
|
@@ -389,13 +534,13 @@ export class RLNLightContract {
|
|
389
534
|
|
390
535
|
const network = await this.contract.provider.getNetwork();
|
391
536
|
const address = this.contract.address;
|
392
|
-
const membershipId = decodedData.index
|
537
|
+
const membershipId = Number(decodedData.index);
|
393
538
|
|
394
539
|
return {
|
395
540
|
identity,
|
396
541
|
membership: {
|
397
542
|
address,
|
398
|
-
treeIndex:
|
543
|
+
treeIndex: membershipId,
|
399
544
|
chainId: network.chainId.toString(),
|
400
545
|
rateLimit: decodedData.membershipRateLimit.toNumber()
|
401
546
|
}
|
@@ -430,43 +575,6 @@ export class RLNLightContract {
|
|
430
575
|
}
|
431
576
|
}
|
432
577
|
|
433
|
-
/**
|
434
|
-
* Helper method to get remaining messages in current epoch
|
435
|
-
* @param membershipId The ID of the membership to check
|
436
|
-
* @returns number of remaining messages allowed in current epoch
|
437
|
-
*/
|
438
|
-
public async getRemainingMessages(membershipId: number): Promise<number> {
|
439
|
-
try {
|
440
|
-
const [startTime, , rateLimit] =
|
441
|
-
await this.contract.getMembershipInfo(membershipId);
|
442
|
-
|
443
|
-
// Calculate current epoch
|
444
|
-
const currentTime = Math.floor(Date.now() / 1000);
|
445
|
-
const epochsPassed = Math.floor(
|
446
|
-
(currentTime - startTime) / RATE_LIMIT_PARAMS.EPOCH_LENGTH
|
447
|
-
);
|
448
|
-
const currentEpochStart =
|
449
|
-
startTime + epochsPassed * RATE_LIMIT_PARAMS.EPOCH_LENGTH;
|
450
|
-
|
451
|
-
// Get message count in current epoch using contract's function
|
452
|
-
const messageCount = await this.contract.getMessageCount(
|
453
|
-
membershipId,
|
454
|
-
currentEpochStart
|
455
|
-
);
|
456
|
-
return Math.max(
|
457
|
-
0,
|
458
|
-
ethers.BigNumber.from(rateLimit)
|
459
|
-
.sub(ethers.BigNumber.from(messageCount))
|
460
|
-
.toNumber()
|
461
|
-
);
|
462
|
-
} catch (error) {
|
463
|
-
log.error(
|
464
|
-
`Error getting remaining messages: ${(error as Error).message}`
|
465
|
-
);
|
466
|
-
return 0; // Fail safe: assume no messages remaining on error
|
467
|
-
}
|
468
|
-
}
|
469
|
-
|
470
578
|
public async registerWithPermitAndErase(
|
471
579
|
identity: IdentityCredential,
|
472
580
|
permit: {
|
@@ -520,13 +628,13 @@ export class RLNLightContract {
|
|
520
628
|
|
521
629
|
const network = await this.contract.provider.getNetwork();
|
522
630
|
const address = this.contract.address;
|
523
|
-
const membershipId = decodedData.index
|
631
|
+
const membershipId = Number(decodedData.index);
|
524
632
|
|
525
633
|
return {
|
526
634
|
identity,
|
527
635
|
membership: {
|
528
636
|
address,
|
529
|
-
treeIndex:
|
637
|
+
treeIndex: membershipId,
|
530
638
|
chainId: network.chainId.toString(),
|
531
639
|
rateLimit: decodedData.membershipRateLimit.toNumber()
|
532
640
|
}
|
@@ -539,85 +647,53 @@ export class RLNLightContract {
|
|
539
647
|
}
|
540
648
|
}
|
541
649
|
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
public async getMembershipInfo(
|
552
|
-
idCommitment: string
|
553
|
-
): Promise<MembershipInfo | undefined> {
|
554
|
-
try {
|
555
|
-
const [startBlock, endBlock, rateLimit] =
|
556
|
-
await this.contract.getMembershipInfo(idCommitment);
|
557
|
-
const currentBlock = await this.contract.provider.getBlockNumber();
|
558
|
-
|
559
|
-
let state: MembershipState;
|
560
|
-
if (currentBlock < startBlock) {
|
561
|
-
state = MembershipState.Active;
|
562
|
-
} else if (currentBlock < endBlock) {
|
563
|
-
state = MembershipState.GracePeriod;
|
564
|
-
} else {
|
565
|
-
state = MembershipState.Expired;
|
566
|
-
}
|
650
|
+
/**
|
651
|
+
* Validates that the rate limit is within the allowed range
|
652
|
+
* @throws Error if the rate limit is outside the allowed range
|
653
|
+
*/
|
654
|
+
private async validateRateLimit(rateLimit: number): Promise<void> {
|
655
|
+
const [minRate, maxRate] = await Promise.all([
|
656
|
+
this.contract.minMembershipRateLimit(),
|
657
|
+
this.contract.maxMembershipRateLimit()
|
658
|
+
]);
|
567
659
|
|
568
|
-
|
569
|
-
|
660
|
+
const minRateNum = ethers.BigNumber.from(minRate).toNumber();
|
661
|
+
const maxRateNum = ethers.BigNumber.from(maxRate).toNumber();
|
570
662
|
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
startBlock: startBlock.toNumber(),
|
576
|
-
endBlock: endBlock.toNumber(),
|
577
|
-
state
|
578
|
-
};
|
579
|
-
} catch (error) {
|
580
|
-
return undefined;
|
663
|
+
if (rateLimit < minRateNum || rateLimit > maxRateNum) {
|
664
|
+
throw new Error(
|
665
|
+
`Rate limit must be between ${minRateNum} and ${maxRateNum} messages per epoch`
|
666
|
+
);
|
581
667
|
}
|
582
668
|
}
|
583
669
|
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
670
|
+
private get membersFilter(): ethers.EventFilter {
|
671
|
+
if (!this._membersFilter) {
|
672
|
+
throw Error("Members filter was not initialized.");
|
673
|
+
}
|
674
|
+
return this._membersFilter;
|
588
675
|
}
|
589
676
|
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
return this.
|
595
|
-
[idCommitment],
|
596
|
-
eraseFromMembershipSet
|
597
|
-
);
|
677
|
+
private get membershipErasedFilter(): ethers.EventFilter {
|
678
|
+
if (!this._membershipErasedFilter) {
|
679
|
+
throw Error("MembershipErased filter was not initialized.");
|
680
|
+
}
|
681
|
+
return this._membershipErasedFilter;
|
598
682
|
}
|
599
683
|
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
): Promise<ethers.ContractTransaction> {
|
604
|
-
if (
|
605
|
-
rateLimit < RATE_LIMIT_PARAMS.MIN_RATE ||
|
606
|
-
rateLimit > RATE_LIMIT_PARAMS.MAX_RATE
|
607
|
-
) {
|
608
|
-
throw new Error(
|
609
|
-
`Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE}`
|
610
|
-
);
|
684
|
+
private get membersExpiredFilter(): ethers.EventFilter {
|
685
|
+
if (!this._membersExpiredFilter) {
|
686
|
+
throw Error("MembersExpired filter was not initialized.");
|
611
687
|
}
|
612
|
-
return this.
|
688
|
+
return this._membersExpiredFilter;
|
613
689
|
}
|
614
690
|
|
615
691
|
private async getMemberIndex(
|
616
|
-
|
692
|
+
idCommitmentBigInt: bigint
|
617
693
|
): Promise<ethers.BigNumber | undefined> {
|
618
694
|
try {
|
619
695
|
const events = await this.contract.queryFilter(
|
620
|
-
this.contract.filters.MembershipRegistered(
|
696
|
+
this.contract.filters.MembershipRegistered(idCommitmentBigInt)
|
621
697
|
);
|
622
698
|
if (events.length === 0) return undefined;
|
623
699
|
|
@@ -629,97 +705,3 @@ export class RLNLightContract {
|
|
629
705
|
}
|
630
706
|
}
|
631
707
|
}
|
632
|
-
|
633
|
-
interface CustomQueryOptions extends FetchMembersOptions {
|
634
|
-
membersFilter: ethers.EventFilter;
|
635
|
-
}
|
636
|
-
|
637
|
-
// These values should be tested on other networks
|
638
|
-
const FETCH_CHUNK = 5;
|
639
|
-
const BLOCK_RANGE = 3000;
|
640
|
-
|
641
|
-
async function queryFilter(
|
642
|
-
contract: ethers.Contract,
|
643
|
-
options: CustomQueryOptions
|
644
|
-
): Promise<ethers.Event[]> {
|
645
|
-
const {
|
646
|
-
fromBlock,
|
647
|
-
membersFilter,
|
648
|
-
fetchRange = BLOCK_RANGE,
|
649
|
-
fetchChunks = FETCH_CHUNK
|
650
|
-
} = options;
|
651
|
-
|
652
|
-
if (fromBlock === undefined) {
|
653
|
-
return contract.queryFilter(membersFilter);
|
654
|
-
}
|
655
|
-
|
656
|
-
if (!contract.provider) {
|
657
|
-
throw Error("No provider found on the contract.");
|
658
|
-
}
|
659
|
-
|
660
|
-
const toBlock = await contract.provider.getBlockNumber();
|
661
|
-
|
662
|
-
if (toBlock - fromBlock < fetchRange) {
|
663
|
-
return contract.queryFilter(membersFilter, fromBlock, toBlock);
|
664
|
-
}
|
665
|
-
|
666
|
-
const events: ethers.Event[][] = [];
|
667
|
-
const chunks = splitToChunks(fromBlock, toBlock, fetchRange);
|
668
|
-
|
669
|
-
for (const portion of takeN<[number, number]>(chunks, fetchChunks)) {
|
670
|
-
const promises = portion.map(([left, right]) =>
|
671
|
-
ignoreErrors(contract.queryFilter(membersFilter, left, right), [])
|
672
|
-
);
|
673
|
-
const fetchedEvents = await Promise.all(promises);
|
674
|
-
events.push(fetchedEvents.flatMap((v) => v));
|
675
|
-
}
|
676
|
-
|
677
|
-
return events.flatMap((v) => v);
|
678
|
-
}
|
679
|
-
|
680
|
-
function splitToChunks(
|
681
|
-
from: number,
|
682
|
-
to: number,
|
683
|
-
step: number
|
684
|
-
): Array<[number, number]> {
|
685
|
-
const chunks: Array<[number, number]> = [];
|
686
|
-
|
687
|
-
let left = from;
|
688
|
-
while (left < to) {
|
689
|
-
const right = left + step < to ? left + step : to;
|
690
|
-
|
691
|
-
chunks.push([left, right] as [number, number]);
|
692
|
-
|
693
|
-
left = right;
|
694
|
-
}
|
695
|
-
|
696
|
-
return chunks;
|
697
|
-
}
|
698
|
-
|
699
|
-
function* takeN<T>(array: T[], size: number): Iterable<T[]> {
|
700
|
-
let start = 0;
|
701
|
-
|
702
|
-
while (start < array.length) {
|
703
|
-
const portion = array.slice(start, start + size);
|
704
|
-
|
705
|
-
yield portion;
|
706
|
-
|
707
|
-
start += size;
|
708
|
-
}
|
709
|
-
}
|
710
|
-
|
711
|
-
async function ignoreErrors<T>(
|
712
|
-
promise: Promise<T>,
|
713
|
-
defaultValue: T
|
714
|
-
): Promise<T> {
|
715
|
-
try {
|
716
|
-
return await promise;
|
717
|
-
} catch (err: unknown) {
|
718
|
-
if (err instanceof Error) {
|
719
|
-
log.info(`Ignoring an error during query: ${err.message}`);
|
720
|
-
} else {
|
721
|
-
log.info(`Ignoring an unknown error during query`);
|
722
|
-
}
|
723
|
-
return defaultValue;
|
724
|
-
}
|
725
|
-
}
|