@0xbow/privacy-pools-core-sdk 0.0.0-c084059
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/README.md +73 -0
- package/dist/esm/ccip-nJye9Itm.js +166 -0
- package/dist/esm/ccip-nJye9Itm.js.map +1 -0
- package/dist/esm/index-DAWUECi8.js +84043 -0
- package/dist/esm/index-DAWUECi8.js.map +1 -0
- package/dist/esm/index.mjs +4 -0
- package/dist/esm/index.mjs.map +1 -0
- package/dist/index.d.mts +666 -0
- package/dist/node/ccip-lPhPeJab.js +183 -0
- package/dist/node/ccip-lPhPeJab.js.map +1 -0
- package/dist/node/index-BiU5Ef8Z.js +90625 -0
- package/dist/node/index-BiU5Ef8Z.js.map +1 -0
- package/dist/node/index.mjs +21 -0
- package/dist/node/index.mjs.map +1 -0
- package/dist/types/abi/ERC20.d.ts +38 -0
- package/dist/types/abi/IEntrypoint.d.ts +794 -0
- package/dist/types/abi/IPrivacyPool.d.ts +51 -0
- package/dist/types/ccip-BZzz1Y5w.js +182 -0
- package/dist/types/circuits/circuits.impl.d.ts +108 -0
- package/dist/types/circuits/circuits.interface.d.ts +127 -0
- package/dist/types/circuits/index.d.ts +2 -0
- package/dist/types/constants.d.ts +2 -0
- package/dist/types/core/bruteForce.service.d.ts +61 -0
- package/dist/types/core/commitment.service.d.ts +30 -0
- package/dist/types/core/contracts.service.d.ts +106 -0
- package/dist/types/core/sdk.d.ts +44 -0
- package/dist/types/core/withdrawal.service.d.ts +32 -0
- package/dist/types/crypto.d.ts +45 -0
- package/dist/types/dirname.helper.d.ts +2 -0
- package/dist/types/errors/base.error.d.ts +52 -0
- package/dist/types/exceptions/circuitInitialization.exception.d.ts +3 -0
- package/dist/types/exceptions/fetchArtifacts.exception.d.ts +3 -0
- package/dist/types/exceptions/index.d.ts +4 -0
- package/dist/types/exceptions/invalidRpcUrl.exception.d.ts +3 -0
- package/dist/types/exceptions/privacyPool.exception.d.ts +13 -0
- package/dist/types/external.d.ts +7 -0
- package/dist/types/filename.helper.d.ts +2 -0
- package/dist/types/index-D-_1h8-n.js +90638 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.js +20 -0
- package/dist/types/interfaces/blockchainProvider.interface.d.ts +12 -0
- package/dist/types/interfaces/circuits.interface.d.ts +30 -0
- package/dist/types/interfaces/contracts.interface.d.ts +28 -0
- package/dist/types/interfaces/index.d.ts +1 -0
- package/dist/types/internal.d.ts +6 -0
- package/dist/types/providers/blockchainProvider.d.ts +8 -0
- package/dist/types/providers/index.d.ts +1 -0
- package/dist/types/types/commitment.d.ts +48 -0
- package/dist/types/types/index.d.ts +2 -0
- package/dist/types/types/withdrawal.d.ts +30 -0
- package/package.json +81 -0
- package/src/abi/ERC20.ts +222 -0
- package/src/abi/IEntrypoint.ts +1059 -0
- package/src/abi/IPrivacyPool.ts +576 -0
- package/src/circuits/circuits.impl.ts +232 -0
- package/src/circuits/circuits.interface.ts +166 -0
- package/src/circuits/fetchArtifacts.esm.ts +12 -0
- package/src/circuits/fetchArtifacts.node.ts +23 -0
- package/src/circuits/fetchArtifacts.ts +7 -0
- package/src/circuits/index.ts +2 -0
- package/src/constants.ts +3 -0
- package/src/core/account.service.ts +1077 -0
- package/src/core/bruteForce.service.ts +120 -0
- package/src/core/commitment.service.ts +84 -0
- package/src/core/contracts.service.ts +442 -0
- package/src/core/data.service.ts +272 -0
- package/src/core/sdk.ts +92 -0
- package/src/core/withdrawal.service.ts +126 -0
- package/src/crypto.ts +226 -0
- package/src/dirname.helper.ts +4 -0
- package/src/errors/account.error.ts +49 -0
- package/src/errors/base.error.ts +125 -0
- package/src/errors/data.error.ts +34 -0
- package/src/errors/events.error.ts +38 -0
- package/src/exceptions/circuitInitialization.exception.ts +6 -0
- package/src/exceptions/fetchArtifacts.exception.ts +7 -0
- package/src/exceptions/index.ts +4 -0
- package/src/exceptions/invalidRpcUrl.exception.ts +6 -0
- package/src/exceptions/privacyPool.exception.ts +19 -0
- package/src/external.ts +13 -0
- package/src/filename.helper.ts +4 -0
- package/src/index.ts +21 -0
- package/src/interfaces/blockchainProvider.interface.ts +13 -0
- package/src/interfaces/circuits.interface.ts +34 -0
- package/src/interfaces/contracts.interface.ts +66 -0
- package/src/interfaces/index.ts +1 -0
- package/src/internal.ts +6 -0
- package/src/keys.ts +42 -0
- package/src/providers/blockchainProvider.ts +26 -0
- package/src/providers/index.ts +1 -0
- package/src/types/account.ts +35 -0
- package/src/types/commitment.ts +50 -0
- package/src/types/events.ts +82 -0
- package/src/types/index.ts +3 -0
- package/src/types/keys.ts +6 -0
- package/src/types/withdrawal.ts +33 -0
- package/src/utils/logger.ts +56 -0
|
@@ -0,0 +1,1077 @@
|
|
|
1
|
+
import { poseidon } from "maci-crypto/build/ts/hashing.js";
|
|
2
|
+
import { Hash, Secret } from "../types/commitment.js";
|
|
3
|
+
import { Hex, bytesToNumber } from "viem";
|
|
4
|
+
import { mnemonicToAccount } from "viem/accounts";
|
|
5
|
+
import { DataService } from "./data.service.js";
|
|
6
|
+
import {
|
|
7
|
+
AccountCommitment,
|
|
8
|
+
PoolAccount,
|
|
9
|
+
PoolInfo,
|
|
10
|
+
PrivacyPoolAccount,
|
|
11
|
+
} from "../types/account.js";
|
|
12
|
+
import {
|
|
13
|
+
DepositEvent,
|
|
14
|
+
PoolEventsError,
|
|
15
|
+
PoolEventsResult,
|
|
16
|
+
RagequitEvent,
|
|
17
|
+
WithdrawalEvent,
|
|
18
|
+
} from "../types/events.js";
|
|
19
|
+
|
|
20
|
+
import { Logger } from "../utils/logger.js";
|
|
21
|
+
import { AccountError } from "../errors/account.error.js";
|
|
22
|
+
import { ErrorCode } from "../errors/base.error.js";
|
|
23
|
+
import { EventError } from "../errors/events.error.js";
|
|
24
|
+
|
|
25
|
+
type AccountServiceConfig =
|
|
26
|
+
| {
|
|
27
|
+
mnemonic: string;
|
|
28
|
+
}
|
|
29
|
+
| {
|
|
30
|
+
account: PrivacyPoolAccount;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Service responsible for managing privacy pool accounts and their associated commitments.
|
|
35
|
+
* Handles account initialization, deposit/withdrawal tracking, and history synchronization.
|
|
36
|
+
*
|
|
37
|
+
* @remarks
|
|
38
|
+
* This service maintains the state of all pool accounts and their commitments across different
|
|
39
|
+
* chains and scopes. It uses deterministic key generation to recover account state from a mnemonic.
|
|
40
|
+
*/
|
|
41
|
+
export class AccountService {
|
|
42
|
+
account: PrivacyPoolAccount;
|
|
43
|
+
private readonly logger: Logger;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Creates a new AccountService instance.
|
|
47
|
+
*
|
|
48
|
+
* @param dataService - Service for fetching on-chain events
|
|
49
|
+
* @param config - Configuration for the account service (either mnemonic or existing account)
|
|
50
|
+
* @param config.mnemonic - Optional mnemonic for deterministic key generation
|
|
51
|
+
* @param config.account - Optional existing account to initialize with
|
|
52
|
+
*
|
|
53
|
+
* @throws {AccountError} If account initialization fails
|
|
54
|
+
*/
|
|
55
|
+
constructor(
|
|
56
|
+
private readonly dataService: DataService,
|
|
57
|
+
config: AccountServiceConfig
|
|
58
|
+
) {
|
|
59
|
+
this.logger = new Logger({ prefix: "Account" });
|
|
60
|
+
if ("mnemonic" in config) {
|
|
61
|
+
this.account = this._initializeAccount(config.mnemonic);
|
|
62
|
+
} else {
|
|
63
|
+
this.account = config.account;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Initializes a new account from a mnemonic phrase.
|
|
69
|
+
*
|
|
70
|
+
* @param mnemonic - The mnemonic phrase to derive keys from
|
|
71
|
+
* @returns A new PrivacyPoolAccount with derived master keys
|
|
72
|
+
*
|
|
73
|
+
* @remarks
|
|
74
|
+
* This method derives two master keys from the mnemonic:
|
|
75
|
+
* 1. A master nullifier key from account index 0
|
|
76
|
+
* 2. A master secret key from account index 1
|
|
77
|
+
* These keys are used to deterministically generate nullifiers and secrets for deposits and withdrawals.
|
|
78
|
+
*
|
|
79
|
+
* @throws {AccountError} If account initialization fails
|
|
80
|
+
* @private
|
|
81
|
+
*/
|
|
82
|
+
private _initializeAccount(mnemonic: string): PrivacyPoolAccount {
|
|
83
|
+
try {
|
|
84
|
+
this.logger.debug("Initializing account with mnemonic");
|
|
85
|
+
|
|
86
|
+
const masterNullifierSeed = bytesToNumber(
|
|
87
|
+
mnemonicToAccount(mnemonic, { accountIndex: 0 }).getHdKey().privateKey!
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const masterSecretSeed = bytesToNumber(
|
|
91
|
+
mnemonicToAccount(mnemonic, { accountIndex: 1 }).getHdKey().privateKey!
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const masterNullifier = poseidon([BigInt(masterNullifierSeed)]) as Secret;
|
|
95
|
+
const masterSecret = poseidon([BigInt(masterSecretSeed)]) as Secret;
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
masterKeys: [masterNullifier, masterSecret],
|
|
99
|
+
poolAccounts: new Map(),
|
|
100
|
+
creationTimestamp: 0n,
|
|
101
|
+
lastUpdateTimestamp: 0n,
|
|
102
|
+
};
|
|
103
|
+
} catch (error) {
|
|
104
|
+
throw AccountError.accountInitializationFailed(
|
|
105
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Generates a deterministic nullifier for a deposit.
|
|
112
|
+
*
|
|
113
|
+
* @param scope - The scope of the pool
|
|
114
|
+
* @param index - The index of the deposit
|
|
115
|
+
* @returns A deterministic nullifier for the deposit
|
|
116
|
+
* @private
|
|
117
|
+
*/
|
|
118
|
+
private _genDepositNullifier(scope: Hash, index: bigint): Secret {
|
|
119
|
+
const [masterNullifier] = this.account.masterKeys;
|
|
120
|
+
return poseidon([masterNullifier, scope, index]) as Secret;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Generates a deterministic secret for a deposit.
|
|
125
|
+
*
|
|
126
|
+
* @param scope - The scope of the pool
|
|
127
|
+
* @param index - The index of the deposit
|
|
128
|
+
* @returns A deterministic secret for the deposit
|
|
129
|
+
* @private
|
|
130
|
+
*/
|
|
131
|
+
private _genDepositSecret(scope: Hash, index: bigint): Secret {
|
|
132
|
+
const [, masterSecret] = this.account.masterKeys;
|
|
133
|
+
return poseidon([masterSecret, scope, index]) as Secret;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Generates a deterministic nullifier for a withdrawal.
|
|
138
|
+
*
|
|
139
|
+
* @param label - The label of the commitment
|
|
140
|
+
* @param index - The index of the withdrawal
|
|
141
|
+
* @returns A deterministic nullifier for the withdrawal
|
|
142
|
+
* @private
|
|
143
|
+
*/
|
|
144
|
+
private _genWithdrawalNullifier(label: Hash, index: bigint): Secret {
|
|
145
|
+
const [masterNullifier] = this.account.masterKeys;
|
|
146
|
+
return poseidon([masterNullifier, label, index]) as Secret;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Generates a deterministic secret for a withdrawal.
|
|
151
|
+
*
|
|
152
|
+
* @param label - The label of the commitment
|
|
153
|
+
* @param index - The index of the withdrawal
|
|
154
|
+
* @returns A deterministic secret for the withdrawal
|
|
155
|
+
* @private
|
|
156
|
+
*/
|
|
157
|
+
private _genWithdrawalSecret(label: Hash, index: bigint): Secret {
|
|
158
|
+
const [, masterSecret] = this.account.masterKeys;
|
|
159
|
+
return poseidon([masterSecret, label, index]) as Secret;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Hashes a commitment using the Poseidon hash function.
|
|
164
|
+
*
|
|
165
|
+
* @param value - The value of the commitment
|
|
166
|
+
* @param label - The label of the commitment
|
|
167
|
+
* @param precommitment - The precommitment hash
|
|
168
|
+
* @returns The commitment hash
|
|
169
|
+
* @private
|
|
170
|
+
*/
|
|
171
|
+
private _hashCommitment(
|
|
172
|
+
value: bigint,
|
|
173
|
+
label: Hash,
|
|
174
|
+
precommitment: Hash
|
|
175
|
+
): Hash {
|
|
176
|
+
return poseidon([value, label, precommitment]) as Hash;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Hashes a precommitment using the Poseidon hash function.
|
|
181
|
+
*
|
|
182
|
+
* @param nullifier - The nullifier for the commitment
|
|
183
|
+
* @param secret - The secret for the commitment
|
|
184
|
+
* @returns The precommitment hash
|
|
185
|
+
* @private
|
|
186
|
+
*/
|
|
187
|
+
private _hashPrecommitment(nullifier: Secret, secret: Secret): Hash {
|
|
188
|
+
return poseidon([nullifier, secret]) as Hash;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Gets all spendable commitments across all pools.
|
|
193
|
+
*
|
|
194
|
+
* @returns A map of scope to array of spendable commitments
|
|
195
|
+
*
|
|
196
|
+
* @remarks
|
|
197
|
+
* A commitment is considered spendable if:
|
|
198
|
+
* 1. It has a non-zero value
|
|
199
|
+
* 2. The account it belongs to has not been ragequit
|
|
200
|
+
*/
|
|
201
|
+
public getSpendableCommitments(): Map<bigint, AccountCommitment[]> {
|
|
202
|
+
const result = new Map<bigint, AccountCommitment[]>();
|
|
203
|
+
|
|
204
|
+
for (const [scope, accounts] of this.account.poolAccounts.entries()) {
|
|
205
|
+
const nonZeroCommitments: AccountCommitment[] = [];
|
|
206
|
+
|
|
207
|
+
for (const account of accounts) {
|
|
208
|
+
// Skip accounts that have been ragequit
|
|
209
|
+
if (account.ragequit) {
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const lastCommitment =
|
|
214
|
+
account.children.length > 0
|
|
215
|
+
? account.children[account.children.length - 1]
|
|
216
|
+
: account.deposit;
|
|
217
|
+
|
|
218
|
+
if (lastCommitment!.value !== BigInt(0)) {
|
|
219
|
+
nonZeroCommitments.push(lastCommitment!);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (nonZeroCommitments.length > 0) {
|
|
224
|
+
result.set(scope, nonZeroCommitments);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return result;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Creates nullifier and secret for a new deposit
|
|
232
|
+
*
|
|
233
|
+
* @param scope - The scope of the pool to deposit into
|
|
234
|
+
* @param index - Optional index for deterministic generation
|
|
235
|
+
* @returns The nullifier, secret, and precommitment for the deposit
|
|
236
|
+
*
|
|
237
|
+
* @remarks
|
|
238
|
+
* If no index is provided, it uses the current number of accounts for the scope.
|
|
239
|
+
* The precommitment is a hash of the nullifier and secret, used in the deposit process.
|
|
240
|
+
*/
|
|
241
|
+
public createDepositSecrets(
|
|
242
|
+
scope: Hash,
|
|
243
|
+
index?: bigint
|
|
244
|
+
): {
|
|
245
|
+
nullifier: Secret;
|
|
246
|
+
secret: Secret;
|
|
247
|
+
precommitment: Hash;
|
|
248
|
+
} {
|
|
249
|
+
if (index && index < 0n) {
|
|
250
|
+
throw AccountError.invalidIndex(index);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const accounts = this.account.poolAccounts.get(scope);
|
|
254
|
+
index = index ?? BigInt(accounts?.length || 0);
|
|
255
|
+
|
|
256
|
+
const nullifier = this._genDepositNullifier(scope, index);
|
|
257
|
+
const secret = this._genDepositSecret(scope, index);
|
|
258
|
+
const precommitment = this._hashPrecommitment(nullifier, secret);
|
|
259
|
+
|
|
260
|
+
return { nullifier, secret, precommitment };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Creates nullifier and secret for spending a commitment
|
|
265
|
+
*
|
|
266
|
+
* @param commitment - The commitment to spend
|
|
267
|
+
* @returns The nullifier and secret for the new commitment
|
|
268
|
+
*
|
|
269
|
+
* @remarks
|
|
270
|
+
* The index used for generating the withdrawal nullifier and secret is based on
|
|
271
|
+
* the number of children the account already has, ensuring each withdrawal has
|
|
272
|
+
* a unique nullifier.
|
|
273
|
+
*
|
|
274
|
+
* @throws {AccountError} If no account is found for the commitment
|
|
275
|
+
*/
|
|
276
|
+
public createWithdrawalSecrets(commitment: AccountCommitment): {
|
|
277
|
+
nullifier: Secret;
|
|
278
|
+
secret: Secret;
|
|
279
|
+
} {
|
|
280
|
+
let index: bigint | undefined;
|
|
281
|
+
|
|
282
|
+
for (const accounts of this.account.poolAccounts.values()) {
|
|
283
|
+
const account = accounts.find((acc) => acc.label === commitment.label);
|
|
284
|
+
if (account) {
|
|
285
|
+
index = BigInt(account.children.length);
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (index === undefined) {
|
|
291
|
+
throw AccountError.commitmentNotFound(commitment.label);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const nullifier = this._genWithdrawalNullifier(commitment.label, index);
|
|
295
|
+
const secret = this._genWithdrawalSecret(commitment.label, index);
|
|
296
|
+
|
|
297
|
+
return { nullifier, secret };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Adds a new pool account after depositing
|
|
302
|
+
*
|
|
303
|
+
* @param scope - The scope of the pool
|
|
304
|
+
* @param value - The deposit value
|
|
305
|
+
* @param nullifier - The nullifier used for the deposit
|
|
306
|
+
* @param secret - The secret used for the deposit
|
|
307
|
+
* @param label - The label for the commitment
|
|
308
|
+
* @param blockNumber - The block number of the deposit
|
|
309
|
+
* @param txHash - The transaction hash of the deposit
|
|
310
|
+
* @returns The new pool account
|
|
311
|
+
*
|
|
312
|
+
* @remarks
|
|
313
|
+
* This method creates a new account with the deposit commitment and adds it to the
|
|
314
|
+
* pool accounts map under the specified scope. The commitment hash is calculated
|
|
315
|
+
* from the value, label, and precommitment.
|
|
316
|
+
*/
|
|
317
|
+
public addPoolAccount(
|
|
318
|
+
scope: Hash,
|
|
319
|
+
value: bigint,
|
|
320
|
+
nullifier: Secret,
|
|
321
|
+
secret: Secret,
|
|
322
|
+
label: Hash,
|
|
323
|
+
blockNumber: bigint,
|
|
324
|
+
txHash: Hex
|
|
325
|
+
): PoolAccount {
|
|
326
|
+
const precommitment = this._hashPrecommitment(nullifier, secret);
|
|
327
|
+
const commitment = this._hashCommitment(value, label, precommitment);
|
|
328
|
+
|
|
329
|
+
const newAccount: PoolAccount = {
|
|
330
|
+
label,
|
|
331
|
+
deposit: {
|
|
332
|
+
hash: commitment,
|
|
333
|
+
value,
|
|
334
|
+
label,
|
|
335
|
+
nullifier,
|
|
336
|
+
secret,
|
|
337
|
+
blockNumber,
|
|
338
|
+
txHash,
|
|
339
|
+
},
|
|
340
|
+
children: [],
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
if (!this.account.poolAccounts.has(scope)) {
|
|
344
|
+
this.account.poolAccounts.set(scope, []);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
this.account.poolAccounts.get(scope)!.push(newAccount);
|
|
348
|
+
|
|
349
|
+
this.logger.info(
|
|
350
|
+
`Added new pool account with value ${value} and label ${label}`
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
return newAccount;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Adds a new commitment to the account after spending
|
|
358
|
+
*
|
|
359
|
+
* @param parentCommitment - The commitment that was spent
|
|
360
|
+
* @param value - The remaining value after spending
|
|
361
|
+
* @param nullifier - The nullifier used for spending
|
|
362
|
+
* @param secret - The secret used for spending
|
|
363
|
+
* @param blockNumber - The block number of the withdrawal
|
|
364
|
+
* @param txHash - The transaction hash of the withdrawal
|
|
365
|
+
* @returns The new commitment
|
|
366
|
+
*
|
|
367
|
+
* @remarks
|
|
368
|
+
* This method finds the account containing the parent commitment, creates a new
|
|
369
|
+
* commitment with the provided parameters, and adds it to the account's children.
|
|
370
|
+
* The new commitment inherits the label from the parent commitment.
|
|
371
|
+
*
|
|
372
|
+
* @throws {AccountError} If no account is found for the commitment
|
|
373
|
+
*/
|
|
374
|
+
public addWithdrawalCommitment(
|
|
375
|
+
parentCommitment: AccountCommitment,
|
|
376
|
+
value: bigint,
|
|
377
|
+
nullifier: Secret,
|
|
378
|
+
secret: Secret,
|
|
379
|
+
blockNumber: bigint,
|
|
380
|
+
txHash: Hex
|
|
381
|
+
): AccountCommitment {
|
|
382
|
+
let foundAccount: PoolAccount | undefined;
|
|
383
|
+
let foundScope: bigint | undefined;
|
|
384
|
+
|
|
385
|
+
for (const [scope, accounts] of this.account.poolAccounts.entries()) {
|
|
386
|
+
foundAccount = accounts.find((account) => {
|
|
387
|
+
if (account.deposit.hash === parentCommitment.hash) return true;
|
|
388
|
+
return account.children.some(
|
|
389
|
+
(child) => child.hash === parentCommitment.hash
|
|
390
|
+
);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
if (foundAccount) {
|
|
394
|
+
foundScope = scope;
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (!foundAccount || !foundScope) {
|
|
400
|
+
throw AccountError.commitmentNotFound(parentCommitment.hash);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const precommitment = this._hashPrecommitment(nullifier, secret);
|
|
404
|
+
const newCommitment: AccountCommitment = {
|
|
405
|
+
hash: this._hashCommitment(value, parentCommitment.label, precommitment),
|
|
406
|
+
value,
|
|
407
|
+
label: parentCommitment.label,
|
|
408
|
+
nullifier,
|
|
409
|
+
secret,
|
|
410
|
+
blockNumber,
|
|
411
|
+
txHash,
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
foundAccount.children.push(newCommitment);
|
|
415
|
+
|
|
416
|
+
this.logger.info(
|
|
417
|
+
`Added new commitment with value ${value} to account with label ${parentCommitment.label}`
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
return newCommitment;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Adds a ragequit event to an existing pool account
|
|
425
|
+
*
|
|
426
|
+
* @param label - The label of the account to add the ragequit to
|
|
427
|
+
* @param ragequit - The ragequit event to add
|
|
428
|
+
* @returns The updated pool account
|
|
429
|
+
*
|
|
430
|
+
* @remarks
|
|
431
|
+
* When an account has a ragequit event, it can no longer be spent.
|
|
432
|
+
* This method finds the account with the matching label and attaches
|
|
433
|
+
* the ragequit event to it.
|
|
434
|
+
*
|
|
435
|
+
* @throws {AccountError} If no account is found with the given label
|
|
436
|
+
*/
|
|
437
|
+
public addRagequitToAccount(
|
|
438
|
+
label: Hash,
|
|
439
|
+
ragequit: RagequitEvent
|
|
440
|
+
): PoolAccount {
|
|
441
|
+
let foundAccount: PoolAccount | undefined;
|
|
442
|
+
let foundScope: Hash | undefined;
|
|
443
|
+
|
|
444
|
+
// Find the account with the matching label
|
|
445
|
+
for (const [scope, accounts] of this.account.poolAccounts.entries()) {
|
|
446
|
+
foundAccount = accounts.find((account) => account.label === label);
|
|
447
|
+
if (foundAccount) {
|
|
448
|
+
foundScope = scope;
|
|
449
|
+
break;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (!foundAccount || !foundScope) {
|
|
454
|
+
throw new AccountError(
|
|
455
|
+
`No account found with label ${label}`,
|
|
456
|
+
ErrorCode.INVALID_INPUT
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Add the ragequit event to the account
|
|
461
|
+
foundAccount.ragequit = ragequit;
|
|
462
|
+
|
|
463
|
+
this.logger.info(
|
|
464
|
+
`Added ragequit event to account with label ${label}, value ${ragequit.value}`
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
return foundAccount;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Fetches deposit events for a given pool and returns a map of precommitments to their events for efficient lookup
|
|
472
|
+
*
|
|
473
|
+
* @param pool - The pool to fetch deposit events for
|
|
474
|
+
*
|
|
475
|
+
* @returns A map of precommitments to their events
|
|
476
|
+
*/
|
|
477
|
+
public async getDepositEvents(
|
|
478
|
+
pool: PoolInfo
|
|
479
|
+
): Promise<Map<Hash, DepositEvent>> {
|
|
480
|
+
try {
|
|
481
|
+
const depositEvents = await this.dataService.getDeposits(pool);
|
|
482
|
+
|
|
483
|
+
this.logger.info(`Found deposits for pool`, {
|
|
484
|
+
poolAddress: pool.address,
|
|
485
|
+
poolChainId: pool.chainId,
|
|
486
|
+
depositCount: depositEvents.length,
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
const depositMap = new Map<Hash, DepositEvent>();
|
|
490
|
+
for (const event of depositEvents) {
|
|
491
|
+
const existingEvent = depositMap.get(event.precommitment);
|
|
492
|
+
|
|
493
|
+
// If no existing event, or current event is older (earlier block), use current event
|
|
494
|
+
if (!existingEvent || event.blockNumber < existingEvent.blockNumber) {
|
|
495
|
+
depositMap.set(event.precommitment, event);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
return depositMap;
|
|
500
|
+
} catch (error) {
|
|
501
|
+
throw EventError.depositEventError(
|
|
502
|
+
pool.chainId,
|
|
503
|
+
pool.scope,
|
|
504
|
+
error as Error
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Fetches withdrawal events for a given pool and returns a map of spent nullifiers to their events for efficient lookup
|
|
511
|
+
*
|
|
512
|
+
* @param pool - The pool to fetch withdrawal events for
|
|
513
|
+
*
|
|
514
|
+
* @returns A map of spent nullifiers to their events
|
|
515
|
+
*/
|
|
516
|
+
public async getWithdrawalEvents(
|
|
517
|
+
pool: PoolInfo
|
|
518
|
+
): Promise<Map<Hash, WithdrawalEvent>> {
|
|
519
|
+
try {
|
|
520
|
+
const withdrawalEvents = await this.dataService.getWithdrawals(pool);
|
|
521
|
+
const withdrawalMap = new Map<Hash, WithdrawalEvent>();
|
|
522
|
+
for (const event of withdrawalEvents) {
|
|
523
|
+
withdrawalMap.set(event.spentNullifier, event);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
return withdrawalMap;
|
|
527
|
+
} catch (error) {
|
|
528
|
+
throw EventError.withdrawalEventError(
|
|
529
|
+
pool.chainId,
|
|
530
|
+
pool.scope,
|
|
531
|
+
error as Error
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Fetches ragequit events for a given pool and returns a map of ragequit labels to their events for efficient lookup
|
|
538
|
+
*
|
|
539
|
+
* @param pool - The pool to fetch ragequit events for
|
|
540
|
+
*
|
|
541
|
+
* @returns A map of ragequit labels to their events
|
|
542
|
+
*/
|
|
543
|
+
public async getRagequitEvents(
|
|
544
|
+
pool: PoolInfo
|
|
545
|
+
): Promise<Map<Hash, RagequitEvent>> {
|
|
546
|
+
try {
|
|
547
|
+
const ragequitEvents = await this.dataService.getRagequits(pool);
|
|
548
|
+
const ragequitMap = new Map<Hash, RagequitEvent>();
|
|
549
|
+
for (const event of ragequitEvents) {
|
|
550
|
+
ragequitMap.set(event.label, event);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
return ragequitMap;
|
|
554
|
+
} catch (error) {
|
|
555
|
+
throw EventError.ragequitEventError(
|
|
556
|
+
pool.chainId,
|
|
557
|
+
pool.scope,
|
|
558
|
+
error as Error
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Fetches events for a given set of pools
|
|
565
|
+
*
|
|
566
|
+
* @param pools - The pools to fetch events for
|
|
567
|
+
*
|
|
568
|
+
* @returns A map of pool scopes to their events
|
|
569
|
+
*/
|
|
570
|
+
public async getEvents(pools: PoolInfo[]): Promise<PoolEventsResult> {
|
|
571
|
+
const events: PoolEventsResult = new Map();
|
|
572
|
+
|
|
573
|
+
const poolEventResults = await Promise.allSettled(
|
|
574
|
+
pools.map(async (pool) => {
|
|
575
|
+
this.logger.info(`Fetching events for pool`, {
|
|
576
|
+
poolAddress: pool.address,
|
|
577
|
+
poolChainId: pool.chainId,
|
|
578
|
+
poolDeploymentBlock: pool.deploymentBlock,
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
const [depositEvents, withdrawalEvents, ragequitEvents] =
|
|
582
|
+
await Promise.all([
|
|
583
|
+
this.getDepositEvents(pool),
|
|
584
|
+
this.getWithdrawalEvents(pool),
|
|
585
|
+
this.getRagequitEvents(pool),
|
|
586
|
+
]);
|
|
587
|
+
|
|
588
|
+
return {
|
|
589
|
+
scope: pool.scope,
|
|
590
|
+
depositEvents,
|
|
591
|
+
withdrawalEvents,
|
|
592
|
+
ragequitEvents,
|
|
593
|
+
};
|
|
594
|
+
})
|
|
595
|
+
);
|
|
596
|
+
|
|
597
|
+
for (const result of poolEventResults) {
|
|
598
|
+
if (result.status === "fulfilled") {
|
|
599
|
+
const { scope, depositEvents, withdrawalEvents, ragequitEvents } =
|
|
600
|
+
result.value;
|
|
601
|
+
events.set(scope, {
|
|
602
|
+
depositEvents,
|
|
603
|
+
withdrawalEvents,
|
|
604
|
+
ragequitEvents,
|
|
605
|
+
});
|
|
606
|
+
} else {
|
|
607
|
+
events.set(result.reason.details?.scope as Hash, {
|
|
608
|
+
reason: result.reason.message,
|
|
609
|
+
scope: result.reason.details?.scope as Hash,
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
return events;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Processes deposit events for a given scope and adds them to the account
|
|
619
|
+
* Deterministically generate deposit secrets and check if they match on-chain deposits
|
|
620
|
+
*
|
|
621
|
+
* @param scope - The scope of the pool
|
|
622
|
+
* @param depositEvents - The map of deposit events
|
|
623
|
+
*
|
|
624
|
+
*/
|
|
625
|
+
private _processDepositEvents(
|
|
626
|
+
scope: Hash,
|
|
627
|
+
depositEvents: Map<Hash, DepositEvent>
|
|
628
|
+
): void {
|
|
629
|
+
for (let index = BigInt(0); index < depositEvents.size; index++) {
|
|
630
|
+
// Generate nullifier, secret, and precommitment for this index
|
|
631
|
+
const { nullifier, secret, precommitment } = this.createDepositSecrets(
|
|
632
|
+
scope,
|
|
633
|
+
index
|
|
634
|
+
);
|
|
635
|
+
|
|
636
|
+
// Look for a deposit with this precommitment
|
|
637
|
+
const event = depositEvents.get(precommitment);
|
|
638
|
+
|
|
639
|
+
if (!event) {
|
|
640
|
+
break; // No more deposits found, exit the loop
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// Create a new pool account for this deposit
|
|
644
|
+
this.addPoolAccount(
|
|
645
|
+
scope,
|
|
646
|
+
event.value,
|
|
647
|
+
nullifier,
|
|
648
|
+
secret,
|
|
649
|
+
event.label,
|
|
650
|
+
event.blockNumber,
|
|
651
|
+
event.transactionHash
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Processes withdrawal events for a given scope and adds them to the account
|
|
658
|
+
*
|
|
659
|
+
* @param scope - The scope of the pool
|
|
660
|
+
* @param withdrawalEvents - The map of withdrawal events
|
|
661
|
+
*
|
|
662
|
+
* @remarks
|
|
663
|
+
* This method performs the following steps for each pool:
|
|
664
|
+
* 1. Identifies the earliest deposit block for each scope
|
|
665
|
+
* 2. For each account, reconstructs the withdrawal history by:
|
|
666
|
+
* - Generating nullifiers sequentially
|
|
667
|
+
* - Matching them against on-chain events
|
|
668
|
+
* - Adding matched withdrawals to the account state
|
|
669
|
+
*
|
|
670
|
+
* @throws {DataError} If event fetching fails
|
|
671
|
+
* @private
|
|
672
|
+
*
|
|
673
|
+
*/
|
|
674
|
+
private _processWithdrawalEvents(
|
|
675
|
+
scope: Hash,
|
|
676
|
+
withdrawalEvents: Map<Hash, WithdrawalEvent>
|
|
677
|
+
): void {
|
|
678
|
+
const accounts = this.account.poolAccounts.get(scope);
|
|
679
|
+
|
|
680
|
+
// Skip if no accounts for this scope
|
|
681
|
+
if (!accounts || accounts.length === 0) {
|
|
682
|
+
this.logger.info(`No accounts found for pool with this scope`, {
|
|
683
|
+
scope,
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// Process each account in parallel for better performance
|
|
690
|
+
for (const account of accounts) {
|
|
691
|
+
let currentCommitment = account.deposit;
|
|
692
|
+
let index = BigInt(0);
|
|
693
|
+
|
|
694
|
+
// Continue processing withdrawals until no more are found secuentially
|
|
695
|
+
while (true) {
|
|
696
|
+
// Generate nullifier for this withdrawal
|
|
697
|
+
const nullifierHash = poseidon([currentCommitment.nullifier]) as Hash;
|
|
698
|
+
|
|
699
|
+
// Look for a withdrawal event with this nullifier
|
|
700
|
+
const withdrawal = withdrawalEvents.get(nullifierHash);
|
|
701
|
+
if (!withdrawal) {
|
|
702
|
+
break;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// Generate secret for this withdrawal
|
|
706
|
+
const nullifier = this._genWithdrawalNullifier(account.label, index);
|
|
707
|
+
const secret = this._genWithdrawalSecret(account.label, index);
|
|
708
|
+
|
|
709
|
+
// Add the withdrawal commitment to the account
|
|
710
|
+
const newCommitment = this.addWithdrawalCommitment(
|
|
711
|
+
currentCommitment,
|
|
712
|
+
currentCommitment.value - withdrawal.withdrawn,
|
|
713
|
+
nullifier,
|
|
714
|
+
secret,
|
|
715
|
+
withdrawal.blockNumber,
|
|
716
|
+
withdrawal.transactionHash
|
|
717
|
+
);
|
|
718
|
+
|
|
719
|
+
// Update current commitment to the newly created one
|
|
720
|
+
currentCommitment = newCommitment;
|
|
721
|
+
|
|
722
|
+
// Increment index for next potential withdrawal
|
|
723
|
+
index++;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* Processes ragequit events for a given scope and adds them to the account
|
|
730
|
+
*
|
|
731
|
+
* @param scope - The scope of the pool
|
|
732
|
+
* @param ragequitEvents - The map of ragequit events
|
|
733
|
+
*
|
|
734
|
+
* @remarks
|
|
735
|
+
* This method performs the following steps for each pool:
|
|
736
|
+
* 1. Adds ragequit events to accounts if found
|
|
737
|
+
*
|
|
738
|
+
* @throws {DataError} If event fetching fails
|
|
739
|
+
* @private
|
|
740
|
+
*
|
|
741
|
+
*/
|
|
742
|
+
private _processRagequitEvents(
|
|
743
|
+
scope: Hash,
|
|
744
|
+
ragequitEvents: Map<Hash, RagequitEvent>
|
|
745
|
+
): void {
|
|
746
|
+
const accounts = this.account.poolAccounts.get(scope);
|
|
747
|
+
|
|
748
|
+
if (!accounts || accounts.length === 0) {
|
|
749
|
+
this.logger.info(`No accounts found for pool with this scope`, {
|
|
750
|
+
scope,
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
for (const account of accounts) {
|
|
757
|
+
const ragequit = ragequitEvents.get(account.label);
|
|
758
|
+
if (ragequit) {
|
|
759
|
+
this.addRagequitToAccount(account.label, ragequit);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* Initializes an AccountService instance with events for a given set of pools
|
|
766
|
+
*
|
|
767
|
+
* @param dataService - The data service to use for fetching events
|
|
768
|
+
* @param source - The source to use for initializing the account. Either a mnemonic or an existing account service instance
|
|
769
|
+
* @param pools - The pools to fetch events for
|
|
770
|
+
*
|
|
771
|
+
* @remarks
|
|
772
|
+
* This method performs the following steps for each pool:
|
|
773
|
+
* 1. Fetches deposit, withdrawal, and ragequit events for each pool
|
|
774
|
+
* 2. Processes deposit events and creates pool accounts
|
|
775
|
+
* 3. Processes withdrawal events and adds commitments to pool accounts
|
|
776
|
+
* 4. Processes ragequit events and adds ragequit to pool accounts
|
|
777
|
+
*
|
|
778
|
+
* @returns The initialized AccountService instance and array of errors if any pool events fetching fails
|
|
779
|
+
*
|
|
780
|
+
* if any pool events fetching fails, the account will be initialized without the events for that pool
|
|
781
|
+
* user can then call to this method again with the same account and missing pools to fetch the missing events
|
|
782
|
+
*
|
|
783
|
+
* @throws {AccountError} If account state reconstruction fails or if duplicate pools are found
|
|
784
|
+
*/
|
|
785
|
+
static async initializeWithEvents(
|
|
786
|
+
dataService: DataService,
|
|
787
|
+
source:
|
|
788
|
+
| {
|
|
789
|
+
mnemonic: string;
|
|
790
|
+
}
|
|
791
|
+
| {
|
|
792
|
+
service: AccountService;
|
|
793
|
+
},
|
|
794
|
+
pools: PoolInfo[]
|
|
795
|
+
): Promise<{ account: AccountService; errors: PoolEventsError[] }> {
|
|
796
|
+
// Log the start of the history retrieval process
|
|
797
|
+
const logger = new Logger({ prefix: "Account" });
|
|
798
|
+
logger.info(`Fetching events for pools`, { poolLength: pools.length });
|
|
799
|
+
|
|
800
|
+
// verify that pools don't contain duplicates based on scope
|
|
801
|
+
const uniqueScopes = new Set<bigint>();
|
|
802
|
+
for (const pool of pools) {
|
|
803
|
+
if (uniqueScopes.has(pool.scope)) {
|
|
804
|
+
throw AccountError.duplicatePools(pool.scope);
|
|
805
|
+
}
|
|
806
|
+
uniqueScopes.add(pool.scope);
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
const errors: PoolEventsError[] = [];
|
|
810
|
+
const account = new AccountService(
|
|
811
|
+
dataService,
|
|
812
|
+
"mnemonic" in source
|
|
813
|
+
? { mnemonic: source.mnemonic }
|
|
814
|
+
: { account: source.service.account }
|
|
815
|
+
);
|
|
816
|
+
|
|
817
|
+
const events = await account.getEvents(pools);
|
|
818
|
+
|
|
819
|
+
for (const [scope, result] of events.entries()) {
|
|
820
|
+
if ("reason" in result) {
|
|
821
|
+
errors.push(result);
|
|
822
|
+
} else {
|
|
823
|
+
// Process deposit events an create pool accounts
|
|
824
|
+
account._processDepositEvents(scope, result.depositEvents);
|
|
825
|
+
|
|
826
|
+
// Process withdrawal events and add commitments to pool accounts
|
|
827
|
+
account._processWithdrawalEvents(scope, result.withdrawalEvents);
|
|
828
|
+
|
|
829
|
+
// Process ragequit events and add ragequit to pool accounts
|
|
830
|
+
account._processRagequitEvents(scope, result.ragequitEvents);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
return { account, errors };
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
/**
|
|
838
|
+
* @deprecated Use `initializeWithEvents` for instantiating an account with history reconstruction
|
|
839
|
+
* Retrieves the history of deposits and withdrawals for the given pools.
|
|
840
|
+
*
|
|
841
|
+
* @param pools - Array of pool configurations to sync history for
|
|
842
|
+
*
|
|
843
|
+
* @remarks
|
|
844
|
+
* This method performs the following steps:
|
|
845
|
+
* 1. Initializes pool accounts for each pool if they don't exist
|
|
846
|
+
* 2. For each pool, fetches deposit events and reconstructs accounts
|
|
847
|
+
* 3. Processes withdrawals and ragequits to update account state
|
|
848
|
+
*
|
|
849
|
+
* The account reconstruction is deterministic based on the master keys,
|
|
850
|
+
* allowing the full state to be recovered from on-chain events.
|
|
851
|
+
*
|
|
852
|
+
* @throws {DataError} If event fetching fails
|
|
853
|
+
* @throws {AccountError} If account state reconstruction fails
|
|
854
|
+
*/
|
|
855
|
+
public async retrieveHistory(pools: PoolInfo[]): Promise<void> {
|
|
856
|
+
// Log the start of the history retrieval process
|
|
857
|
+
this.logger.info(`Fetching events for ${pools.length} pools`);
|
|
858
|
+
|
|
859
|
+
// Initialize pool accounts map for each pool if it doesn't exist
|
|
860
|
+
for (const pool of pools) {
|
|
861
|
+
if (!this.account.poolAccounts.has(pool.scope)) {
|
|
862
|
+
this.account.poolAccounts.set(pool.scope, []);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// Process all pools in parallel for better performance
|
|
867
|
+
await Promise.all(
|
|
868
|
+
pools.map(async (pool) => {
|
|
869
|
+
// Log which pool is being processed
|
|
870
|
+
this.logger.info(
|
|
871
|
+
`Processing pool ${pool.address} on chain ${pool.chainId} from block ${pool.deploymentBlock}`
|
|
872
|
+
);
|
|
873
|
+
|
|
874
|
+
// Fetch all deposit events for this pool
|
|
875
|
+
const deposits = await this.dataService.getDeposits(pool);
|
|
876
|
+
|
|
877
|
+
this.logger.info(
|
|
878
|
+
`Found ${deposits.length} deposits for pool ${pool.address}`
|
|
879
|
+
);
|
|
880
|
+
|
|
881
|
+
// Create a map of deposits by precommitment for efficient lookup
|
|
882
|
+
const depositMap = new Map<Hash, DepositEvent>();
|
|
883
|
+
for (const deposit of deposits) {
|
|
884
|
+
if (!depositMap.has(deposit.precommitment)) {
|
|
885
|
+
depositMap.set(deposit.precommitment, deposit);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// Track found deposits for logging and debugging
|
|
890
|
+
const foundDeposits: Array<{
|
|
891
|
+
index: bigint;
|
|
892
|
+
nullifier: Secret;
|
|
893
|
+
secret: Secret;
|
|
894
|
+
pool: PoolInfo;
|
|
895
|
+
deposit: (typeof deposits)[0];
|
|
896
|
+
}> = [];
|
|
897
|
+
|
|
898
|
+
// Start with index 0 and try to find deposits deterministically
|
|
899
|
+
let index = BigInt(0);
|
|
900
|
+
let firstDepositBlock: bigint | undefined;
|
|
901
|
+
|
|
902
|
+
// Deterministically generate deposit secrets and check if they match on-chain deposits
|
|
903
|
+
while (true) {
|
|
904
|
+
// Generate nullifier, secret, and precommitment for this index
|
|
905
|
+
const nullifier = this._genDepositNullifier(pool.scope, index);
|
|
906
|
+
const secret = this._genDepositSecret(pool.scope, index);
|
|
907
|
+
const precommitment = this._hashPrecommitment(nullifier, secret);
|
|
908
|
+
|
|
909
|
+
// Look for a deposit with this precommitment
|
|
910
|
+
const deposit = depositMap.get(precommitment);
|
|
911
|
+
if (!deposit) break; // No more deposits found, exit the loop
|
|
912
|
+
|
|
913
|
+
// Track the earliest deposit block for later withdrawal processing
|
|
914
|
+
if (!firstDepositBlock || deposit.blockNumber < firstDepositBlock) {
|
|
915
|
+
firstDepositBlock = deposit.blockNumber;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// Create a new pool account for this deposit
|
|
919
|
+
this.addPoolAccount(
|
|
920
|
+
pool.scope,
|
|
921
|
+
deposit.value,
|
|
922
|
+
nullifier,
|
|
923
|
+
secret,
|
|
924
|
+
deposit.label,
|
|
925
|
+
deposit.blockNumber,
|
|
926
|
+
deposit.transactionHash
|
|
927
|
+
);
|
|
928
|
+
|
|
929
|
+
// Track the found deposit
|
|
930
|
+
foundDeposits.push({ index, nullifier, secret, pool, deposit });
|
|
931
|
+
|
|
932
|
+
// Move to the next index
|
|
933
|
+
index++;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
// If no accounts were found for this scope, log and skip further processing
|
|
937
|
+
if (this.account.poolAccounts.get(pool.scope)!.length === 0) {
|
|
938
|
+
this.logger.info(
|
|
939
|
+
`No Pool Accounts were found for scope ${pool.scope}`
|
|
940
|
+
);
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
this.logger.info(
|
|
945
|
+
`Found ${foundDeposits.length} deposits for pool ${pool.address}`
|
|
946
|
+
);
|
|
947
|
+
})
|
|
948
|
+
);
|
|
949
|
+
|
|
950
|
+
// Process withdrawals and ragequits for all pools
|
|
951
|
+
// This is done after all deposits are processed to ensure we have the complete account state
|
|
952
|
+
await this._processWithdrawalsAndRagequits(pools);
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
/**
|
|
956
|
+
* Processes withdrawal events for all pools and updates account state.
|
|
957
|
+
*
|
|
958
|
+
* @param pools - Array of pool configurations to process withdrawals for
|
|
959
|
+
*
|
|
960
|
+
* @remarks
|
|
961
|
+
* This method performs the following steps for each pool:
|
|
962
|
+
* 1. Identifies the earliest deposit block for each scope
|
|
963
|
+
* 2. Fetches withdrawal and ragequit events from that block
|
|
964
|
+
* 3. Maps withdrawals by nullifier hash and ragequits by label for efficient lookup
|
|
965
|
+
* 4. For each account, reconstructs the withdrawal history by:
|
|
966
|
+
* - Generating nullifiers sequentially
|
|
967
|
+
* - Matching them against on-chain events
|
|
968
|
+
* - Adding matched withdrawals to the account state
|
|
969
|
+
* 5. Adds ragequit events to accounts if found
|
|
970
|
+
*
|
|
971
|
+
* @throws {DataError} If event fetching fails
|
|
972
|
+
* @private
|
|
973
|
+
*/
|
|
974
|
+
private async _processWithdrawalsAndRagequits(
|
|
975
|
+
pools: PoolInfo[]
|
|
976
|
+
): Promise<void> {
|
|
977
|
+
await Promise.all(
|
|
978
|
+
pools.map(async (pool) => {
|
|
979
|
+
const accounts = this.account.poolAccounts.get(pool.scope);
|
|
980
|
+
|
|
981
|
+
// Skip if no accounts for this scope
|
|
982
|
+
if (!accounts || accounts.length === 0) {
|
|
983
|
+
this.logger.info(
|
|
984
|
+
`No accounts found for pool ${pool.address} with scope ${pool.scope}`
|
|
985
|
+
);
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// Find the earliest deposit block for this scope
|
|
990
|
+
let firstDepositBlock = BigInt(Number.MAX_SAFE_INTEGER);
|
|
991
|
+
for (const account of accounts) {
|
|
992
|
+
if (account.deposit.blockNumber < firstDepositBlock) {
|
|
993
|
+
firstDepositBlock = account.deposit.blockNumber;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
// Fetch withdrawal and ragequit events from the first deposit block
|
|
998
|
+
const withdrawals = await this.dataService.getWithdrawals(
|
|
999
|
+
pool,
|
|
1000
|
+
firstDepositBlock
|
|
1001
|
+
);
|
|
1002
|
+
const ragequits = await this.dataService.getRagequits(
|
|
1003
|
+
pool,
|
|
1004
|
+
firstDepositBlock
|
|
1005
|
+
);
|
|
1006
|
+
|
|
1007
|
+
this.logger.info(
|
|
1008
|
+
`Found ${withdrawals.length} withdrawals for pool ${pool.address}`
|
|
1009
|
+
);
|
|
1010
|
+
|
|
1011
|
+
if (withdrawals.length === 0 && ragequits.length === 0) {
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
// Map withdrawals by spent nullifier for quick lookup
|
|
1016
|
+
const withdrawalMap = new Map<Hash, WithdrawalEvent>();
|
|
1017
|
+
for (const withdrawal of withdrawals) {
|
|
1018
|
+
withdrawalMap.set(withdrawal.spentNullifier, withdrawal);
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
// Map ragequits by label for quick lookup
|
|
1022
|
+
const ragequitMap = new Map<Hash, RagequitEvent>();
|
|
1023
|
+
for (const ragequit of ragequits) {
|
|
1024
|
+
ragequitMap.set(ragequit.label, ragequit);
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
// Process each account
|
|
1028
|
+
for (const account of accounts) {
|
|
1029
|
+
let currentCommitment = account.deposit;
|
|
1030
|
+
let index = BigInt(0);
|
|
1031
|
+
|
|
1032
|
+
// Continue processing withdrawals until no more are found
|
|
1033
|
+
while (true) {
|
|
1034
|
+
// Generate nullifier for this withdrawal
|
|
1035
|
+
const nullifierHash = poseidon([
|
|
1036
|
+
currentCommitment.nullifier,
|
|
1037
|
+
]) as Hash;
|
|
1038
|
+
|
|
1039
|
+
// Look for a withdrawal event with this nullifier
|
|
1040
|
+
const withdrawal = withdrawalMap.get(nullifierHash);
|
|
1041
|
+
if (!withdrawal) {
|
|
1042
|
+
break;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// Generate secret for this withdrawal
|
|
1046
|
+
const nullifier = this._genWithdrawalNullifier(
|
|
1047
|
+
account.label,
|
|
1048
|
+
index
|
|
1049
|
+
);
|
|
1050
|
+
const secret = this._genWithdrawalSecret(account.label, index);
|
|
1051
|
+
|
|
1052
|
+
// Add the withdrawal commitment to the account
|
|
1053
|
+
const newCommitment = this.addWithdrawalCommitment(
|
|
1054
|
+
currentCommitment,
|
|
1055
|
+
currentCommitment.value - withdrawal.withdrawn,
|
|
1056
|
+
nullifier,
|
|
1057
|
+
secret,
|
|
1058
|
+
withdrawal.blockNumber,
|
|
1059
|
+
withdrawal.transactionHash
|
|
1060
|
+
);
|
|
1061
|
+
|
|
1062
|
+
// Update current commitment to the newly created one
|
|
1063
|
+
currentCommitment = newCommitment;
|
|
1064
|
+
|
|
1065
|
+
// Increment index for next potential withdrawal
|
|
1066
|
+
index++;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
const ragequit = ragequitMap.get(account.label);
|
|
1070
|
+
if (ragequit) {
|
|
1071
|
+
this.addRagequitToAccount(account.label, ragequit);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
})
|
|
1075
|
+
);
|
|
1076
|
+
}
|
|
1077
|
+
}
|