@0xbow/privacy-pools-core-sdk 1.1.0 → 1.2.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.
Files changed (34) hide show
  1. package/README.md +102 -23
  2. package/dist/esm/{fetchArtifacts.esm-B6qveiM8.js → fetchArtifacts.esm-B0qaot8v.js} +2 -2
  3. package/dist/esm/{fetchArtifacts.esm-B6qveiM8.js.map → fetchArtifacts.esm-B0qaot8v.js.map} +1 -1
  4. package/dist/esm/{fetchArtifacts.node-BPQQPsnb.js → fetchArtifacts.node-PzijuwVc.js} +2 -2
  5. package/dist/esm/{fetchArtifacts.node-BPQQPsnb.js.map → fetchArtifacts.node-PzijuwVc.js.map} +1 -1
  6. package/dist/esm/{index-CRtEyHEf.js → index-BjOXETm6.js} +316 -316
  7. package/dist/esm/{index-CRtEyHEf.js.map → index-BjOXETm6.js.map} +1 -1
  8. package/dist/esm/index.mjs +1 -1
  9. package/dist/index.d.mts +81 -0
  10. package/dist/node/{fetchArtifacts.esm-z-KXbilc.js → fetchArtifacts.esm-B6uU6QdA.js} +2 -2
  11. package/dist/node/{fetchArtifacts.esm-z-KXbilc.js.map → fetchArtifacts.esm-B6uU6QdA.js.map} +1 -1
  12. package/dist/node/{fetchArtifacts.node-DvqhqpW9.js → fetchArtifacts.node-CZRy6KmV.js} +2 -2
  13. package/dist/node/{fetchArtifacts.node-DvqhqpW9.js.map → fetchArtifacts.node-CZRy6KmV.js.map} +1 -1
  14. package/dist/node/{index-BsmEKESv.js → index-b-U_m4Mi.js} +337 -337
  15. package/dist/node/{index-BsmEKESv.js.map → index-b-U_m4Mi.js.map} +1 -1
  16. package/dist/node/index.mjs +1 -1
  17. package/dist/types/circuits/artifactHashes.d.ts +19 -0
  18. package/dist/types/core/account.service.d.ts +79 -0
  19. package/dist/types/core/tmp.d.ts +1 -0
  20. package/dist/types/{fetchArtifacts.esm-DF01Zpo3.js → fetchArtifacts.esm-BKxGrC6w.js} +1 -1
  21. package/dist/types/{fetchArtifacts.node-BO6FBCAw.js → fetchArtifacts.node-kXMUDgNn.js} +1 -1
  22. package/dist/types/{index-CH7gk4sK.js → index-BwyNuaY0.js} +336 -336
  23. package/dist/types/index.js +1 -1
  24. package/dist/types/types/account.d.ts +2 -0
  25. package/package.json +1 -1
  26. package/src/circuits/artifactHashes.ts +74 -0
  27. package/src/circuits/circuits.impl.ts +8 -0
  28. package/src/core/account.service.ts +329 -35
  29. package/src/core/data.service.ts +3 -9
  30. package/src/core/tmp.ts +4 -0
  31. package/src/crypto.ts +5 -6
  32. package/src/types/account.ts +3 -1
  33. package/dist/types/keys.d.ts +0 -18
  34. package/src/keys.ts +0 -42
@@ -1,7 +1,7 @@
1
1
  import { mnemonicToAccount, privateKeyToAccount } from 'viem/accounts';
2
2
  import require$$0 from 'buffer';
3
3
  import require$$2 from 'assert';
4
- import { keccak256, encodeAbiParameters, numberToHex, createPublicClient, http, createWalletClient, getAddress, bytesToNumber as bytesToNumber$1, parseAbiItem } from 'viem';
4
+ import { bytesToBigInt, keccak256, encodeAbiParameters, numberToHex, createPublicClient, http, createWalletClient, getAddress, bytesToNumber, parseAbiItem } from 'viem';
5
5
  import { mainnet } from 'viem/chains';
6
6
 
7
7
  /**
@@ -16,281 +16,6 @@ const DEFAULT_LOG_FETCH_CONFIG = {
16
16
  retryBaseDelayMs: 1000,
17
17
  };
18
18
 
19
- const version = '2.22.14';
20
-
21
- let errorConfig = {
22
- getDocsUrl: ({ docsBaseUrl, docsPath = '', docsSlug, }) => docsPath
23
- ? `${docsBaseUrl ?? 'https://viem.sh'}${docsPath}${docsSlug ? `#${docsSlug}` : ''}`
24
- : undefined,
25
- version: `viem@${version}`,
26
- };
27
- class BaseError extends Error {
28
- constructor(shortMessage, args = {}) {
29
- const details = (() => {
30
- if (args.cause instanceof BaseError)
31
- return args.cause.details;
32
- if (args.cause?.message)
33
- return args.cause.message;
34
- return args.details;
35
- })();
36
- const docsPath = (() => {
37
- if (args.cause instanceof BaseError)
38
- return args.cause.docsPath || args.docsPath;
39
- return args.docsPath;
40
- })();
41
- const docsUrl = errorConfig.getDocsUrl?.({ ...args, docsPath });
42
- const message = [
43
- shortMessage || 'An error occurred.',
44
- '',
45
- ...(args.metaMessages ? [...args.metaMessages, ''] : []),
46
- ...(docsUrl ? [`Docs: ${docsUrl}`] : []),
47
- ...(details ? [`Details: ${details}`] : []),
48
- ...(errorConfig.version ? [`Version: ${errorConfig.version}`] : []),
49
- ].join('\n');
50
- super(message, args.cause ? { cause: args.cause } : undefined);
51
- Object.defineProperty(this, "details", {
52
- enumerable: true,
53
- configurable: true,
54
- writable: true,
55
- value: undefined
56
- });
57
- Object.defineProperty(this, "docsPath", {
58
- enumerable: true,
59
- configurable: true,
60
- writable: true,
61
- value: undefined
62
- });
63
- Object.defineProperty(this, "metaMessages", {
64
- enumerable: true,
65
- configurable: true,
66
- writable: true,
67
- value: undefined
68
- });
69
- Object.defineProperty(this, "shortMessage", {
70
- enumerable: true,
71
- configurable: true,
72
- writable: true,
73
- value: undefined
74
- });
75
- Object.defineProperty(this, "version", {
76
- enumerable: true,
77
- configurable: true,
78
- writable: true,
79
- value: undefined
80
- });
81
- Object.defineProperty(this, "name", {
82
- enumerable: true,
83
- configurable: true,
84
- writable: true,
85
- value: 'BaseError'
86
- });
87
- this.details = details;
88
- this.docsPath = docsPath;
89
- this.metaMessages = args.metaMessages;
90
- this.name = args.name ?? this.name;
91
- this.shortMessage = shortMessage;
92
- this.version = version;
93
- }
94
- walk(fn) {
95
- return walk(this, fn);
96
- }
97
- }
98
- function walk(err, fn) {
99
- if (fn?.(err))
100
- return err;
101
- if (err &&
102
- typeof err === 'object' &&
103
- 'cause' in err &&
104
- err.cause !== undefined)
105
- return walk(err.cause, fn);
106
- return fn ? null : err;
107
- }
108
-
109
- class SizeOverflowError extends BaseError {
110
- constructor({ givenSize, maxSize }) {
111
- super(`Size cannot exceed ${maxSize} bytes. Given size: ${givenSize} bytes.`, { name: 'SizeOverflowError' });
112
- }
113
- }
114
-
115
- class SizeExceedsPaddingSizeError extends BaseError {
116
- constructor({ size, targetSize, type, }) {
117
- super(`${type.charAt(0).toUpperCase()}${type
118
- .slice(1)
119
- .toLowerCase()} size (${size}) exceeds padding size (${targetSize}).`, { name: 'SizeExceedsPaddingSizeError' });
120
- }
121
- }
122
-
123
- function pad(hexOrBytes, { dir, size = 32 } = {}) {
124
- if (typeof hexOrBytes === 'string')
125
- return padHex(hexOrBytes, { dir, size });
126
- return padBytes(hexOrBytes, { dir, size });
127
- }
128
- function padHex(hex_, { dir, size = 32 } = {}) {
129
- if (size === null)
130
- return hex_;
131
- const hex = hex_.replace('0x', '');
132
- if (hex.length > size * 2)
133
- throw new SizeExceedsPaddingSizeError({
134
- size: Math.ceil(hex.length / 2),
135
- targetSize: size,
136
- type: 'hex',
137
- });
138
- return `0x${hex[dir === 'right' ? 'padEnd' : 'padStart'](size * 2, '0')}`;
139
- }
140
- function padBytes(bytes, { dir, size = 32 } = {}) {
141
- if (size === null)
142
- return bytes;
143
- if (bytes.length > size)
144
- throw new SizeExceedsPaddingSizeError({
145
- size: bytes.length,
146
- targetSize: size,
147
- type: 'bytes',
148
- });
149
- const paddedBytes = new Uint8Array(size);
150
- for (let i = 0; i < size; i++) {
151
- const padEnd = dir === 'right';
152
- paddedBytes[padEnd ? i : size - i - 1] =
153
- bytes[padEnd ? i : bytes.length - i - 1];
154
- }
155
- return paddedBytes;
156
- }
157
-
158
- function isHex(value, { strict = true } = {}) {
159
- if (!value)
160
- return false;
161
- if (typeof value !== 'string')
162
- return false;
163
- return strict ? /^0x[0-9a-fA-F]*$/.test(value) : value.startsWith('0x');
164
- }
165
-
166
- /**
167
- * @description Retrieves the size of the value (in bytes).
168
- *
169
- * @param value The value (hex or byte array) to retrieve the size of.
170
- * @returns The size of the value (in bytes).
171
- */
172
- function size(value) {
173
- if (isHex(value, { strict: false }))
174
- return Math.ceil((value.length - 2) / 2);
175
- return value.length;
176
- }
177
-
178
- function assertSize(hexOrBytes, { size: size$1 }) {
179
- if (size(hexOrBytes) > size$1)
180
- throw new SizeOverflowError({
181
- givenSize: size(hexOrBytes),
182
- maxSize: size$1,
183
- });
184
- }
185
- /**
186
- * Decodes a hex value into a bigint.
187
- *
188
- * - Docs: https://viem.sh/docs/utilities/fromHex#hextobigint
189
- *
190
- * @param hex Hex value to decode.
191
- * @param opts Options.
192
- * @returns BigInt value.
193
- *
194
- * @example
195
- * import { hexToBigInt } from 'viem'
196
- * const data = hexToBigInt('0x1a4', { signed: true })
197
- * // 420n
198
- *
199
- * @example
200
- * import { hexToBigInt } from 'viem'
201
- * const data = hexToBigInt('0x00000000000000000000000000000000000000000000000000000000000001a4', { size: 32 })
202
- * // 420n
203
- */
204
- function hexToBigInt(hex, opts = {}) {
205
- const { signed } = opts;
206
- if (opts.size)
207
- assertSize(hex, { size: opts.size });
208
- const value = BigInt(hex);
209
- if (!signed)
210
- return value;
211
- const size = (hex.length - 2) / 2;
212
- const max = (1n << (BigInt(size) * 8n - 1n)) - 1n;
213
- if (value <= max)
214
- return value;
215
- return value - BigInt(`0x${'f'.padStart(size * 2, 'f')}`) - 1n;
216
- }
217
- /**
218
- * Decodes a hex string into a number.
219
- *
220
- * - Docs: https://viem.sh/docs/utilities/fromHex#hextonumber
221
- *
222
- * @param hex Hex value to decode.
223
- * @param opts Options.
224
- * @returns Number value.
225
- *
226
- * @example
227
- * import { hexToNumber } from 'viem'
228
- * const data = hexToNumber('0x1a4')
229
- * // 420
230
- *
231
- * @example
232
- * import { hexToNumber } from 'viem'
233
- * const data = hexToBigInt('0x00000000000000000000000000000000000000000000000000000000000001a4', { size: 32 })
234
- * // 420
235
- */
236
- function hexToNumber(hex, opts = {}) {
237
- return Number(hexToBigInt(hex, opts));
238
- }
239
-
240
- const hexes = /*#__PURE__*/ Array.from({ length: 256 }, (_v, i) => i.toString(16).padStart(2, '0'));
241
- /**
242
- * Encodes a bytes array into a hex string
243
- *
244
- * - Docs: https://viem.sh/docs/utilities/toHex#bytestohex
245
- *
246
- * @param value Value to encode.
247
- * @param opts Options.
248
- * @returns Hex value.
249
- *
250
- * @example
251
- * import { bytesToHex } from 'viem'
252
- * const data = bytesToHex(Uint8Array.from([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33])
253
- * // '0x48656c6c6f20576f726c6421'
254
- *
255
- * @example
256
- * import { bytesToHex } from 'viem'
257
- * const data = bytesToHex(Uint8Array.from([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33]), { size: 32 })
258
- * // '0x48656c6c6f20576f726c64210000000000000000000000000000000000000000'
259
- */
260
- function bytesToHex(value, opts = {}) {
261
- let string = '';
262
- for (let i = 0; i < value.length; i++) {
263
- string += hexes[value[i]];
264
- }
265
- const hex = `0x${string}`;
266
- if (typeof opts.size === 'number') {
267
- assertSize(hex, { size: opts.size });
268
- return pad(hex, { dir: 'right', size: opts.size });
269
- }
270
- return hex;
271
- }
272
-
273
- /**
274
- * Decodes a byte array into a number.
275
- *
276
- * - Docs: https://viem.sh/docs/utilities/fromBytes#bytestonumber
277
- *
278
- * @param bytes Byte array to decode.
279
- * @param opts Options.
280
- * @returns Number value.
281
- *
282
- * @example
283
- * import { bytesToNumber } from 'viem'
284
- * const data = bytesToNumber(new Uint8Array([1, 164]))
285
- * // 420
286
- */
287
- function bytesToNumber(bytes, opts = {}) {
288
- if (typeof opts.size !== 'undefined')
289
- assertSize(bytes, { size: opts.size });
290
- const hex = bytesToHex(bytes, opts);
291
- return hexToNumber(hex, opts);
292
- }
293
-
294
19
  var commonjsGlobal$1 = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
295
20
 
296
21
  var hashing = {};
@@ -45595,10 +45320,10 @@ function generateMasterKeys(mnemonic) {
45595
45320
  if (!mnemonic) {
45596
45321
  throw new PrivacyPoolError(ErrorCode$1.INVALID_VALUE, "Invalid input: mnemonic phrase is required.");
45597
45322
  }
45598
- const key1 = bytesToNumber(mnemonicToAccount(mnemonic, { accountIndex: 0 }).getHdKey().privateKey);
45599
- const key2 = bytesToNumber(mnemonicToAccount(mnemonic, { accountIndex: 1 }).getHdKey().privateKey);
45600
- const masterNullifier = hashingExports.poseidon([BigInt(key1)]);
45601
- const masterSecret = hashingExports.poseidon([BigInt(key2)]);
45323
+ const key1 = bytesToBigInt(mnemonicToAccount(mnemonic, { accountIndex: 0 }).getHdKey().privateKey);
45324
+ const key2 = bytesToBigInt(mnemonicToAccount(mnemonic, { accountIndex: 1 }).getHdKey().privateKey);
45325
+ const masterNullifier = hashingExports.poseidon([key1]);
45326
+ const masterSecret = hashingExports.poseidon([key2]);
45602
45327
  return { masterNullifier, masterSecret };
45603
45328
  }
45604
45329
  /**
@@ -45815,10 +45540,62 @@ const circuitToAsset = {
45815
45540
 
45816
45541
  async function importFetchVersionedArtifact(isBrowser) {
45817
45542
  if (isBrowser) {
45818
- return import('./fetchArtifacts.esm-B6qveiM8.js');
45543
+ return import('./fetchArtifacts.esm-B0qaot8v.js');
45819
45544
  }
45820
45545
  else {
45821
- return import('./fetchArtifacts.node-BPQQPsnb.js');
45546
+ return import('./fetchArtifacts.node-PzijuwVc.js');
45547
+ }
45548
+ }
45549
+
45550
+ /**
45551
+ * Expected SHA-256 hex digests for every downloaded circuit artifact.
45552
+ *
45553
+ * vkey and zkey hashes are derived from the trusted-setup ceremony outputs
45554
+ * committed in packages/circuits/trusted-setup/final-keys/.
45555
+ *
45556
+ * wasm hashes are derived from the compiled circuit outputs
45557
+ * in packages/circuits/build/.
45558
+ *
45559
+ * Every artifact downloaded by the SDK MUST have a hash entry here.
45560
+ * verifyArtifactIntegrity throws if a hash is missing — refusing to
45561
+ * load unverified artifacts is the correct security posture.
45562
+ */
45563
+ const ARTIFACT_HASHES = {
45564
+ [CircuitName$1.Commitment]: {
45565
+ wasm: "254d2130607182fd6fd1aee67971526b13cfe178c88e360da96dce92663828d8",
45566
+ vkey: "7d48b4eb3dedc12fb774348287b587f0c18c3c7254cd60e9cf0f8b3636a570d8",
45567
+ zkey: "494ae92d64098fda2a5649690ddc5821fcd7449ca5fe8ef99ee7447544d7e1f3",
45568
+ },
45569
+ [CircuitName$1.Withdraw]: {
45570
+ wasm: "36cda22791def3d520a55c0fc808369cd5849532a75fab65686e666ed3d55c10",
45571
+ vkey: "666bd0983b20c1611543b04f7712e067fbe8cad69f07ada8a310837ff398d21e",
45572
+ zkey: "2a893b42174c813566e5c40c715a8b90cd49fc4ecf384e3a6024158c3d6de677",
45573
+ },
45574
+ [CircuitName$1.MerkleTree]: {},
45575
+ };
45576
+ // Freeze the manifest so runtime code cannot swap out trusted hashes.
45577
+ for (const circuitHashes of Object.values(ARTIFACT_HASHES)) {
45578
+ if (circuitHashes != null) {
45579
+ Object.freeze(circuitHashes);
45580
+ }
45581
+ }
45582
+ Object.freeze(ARTIFACT_HASHES);
45583
+ async function sha256Hex(data) {
45584
+ const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", data);
45585
+ return Array.from(new Uint8Array(hashBuffer))
45586
+ .map((b) => b.toString(16).padStart(2, "0"))
45587
+ .join("");
45588
+ }
45589
+ async function verifyArtifactIntegrity(circuitName, artifactType, data) {
45590
+ const expectedHash = ARTIFACT_HASHES[circuitName]?.[artifactType];
45591
+ if (expectedHash === undefined) {
45592
+ throw new Error(`No integrity hash registered for ${circuitName}.${artifactType}. ` +
45593
+ `Refusing to load unverified artifact.`);
45594
+ }
45595
+ const actualHash = await sha256Hex(data);
45596
+ if (actualHash !== expectedHash) {
45597
+ throw new Error(`Integrity check failed for ${circuitName}.${artifactType}: ` +
45598
+ `expected ${expectedHash}, got ${actualHash}`);
45822
45599
  }
45823
45600
  }
45824
45601
 
@@ -45935,6 +45712,11 @@ class Circuits {
45935
45712
  this._fetchVersionedArtifact(["artifacts", assetName.vkey].join("/")),
45936
45713
  this._fetchVersionedArtifact(["artifacts", assetName.zkey].join("/")),
45937
45714
  ]);
45715
+ await Promise.all([
45716
+ verifyArtifactIntegrity(circuitName, "wasm", wasm),
45717
+ verifyArtifactIntegrity(circuitName, "vkey", vkey),
45718
+ verifyArtifactIntegrity(circuitName, "zkey", zkey),
45719
+ ]);
45938
45720
  return { wasm, vkey, zkey };
45939
45721
  }
45940
45722
  /**
@@ -72069,6 +71851,38 @@ class AccountService {
72069
71851
  this.account = config.account;
72070
71852
  }
72071
71853
  }
71854
+ /**
71855
+ * Initializes a new account from a mnemonic phrase for the legacy account.
71856
+ *
71857
+ * @param mnemonic - The mnemonic phrase to derive keys from
71858
+ * @returns A new PrivacyPoolAccount with derived master keys
71859
+ *
71860
+ * @remarks
71861
+ * This method derives two master keys from the mnemonic:
71862
+ * 1. A master nullifier key from account index 0
71863
+ * 2. A master secret key from account index 1
71864
+ * These keys are used to deterministically generate nullifiers and secrets for deposits and withdrawals.
71865
+ *
71866
+ * @throws {AccountError} If account initialization fails
71867
+ * @private
71868
+ */
71869
+ static _initializeLegacyAccount(mnemonic) {
71870
+ try {
71871
+ const masterNullifierSeed = bytesToNumber(mnemonicToAccount(mnemonic, { accountIndex: 0 }).getHdKey().privateKey);
71872
+ const masterSecretSeed = bytesToNumber(mnemonicToAccount(mnemonic, { accountIndex: 1 }).getHdKey().privateKey);
71873
+ const masterNullifier = hashingExports.poseidon([BigInt(masterNullifierSeed)]);
71874
+ const masterSecret = hashingExports.poseidon([BigInt(masterSecretSeed)]);
71875
+ return {
71876
+ masterKeys: [masterNullifier, masterSecret],
71877
+ poolAccounts: new Map(),
71878
+ creationTimestamp: 0n,
71879
+ lastUpdateTimestamp: 0n,
71880
+ };
71881
+ }
71882
+ catch (error) {
71883
+ throw AccountError.accountInitializationFailed(error instanceof Error ? error.message : "Unknown error");
71884
+ }
71885
+ }
72072
71886
  /**
72073
71887
  * Initializes a new account from a mnemonic phrase.
72074
71888
  *
@@ -72087,10 +71901,7 @@ class AccountService {
72087
71901
  _initializeAccount(mnemonic) {
72088
71902
  try {
72089
71903
  this.logger.debug("Initializing account with mnemonic");
72090
- const masterNullifierSeed = bytesToNumber$1(mnemonicToAccount(mnemonic, { accountIndex: 0 }).getHdKey().privateKey);
72091
- const masterSecretSeed = bytesToNumber$1(mnemonicToAccount(mnemonic, { accountIndex: 1 }).getHdKey().privateKey);
72092
- const masterNullifier = hashingExports.poseidon([BigInt(masterNullifierSeed)]);
72093
- const masterSecret = hashingExports.poseidon([BigInt(masterSecretSeed)]);
71904
+ const { masterNullifier, masterSecret } = generateMasterKeys(mnemonic);
72094
71905
  return {
72095
71906
  masterKeys: [masterNullifier, masterSecret],
72096
71907
  poolAccounts: new Map(),
@@ -72189,7 +72000,7 @@ class AccountService {
72189
72000
  const nonZeroCommitments = [];
72190
72001
  for (const account of accounts) {
72191
72002
  // Skip accounts that have been ragequit
72192
- if (account.ragequit) {
72003
+ if (account.ragequit || account.isMigrated) {
72193
72004
  continue;
72194
72005
  }
72195
72006
  const lastCommitment = account.children.length > 0
@@ -72345,6 +72156,57 @@ class AccountService {
72345
72156
  this.logger.info(`Added new commitment with value ${value} to account with label ${parentCommitment.label}`);
72346
72157
  return newCommitment;
72347
72158
  }
72159
+ /**
72160
+ * Adds a new commitment to the account after migrate
72161
+ *
72162
+ * @param parentCommitment - The commitment that was spent
72163
+ * @param value - The remaining value after spending
72164
+ * @param nullifier - The nullifier used for migrate
72165
+ * @param secret - The secret used for migrate
72166
+ * @param blockNumber - The block number of the withdrawal
72167
+ * @param txHash - The transaction hash of the withdrawal
72168
+ * @returns The new commitment
72169
+ *
72170
+ * @remarks
72171
+ * This method finds the account containing the parent commitment, creates a new
72172
+ * commitment with the provided parameters, and adds it to the account's children.
72173
+ * The new commitment inherits the label from the parent commitment.
72174
+ *
72175
+ * @throws {AccountError} If no account is found for the commitment
72176
+ */
72177
+ addMigrationCommitment(parentCommitment, value, nullifier, secret, blockNumber, txHash) {
72178
+ let foundAccount;
72179
+ let foundScope;
72180
+ for (const [scope, accounts] of this.account.poolAccounts.entries()) {
72181
+ foundAccount = accounts.find((account) => {
72182
+ if (account.deposit.hash === parentCommitment.hash)
72183
+ return true;
72184
+ return account.children.some((child) => child.hash === parentCommitment.hash);
72185
+ });
72186
+ if (foundAccount) {
72187
+ foundScope = scope;
72188
+ break;
72189
+ }
72190
+ }
72191
+ if (!foundAccount || !foundScope) {
72192
+ throw AccountError.commitmentNotFound(parentCommitment.hash);
72193
+ }
72194
+ const precommitment = this._hashPrecommitment(nullifier, secret);
72195
+ const newCommitment = {
72196
+ hash: this._hashCommitment(value, parentCommitment.label, precommitment),
72197
+ value,
72198
+ label: parentCommitment.label,
72199
+ nullifier,
72200
+ secret,
72201
+ blockNumber,
72202
+ txHash,
72203
+ isMigration: true
72204
+ };
72205
+ foundAccount.children.push(newCommitment);
72206
+ foundAccount.isMigrated = true;
72207
+ this.logger.info(`Added new commitment with value ${value} to account with label ${parentCommitment.label}`);
72208
+ return newCommitment;
72209
+ }
72348
72210
  /**
72349
72211
  * Adds a ragequit event to an existing pool account
72350
72212
  *
@@ -72496,9 +72358,11 @@ class AccountService {
72496
72358
  });
72497
72359
  }
72498
72360
  else {
72499
- events.set(result.reason.details?.scope, {
72361
+ const errorWithDetails = result.reason;
72362
+ const scope = errorWithDetails.details?.scope;
72363
+ events.set(scope, {
72500
72364
  reason: result.reason.message,
72501
- scope: result.reason.details?.scope,
72365
+ scope: scope,
72502
72366
  });
72503
72367
  }
72504
72368
  }
@@ -72512,11 +72376,11 @@ class AccountService {
72512
72376
  * @param depositEvents - The map of deposit events
72513
72377
  *
72514
72378
  */
72515
- _processDepositEvents(scope, depositEvents) {
72379
+ _processDepositEvents(scope, depositEvents, startIndex = 0n) {
72516
72380
  const MAX_CONSECUTIVE_MISSES = 10; // Large enough to avoid tx failures
72517
72381
  const foundIndices = new Set();
72518
72382
  let consecutiveMisses = 0;
72519
- for (let index = BigInt(0);; index++) {
72383
+ for (let index = startIndex;; index++) {
72520
72384
  // Generate nullifier, secret, and precommitment for this index
72521
72385
  const { nullifier, secret, precommitment } = this.createDepositSecrets(scope, index);
72522
72386
  // Look for a deposit with this precommitment
@@ -72567,8 +72431,8 @@ class AccountService {
72567
72431
  // Process each account in parallel for better performance
72568
72432
  for (const account of accounts) {
72569
72433
  let currentCommitment = account.deposit;
72570
- let index = BigInt(0);
72571
- // Continue processing withdrawals until no more are found secuentially
72434
+ let index = BigInt(account.children.length);
72435
+ // Continue processing withdrawals until no more are found sequentially
72572
72436
  while (true) {
72573
72437
  // Generate nullifier for this withdrawal
72574
72438
  const nullifierHash = hashingExports.poseidon([currentCommitment.nullifier]);
@@ -72577,13 +72441,26 @@ class AccountService {
72577
72441
  if (!withdrawal) {
72578
72442
  break;
72579
72443
  }
72444
+ const remainingValue = currentCommitment.value - withdrawal.withdrawn;
72580
72445
  // Generate secret for this withdrawal
72581
72446
  const nullifier = this._genWithdrawalNullifier(account.label, index);
72582
72447
  const secret = this._genWithdrawalSecret(account.label, index);
72583
- // Add the withdrawal commitment to the account
72584
- const newCommitment = this.addWithdrawalCommitment(currentCommitment, currentCommitment.value - withdrawal.withdrawn, nullifier, secret, withdrawal.blockNumber, withdrawal.transactionHash);
72585
- // Update current commitment to the newly created one
72586
- currentCommitment = newCommitment;
72448
+ const precommitment = this._hashPrecommitment(nullifier, secret);
72449
+ const accountCommitment = this._hashCommitment(remainingValue, currentCommitment.label, precommitment);
72450
+ // If the locally-computed hash doesn't match the on-chain commitment,
72451
+ // the withdrawal was performed with different keys (e.g. migration from
72452
+ // legacy to safe keys). Mark the child as unspendable from this account.
72453
+ if (accountCommitment !== withdrawal.newCommitment) {
72454
+ this.logger.info(`Withdrawal commitment hash mismatch — marking as unspendable (migrated with different keys)`, { label: currentCommitment.label, expected: withdrawal.newCommitment, computed: accountCommitment });
72455
+ // Add the withdrawal commitment to the account
72456
+ const migrationCommitment = this.addMigrationCommitment(currentCommitment, remainingValue, nullifier, secret, withdrawal.blockNumber, withdrawal.transactionHash);
72457
+ currentCommitment = migrationCommitment;
72458
+ }
72459
+ else {
72460
+ // Add the withdrawal commitment to the account
72461
+ const withdrawalCommitment = this.addWithdrawalCommitment(currentCommitment, remainingValue, nullifier, secret, withdrawal.blockNumber, withdrawal.transactionHash);
72462
+ currentCommitment = withdrawalCommitment;
72463
+ }
72587
72464
  // Increment index for next potential withdrawal
72588
72465
  index++;
72589
72466
  }
@@ -72618,6 +72495,59 @@ class AccountService {
72618
72495
  }
72619
72496
  }
72620
72497
  }
72498
+ /**
72499
+ * Discovers commitments that were migrated from legacy accounts via 0-value withdrawal.
72500
+ *
72501
+ * @param scope - The scope of the pool
72502
+ * @param legacyAccounts - The legacy pool accounts for this scope
72503
+ * @param withdrawalEvents - The map of withdrawal events (keyed by spentNullifier)
72504
+ *
72505
+ * @remarks
72506
+ * When a legacy account performs a 0-value withdrawal to rotate keys (migration),
72507
+ * the resulting on-chain commitment is created with safe keys. This method finds
72508
+ * those commitments by:
72509
+ * 1. Identifying legacy accounts with the `isMigrated` flag (set by `addMigrationCommitment`)
72510
+ * 2. Computing the expected commitment hash using safe keys at withdrawal index 0
72511
+ * 3. Verifying the hash exists in on-chain withdrawal events
72512
+ * 4. Adding verified commitments as new safe pool accounts
72513
+ *
72514
+ * @private
72515
+ */
72516
+ _discoverMigratedCommitments(scope, legacyAccounts, withdrawalEvents) {
72517
+ // Build reverse lookup: newCommitment hash → WithdrawalEvent
72518
+ const newCommitmentMap = new Map();
72519
+ for (const event of withdrawalEvents.values()) {
72520
+ newCommitmentMap.set(event.newCommitment, event);
72521
+ }
72522
+ for (const legacyAccount of legacyAccounts) {
72523
+ // Skip if not flagged as migrated (set by addMigrationCommitment)
72524
+ if (!legacyAccount.isMigrated)
72525
+ continue;
72526
+ const migrationChild = legacyAccount.children.find(c => c.isMigration);
72527
+ if (!migrationChild)
72528
+ continue;
72529
+ const label = legacyAccount.label;
72530
+ // The migration child's value is the remaining value carried forward.
72531
+ // Zero-value migrations (full withdrawal + key rotation) are valid and
72532
+ // must still be registered so that poolAccounts.length reflects the
72533
+ // correct slot count for deposit index alignment in step C.
72534
+ const remainingValue = migrationChild.value;
72535
+ // Generate safe nullifier/secret at withdrawal index 0
72536
+ const nullifier = this._genWithdrawalNullifier(label, 0n);
72537
+ const secret = this._genWithdrawalSecret(label, 0n);
72538
+ // Compute expected commitment hash
72539
+ const precommitment = this._hashPrecommitment(nullifier, secret);
72540
+ const expectedHash = this._hashCommitment(remainingValue, label, precommitment);
72541
+ // Verify hash exists in withdrawal events' newCommitment
72542
+ const withdrawalEvent = newCommitmentMap.get(expectedHash);
72543
+ if (!withdrawalEvent)
72544
+ continue;
72545
+ // Verified — add as a new safe pool account
72546
+ const newAccount = this.addPoolAccount(scope, remainingValue, nullifier, secret, label, withdrawalEvent.blockNumber, withdrawalEvent.transactionHash);
72547
+ this.addWithdrawalCommitment(newAccount.deposit, remainingValue, nullifier, secret, withdrawalEvent.blockNumber, withdrawalEvent.transactionHash);
72548
+ this.logger.info(`Discovered migrated commitment for label ${label} with value ${remainingValue}`);
72549
+ }
72550
+ }
72621
72551
  /**
72622
72552
  * Initializes an AccountService instance with events for a given set of pools
72623
72553
  *
@@ -72651,25 +72581,99 @@ class AccountService {
72651
72581
  }
72652
72582
  uniqueScopes.add(pool.scope);
72653
72583
  }
72584
+ // Retry path (non-migration): reuse the existing service's account and
72585
+ // only process pools whose scopes haven't been fully processed yet.
72586
+ // Already-processed scopes are skipped to avoid duplicate deposits and
72587
+ // withdrawal misclassification.
72588
+ //
72589
+ // This path performs simple deposit/withdrawal/ragequit processing only
72590
+ // — no migration discovery. For migration-aware retries, the caller
72591
+ // should re-invoke with { mnemonic } scoped to only the failed pools;
72592
+ // the mnemonic path builds both safe and legacy accounts from scratch
72593
+ // with no shared references.
72594
+ if (!('mnemonic' in source)) {
72595
+ const account = new AccountService(dataService, { account: source.service.account });
72596
+ const processedScopes = source.service.account.poolAccounts;
72597
+ const newPools = pools.filter((p) => !processedScopes.has(p.scope));
72598
+ const errors = await account._processEvents(newPools);
72599
+ return { account, errors };
72600
+ }
72601
+ // Mnemonic path: phased processing with migration discovery
72602
+ const account = new AccountService(dataService, { mnemonic: source.mnemonic });
72603
+ const legacyPrivacyPoolAccount = AccountService._initializeLegacyAccount(source.mnemonic);
72604
+ const legacyAccount = new AccountService(dataService, { account: legacyPrivacyPoolAccount });
72605
+ const errors = await account._processEvents(pools, legacyAccount);
72606
+ return { account, legacyAccount, errors };
72607
+ }
72608
+ /**
72609
+ * Fetches and processes events for a set of pools.
72610
+ *
72611
+ * When a legacyAccount is provided, the full migration-aware pipeline runs
72612
+ * for each scope:
72613
+ * 1. Legacy account: process deposits and withdrawals (to detect migrations)
72614
+ * 2. Safe account: discover migrated commitments from the legacy accounts
72615
+ * 3. Safe account (this): process deposits (starting after migrated accounts)
72616
+ * 4. Safe account: process withdrawals (now includes migrated accounts)
72617
+ * 5. Both accounts: process ragequits
72618
+ *
72619
+ * Migration discovery (step 2) must run before safe deposit scanning (step 3)
72620
+ * so that the migrated account count can be used as the starting index.
72621
+ * Post-migration deposits use poolAccounts.length as their index, which
72622
+ * sits right after the migrated slots; scanning from 0 would hit
72623
+ * MAX_CONSECUTIVE_MISSES on the legacy-key indices and never reach them.
72624
+ *
72625
+ * Without a legacyAccount, only steps 3, 4, and 5 run (simple processing).
72626
+ *
72627
+ * Per-scope errors are caught and returned rather than thrown, and any
72628
+ * partial state left by a mid-scope failure is cleaned from both accounts
72629
+ * so that a subsequent retry starts fresh for that scope.
72630
+ */
72631
+ async _processEvents(pools, legacyAccount) {
72654
72632
  const errors = [];
72655
- const account = new AccountService(dataService, "mnemonic" in source
72656
- ? { mnemonic: source.mnemonic }
72657
- : { account: source.service.account });
72658
- const events = await account.getEvents(pools);
72633
+ const events = await this.getEvents(pools);
72659
72634
  for (const [scope, result] of events.entries()) {
72660
72635
  if ("reason" in result) {
72661
72636
  errors.push(result);
72662
72637
  }
72663
72638
  else {
72664
- // Process deposit events an create pool accounts
72665
- account._processDepositEvents(scope, result.depositEvents);
72666
- // Process withdrawal events and add commitments to pool accounts
72667
- account._processWithdrawalEvents(scope, result.withdrawalEvents);
72668
- // Process ragequit events and add ragequit to pool accounts
72669
- account._processRagequitEvents(scope, result.ragequitEvents);
72639
+ try {
72640
+ // a. Legacy: process deposits + withdrawals
72641
+ if (legacyAccount) {
72642
+ legacyAccount._processDepositEvents(scope, result.depositEvents);
72643
+ legacyAccount._processWithdrawalEvents(scope, result.withdrawalEvents);
72644
+ }
72645
+ // b. Safe: discover migrated commitments from legacy accounts.
72646
+ // Must run before safe deposit scanning so that the migrated
72647
+ // account count can serve as the starting index for step (c),
72648
+ // avoiding a gap of consecutive misses over legacy-key indices.
72649
+ if (legacyAccount) {
72650
+ const legacyAccounts = legacyAccount.account.poolAccounts.get(scope) ?? [];
72651
+ this._discoverMigratedCommitments(scope, legacyAccounts, result.withdrawalEvents);
72652
+ }
72653
+ // c. Safe: process deposits, starting after any migrated accounts.
72654
+ // New deposits created after migration use poolAccounts.length as
72655
+ // their index, so they sit right after the migrated slots.
72656
+ const depositStartIndex = BigInt(this.account.poolAccounts.get(scope)?.length ?? 0);
72657
+ this._processDepositEvents(scope, result.depositEvents, depositStartIndex);
72658
+ // d. Safe: process withdrawals (now includes migrated accounts)
72659
+ this._processWithdrawalEvents(scope, result.withdrawalEvents);
72660
+ // e. Both: process ragequits
72661
+ if (legacyAccount) {
72662
+ legacyAccount._processRagequitEvents(scope, result.ragequitEvents);
72663
+ }
72664
+ this._processRagequitEvents(scope, result.ragequitEvents);
72665
+ }
72666
+ catch (e) {
72667
+ this.account.poolAccounts.delete(scope);
72668
+ legacyAccount?.account.poolAccounts.delete(scope);
72669
+ errors.push({
72670
+ reason: e instanceof Error ? e.message : String(e),
72671
+ scope,
72672
+ });
72673
+ }
72670
72674
  }
72671
72675
  }
72672
- return { account, errors };
72676
+ return errors;
72673
72677
  }
72674
72678
  /**
72675
72679
  * @deprecated Use `initializeWithEvents` for instantiating an account with history reconstruction
@@ -72928,7 +72932,7 @@ class DataService {
72928
72932
  depositor: depositor.toLowerCase(),
72929
72933
  commitment: commitment,
72930
72934
  label: label,
72931
- value: value || BigInt(0),
72935
+ value: value ?? BigInt(0),
72932
72936
  precommitment: precommitment,
72933
72937
  blockNumber: BigInt(typedLog.blockNumber),
72934
72938
  transactionHash: typedLog.transactionHash,
@@ -72980,11 +72984,7 @@ class DataService {
72980
72984
  throw DataError.invalidLog("withdrawal", "missing args");
72981
72985
  }
72982
72986
  const { _value: value, _spentNullifier: spentNullifier, _newCommitment: newCommitment, } = typedLog.args;
72983
- if (!value ||
72984
- !spentNullifier ||
72985
- !newCommitment ||
72986
- !typedLog.blockNumber ||
72987
- !typedLog.transactionHash) {
72987
+ if (value == null || !spentNullifier || !newCommitment || !typedLog.blockNumber || !typedLog.transactionHash) {
72988
72988
  throw DataError.invalidLog("withdrawal", "missing required fields");
72989
72989
  }
72990
72990
  return {
@@ -73052,7 +73052,7 @@ class DataService {
73052
73052
  ragequitter: ragequitter.toLowerCase(),
73053
73053
  commitment: commitment,
73054
73054
  label: label,
73055
- value: value || BigInt(0),
73055
+ value: value ?? BigInt(0),
73056
73056
  blockNumber: BigInt(typedLog.blockNumber),
73057
73057
  transactionHash: typedLog.transactionHash,
73058
73058
  };
@@ -73155,4 +73155,4 @@ class DataService {
73155
73155
  }
73156
73156
 
73157
73157
  export { AccountService as A, BlockchainProvider as B, CommitmentService as C, DataService as D, ErrorCode as E, FetchArtifact as F, InvalidRpcUrl as I, PrivacyPoolSDK as P, SDKError as S, WithdrawalService as W, DEFAULT_LOG_FETCH_CONFIG as a, generateDepositSecrets as b, generateWithdrawalSecrets as c, getCommitment as d, generateMerkleProof as e, bigintToHash as f, generateMasterKeys as g, hashPrecommitment as h, bigintToHex as i, calculateContext as j, Circuits as k, ContractInteractionsService as l, ProofError as m, ContractError as n, AccountError as o, CircuitName as p };
73158
- //# sourceMappingURL=index-CRtEyHEf.js.map
73158
+ //# sourceMappingURL=index-BjOXETm6.js.map