@waku/rln 0.0.13 → 0.0.14-7e0966a

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.
@@ -0,0 +1,204 @@
1
+ import { ethers } from "ethers";
2
+
3
+ import { RLN_ABI } from "./constants.js";
4
+ import { MembershipKey, RLNInstance } from "./rln.js";
5
+
6
+ type Member = {
7
+ pubkey: string;
8
+ index: number;
9
+ };
10
+
11
+ type ContractOptions = {
12
+ address: string;
13
+ provider: ethers.Signer | ethers.providers.Provider;
14
+ };
15
+
16
+ type FetchMembersOptions = {
17
+ fromBlock?: number;
18
+ fetchRange?: number;
19
+ fetchChunks?: number;
20
+ };
21
+
22
+ export class RLNContract {
23
+ private _contract: ethers.Contract;
24
+ private membersFilter: ethers.EventFilter;
25
+
26
+ private _members: Member[] = [];
27
+
28
+ public static async init(
29
+ rlnInstance: RLNInstance,
30
+ options: ContractOptions
31
+ ): Promise<RLNContract> {
32
+ const rlnContract = new RLNContract(options);
33
+
34
+ await rlnContract.fetchMembers(rlnInstance);
35
+ rlnContract.subscribeToMembers(rlnInstance);
36
+
37
+ return rlnContract;
38
+ }
39
+
40
+ constructor({ address, provider }: ContractOptions) {
41
+ this._contract = new ethers.Contract(address, RLN_ABI, provider);
42
+ this.membersFilter = this.contract.filters.MemberRegistered();
43
+ }
44
+
45
+ public get contract(): ethers.Contract {
46
+ return this._contract;
47
+ }
48
+
49
+ public get members(): Member[] {
50
+ return this._members;
51
+ }
52
+
53
+ public async fetchMembers(
54
+ rlnInstance: RLNInstance,
55
+ options: FetchMembersOptions = {}
56
+ ): Promise<void> {
57
+ const registeredMemberEvents = await queryFilter(this.contract, {
58
+ ...options,
59
+ membersFilter: this.membersFilter,
60
+ });
61
+
62
+ for (const event of registeredMemberEvents) {
63
+ this.addMemberFromEvent(rlnInstance, event);
64
+ }
65
+ }
66
+
67
+ public subscribeToMembers(rlnInstance: RLNInstance): void {
68
+ this.contract.on(this.membersFilter, (_pubkey, _index, event) =>
69
+ this.addMemberFromEvent(rlnInstance, event)
70
+ );
71
+ }
72
+
73
+ private addMemberFromEvent(
74
+ rlnInstance: RLNInstance,
75
+ event: ethers.Event
76
+ ): void {
77
+ if (!event.args) {
78
+ return;
79
+ }
80
+
81
+ const pubkey: string = event.args.pubkey;
82
+ const index: number = event.args.index;
83
+
84
+ this.members.push({ index, pubkey });
85
+
86
+ const idCommitment = ethers.utils.zeroPad(
87
+ ethers.utils.arrayify(pubkey),
88
+ 32
89
+ );
90
+ rlnInstance.insertMember(idCommitment);
91
+ }
92
+
93
+ public async registerWithSignature(
94
+ rlnInstance: RLNInstance,
95
+ signature: string
96
+ ): Promise<ethers.Event | undefined> {
97
+ const membershipKey = await rlnInstance.generateSeededMembershipKey(
98
+ signature
99
+ );
100
+
101
+ return this.registerWithKey(membershipKey);
102
+ }
103
+
104
+ public async registerWithKey(
105
+ membershipKey: MembershipKey
106
+ ): Promise<ethers.Event | undefined> {
107
+ const depositValue = await this.contract.MEMBERSHIP_DEPOSIT();
108
+
109
+ const txRegisterResponse: ethers.ContractTransaction =
110
+ await this.contract.register(membershipKey.IDCommitmentBigInt, {
111
+ value: depositValue,
112
+ });
113
+ const txRegisterReceipt = await txRegisterResponse.wait();
114
+
115
+ return txRegisterReceipt?.events?.[0];
116
+ }
117
+ }
118
+
119
+ type CustomQueryOptions = FetchMembersOptions & {
120
+ membersFilter: ethers.EventFilter;
121
+ };
122
+
123
+ // these value should be tested on other networks
124
+ const FETCH_CHUNK = 5;
125
+ const BLOCK_RANGE = 3000;
126
+
127
+ async function queryFilter(
128
+ contract: ethers.Contract,
129
+ options: CustomQueryOptions
130
+ ): Promise<ethers.Event[]> {
131
+ const {
132
+ fromBlock,
133
+ membersFilter,
134
+ fetchRange = BLOCK_RANGE,
135
+ fetchChunks = FETCH_CHUNK,
136
+ } = options;
137
+
138
+ if (!fromBlock) {
139
+ return contract.queryFilter(membersFilter);
140
+ }
141
+
142
+ if (!contract.signer.provider) {
143
+ throw Error("No provider found on the contract's signer.");
144
+ }
145
+
146
+ const toBlock = await contract.signer.provider.getBlockNumber();
147
+
148
+ if (toBlock - fromBlock < fetchRange) {
149
+ return contract.queryFilter(membersFilter);
150
+ }
151
+
152
+ const events: ethers.Event[][] = [];
153
+ const chunks = splitToChunks(fromBlock, toBlock, fetchRange);
154
+
155
+ for (const portion of takeN<[number, number]>(chunks, fetchChunks)) {
156
+ const promises = portion.map(([left, right]) =>
157
+ ignoreErrors(contract.queryFilter(membersFilter, left, right), [])
158
+ );
159
+ const fetchedEvents = await Promise.all(promises);
160
+ events.push(fetchedEvents.flatMap((v) => v));
161
+ }
162
+
163
+ return events.flatMap((v) => v);
164
+ }
165
+
166
+ function splitToChunks(
167
+ from: number,
168
+ to: number,
169
+ step: number
170
+ ): Array<[number, number]> {
171
+ const chunks = [];
172
+
173
+ let left = from;
174
+ while (left < to) {
175
+ const right = left + step < to ? left + step : to;
176
+
177
+ chunks.push([left, right] as [number, number]);
178
+
179
+ left = right;
180
+ }
181
+
182
+ return chunks;
183
+ }
184
+
185
+ function* takeN<T>(array: T[], size: number): Iterable<T[]> {
186
+ let start = 0;
187
+ let skip = size;
188
+
189
+ while (skip < array.length) {
190
+ const portion = array.slice(start, skip);
191
+
192
+ yield portion;
193
+
194
+ start = skip;
195
+ skip += size;
196
+ }
197
+ }
198
+
199
+ function ignoreErrors<T>(promise: Promise<T>, defaultValue: T): Promise<T> {
200
+ return promise.catch((err) => {
201
+ console.error(`Ignoring an error during query: ${err?.message}`);
202
+ return defaultValue;
203
+ });
204
+ }