@0xbow/privacy-pools-core-sdk 0.1.21 → 0.1.22

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