@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.
- package/CHANGELOG.md +19 -0
- package/README.md +5 -0
- package/bundle/_virtual/utils.js +2 -2
- package/bundle/_virtual/utils2.js +2 -2
- package/bundle/index.js +3 -1
- package/bundle/node_modules/@ethersproject/abi/lib.esm/coders/abstract-coder.js +1 -1
- package/bundle/node_modules/@ethersproject/abi/lib.esm/interface.js +1 -1
- package/bundle/node_modules/@ethersproject/abstract-provider/lib.esm/index.js +1 -1
- package/bundle/node_modules/@ethersproject/abstract-signer/lib.esm/index.js +11 -11
- package/bundle/node_modules/@ethersproject/bytes/lib.esm/_version.js +1 -1
- package/bundle/node_modules/@ethersproject/contracts/lib.esm/index.js +7 -7
- package/bundle/node_modules/@ethersproject/hash/lib.esm/ens-normalize/lib.js +2 -2
- package/bundle/node_modules/@ethersproject/hash/lib.esm/typed-data.js +2 -2
- package/bundle/node_modules/@ethersproject/keccak256/node_modules/js-sha3/src/sha3.js +1 -1
- package/bundle/node_modules/@ethersproject/logger/lib.esm/_version.js +1 -1
- package/bundle/node_modules/@ethersproject/properties/lib.esm/index.js +1 -1
- package/bundle/node_modules/@ethersproject/providers/lib.esm/base-provider.js +44 -44
- package/bundle/node_modules/@ethersproject/providers/lib.esm/json-rpc-provider.js +8 -8
- package/bundle/node_modules/@ethersproject/rlp/lib.esm/_version.js +1 -1
- package/bundle/node_modules/@ethersproject/transactions/lib.esm/index.js +1 -1
- package/bundle/node_modules/@ethersproject/web/lib.esm/geturl.js +1 -1
- package/bundle/node_modules/@ethersproject/web/lib.esm/index.js +2 -2
- package/bundle/node_modules/@multiformats/multiaddr/dist/src/multiaddr.js +1 -0
- package/bundle/node_modules/@noble/hashes/esm/sha3.js +1 -1
- package/bundle/node_modules/bn.js/lib/bn.js +1 -1
- package/bundle/packages/core/dist/lib/connection_manager/connection_manager.js +6 -6
- package/bundle/packages/rln/dist/contract/abi.js +502 -248
- package/bundle/packages/rln/dist/contract/constants.js +5 -9
- package/bundle/packages/rln/dist/contract/errors.js +62 -0
- package/bundle/packages/rln/dist/contract/rln_base_contract.js +347 -0
- package/bundle/packages/rln/dist/contract/rln_contract.js +81 -392
- package/bundle/packages/rln/dist/contract/types.js +9 -0
- package/bundle/packages/rln/dist/create.js +1 -1
- package/bundle/packages/rln/dist/credentials_manager.js +215 -0
- package/bundle/packages/rln/dist/identity.js +8 -0
- package/bundle/packages/rln/dist/keystore/keystore.js +20 -28
- package/bundle/packages/rln/dist/rln.js +56 -166
- package/bundle/packages/rln/dist/zerokit.js +5 -5
- package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/lib/checksum.js +2 -2
- package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/lib/cipher.js +3 -3
- package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/lib/class.js +4 -4
- package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/lib/functional.js +4 -4
- package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/lib/kdf.js +4 -4
- package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/lib/password.js +1 -1
- package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/lib/schema-validation-generated.js +1 -1
- package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/lib/schema-validation.js +1 -1
- package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/node_modules/ethereum-cryptography/aes.js +1 -1
- package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/node_modules/ethereum-cryptography/pbkdf2.js +1 -1
- package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/node_modules/ethereum-cryptography/random.js +2 -2
- package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/node_modules/ethereum-cryptography/scrypt.js +1 -1
- package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/node_modules/ethereum-cryptography/sha256.js +1 -1
- package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/node_modules/ethereum-cryptography/utils.js +3 -3
- package/bundle/packages/rln/node_modules/@noble/hashes/_assert.js +1 -1
- package/bundle/packages/rln/node_modules/@noble/hashes/_sha2.js +2 -2
- package/bundle/packages/rln/node_modules/@noble/hashes/_u64.js +1 -1
- package/bundle/packages/rln/node_modules/@noble/hashes/cryptoBrowser.js +1 -1
- package/bundle/packages/rln/node_modules/@noble/hashes/esm/_assert.js +43 -0
- package/bundle/packages/rln/node_modules/@noble/hashes/esm/_sha2.js +116 -0
- package/bundle/packages/rln/node_modules/@noble/hashes/esm/hmac.js +79 -0
- package/bundle/packages/rln/node_modules/@noble/hashes/esm/sha256.js +126 -0
- package/bundle/packages/rln/node_modules/@noble/hashes/esm/utils.js +43 -0
- package/bundle/packages/rln/node_modules/@noble/hashes/hmac.js +2 -2
- package/bundle/packages/rln/node_modules/@noble/hashes/pbkdf2.js +2 -2
- package/bundle/packages/rln/node_modules/@noble/hashes/scrypt.js +3 -3
- package/bundle/packages/rln/node_modules/@noble/hashes/sha256.js +2 -2
- package/bundle/packages/rln/node_modules/@noble/hashes/sha512.js +3 -3
- package/bundle/packages/rln/node_modules/@noble/hashes/utils.js +2 -2
- package/dist/.tsbuildinfo +1 -1
- package/dist/codec.test-utils.d.ts +36 -0
- package/dist/codec.test-utils.js +56 -0
- package/dist/codec.test-utils.js.map +1 -0
- package/dist/contract/abi.d.ts +21 -17
- package/dist/contract/abi.js +502 -248
- package/dist/contract/abi.js.map +1 -1
- package/dist/contract/constants.d.ts +23 -19
- package/dist/contract/constants.js +3 -3
- package/dist/contract/constants.js.map +1 -1
- package/dist/contract/errors.d.ts +30 -0
- package/dist/contract/errors.js +61 -0
- package/dist/contract/errors.js.map +1 -0
- package/dist/contract/rln_base_contract.d.ts +88 -0
- package/dist/contract/rln_base_contract.js +330 -0
- package/dist/contract/rln_base_contract.js.map +1 -0
- package/dist/contract/rln_contract.d.ts +19 -109
- package/dist/contract/rln_contract.js +80 -390
- package/dist/contract/rln_contract.js.map +1 -1
- package/dist/contract/test-setup.d.ts +26 -0
- package/dist/contract/test-setup.js +56 -0
- package/dist/contract/test-setup.js.map +1 -0
- package/dist/contract/test-utils.d.ts +39 -0
- package/dist/contract/test-utils.js +118 -0
- package/dist/contract/test-utils.js.map +1 -0
- package/dist/contract/types.d.ts +40 -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 +1 -0
- package/dist/identity.js +8 -0
- package/dist/identity.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/keystore/keystore.d.ts +0 -1
- package/dist/keystore/keystore.js +20 -28
- package/dist/keystore/keystore.js.map +1 -1
- package/dist/keystore/types.d.ts +2 -1
- 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/codec.test-utils.ts +80 -0
- package/src/contract/abi.ts +502 -248
- package/src/contract/constants.ts +3 -3
- package/src/contract/errors.ts +75 -0
- package/src/contract/rln_base_contract.ts +500 -0
- package/src/contract/rln_contract.ts +102 -619
- package/src/contract/test-setup.ts +86 -0
- package/src/contract/test-utils.ts +179 -0
- package/src/contract/types.ts +48 -0
- package/src/create.ts +1 -1
- package/src/credentials_manager.ts +282 -0
- package/src/identity.ts +9 -0
- package/src/index.ts +17 -2
- package/src/keystore/keystore.ts +32 -46
- package/src/keystore/types.ts +2 -1
- package/src/rln.ts +67 -258
- package/src/types.ts +31 -0
- package/src/zerokit.ts +3 -3
@@ -1,8 +1,8 @@
|
|
1
1
|
import { RLN_ABI } from "./abi.js";
|
2
2
|
|
3
|
-
export const
|
4
|
-
chainId:
|
5
|
-
address: "
|
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
|
+
}
|