@cloak.ag/sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,3707 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ ArtifactProverService: () => ArtifactProverService,
34
+ CLOAK_PROGRAM_ID: () => CLOAK_PROGRAM_ID,
35
+ CloakError: () => CloakError,
36
+ CloakSDK: () => CloakSDK,
37
+ DepositRecoveryService: () => DepositRecoveryService,
38
+ FIXED_FEE_LAMPORTS: () => FIXED_FEE_LAMPORTS,
39
+ IndexerService: () => IndexerService,
40
+ LAMPORTS_PER_SOL: () => LAMPORTS_PER_SOL,
41
+ LocalStorageAdapter: () => LocalStorageAdapter,
42
+ MemoryStorageAdapter: () => MemoryStorageAdapter,
43
+ ProverService: () => ProverService,
44
+ RelayService: () => RelayService,
45
+ VARIABLE_FEE_RATE: () => VARIABLE_FEE_RATE,
46
+ VERSION: () => VERSION,
47
+ bigintToBytes32: () => bigintToBytes32,
48
+ buildPublicInputsBytes: () => buildPublicInputsBytes,
49
+ bytesToHex: () => bytesToHex,
50
+ calculateFee: () => calculateFee2,
51
+ calculateRelayFee: () => calculateRelayFee,
52
+ computeCommitment: () => computeCommitment,
53
+ computeMerkleRoot: () => computeMerkleRoot,
54
+ computeNullifier: () => computeNullifier,
55
+ computeNullifierAsync: () => computeNullifierAsync,
56
+ computeNullifierSync: () => computeNullifierSync,
57
+ computeOutputsHash: () => computeOutputsHash,
58
+ computeOutputsHashAsync: () => computeOutputsHashAsync,
59
+ computeOutputsHashSync: () => computeOutputsHashSync,
60
+ computeSwapOutputsHash: () => computeSwapOutputsHash,
61
+ computeSwapOutputsHashAsync: () => computeSwapOutputsHashAsync,
62
+ computeSwapOutputsHashSync: () => computeSwapOutputsHashSync,
63
+ copyNoteToClipboard: () => copyNoteToClipboard,
64
+ createCloakError: () => createCloakError,
65
+ createDepositInstruction: () => createDepositInstruction,
66
+ deriveSpendKey: () => deriveSpendKey,
67
+ deriveViewKey: () => deriveViewKey,
68
+ detectNetworkFromRpcUrl: () => detectNetworkFromRpcUrl,
69
+ downloadNote: () => downloadNote,
70
+ encodeNoteSimple: () => encodeNoteSimple,
71
+ encryptNoteForRecipient: () => encryptNoteForRecipient,
72
+ exportKeys: () => exportKeys,
73
+ exportNote: () => exportNote,
74
+ exportWalletKeys: () => exportWalletKeys,
75
+ filterNotesByNetwork: () => filterNotesByNetwork,
76
+ filterWithdrawableNotes: () => filterWithdrawableNotes,
77
+ findNoteByCommitment: () => findNoteByCommitment,
78
+ formatAmount: () => formatAmount,
79
+ formatErrorForLogging: () => formatErrorForLogging,
80
+ generateCloakKeys: () => generateCloakKeys,
81
+ generateCommitment: () => generateCommitment,
82
+ generateCommitmentAsync: () => generateCommitmentAsync,
83
+ generateMasterSeed: () => generateMasterSeed,
84
+ generateNote: () => generateNote,
85
+ generateNoteFromWallet: () => generateNoteFromWallet,
86
+ getAddressExplorerUrl: () => getAddressExplorerUrl,
87
+ getDistributableAmount: () => getDistributableAmount2,
88
+ getExplorerUrl: () => getExplorerUrl,
89
+ getPublicKey: () => getPublicKey,
90
+ getPublicViewKey: () => getPublicViewKey,
91
+ getRecipientAmount: () => getRecipientAmount,
92
+ getRpcUrlForNetwork: () => getRpcUrlForNetwork,
93
+ getShieldPoolPDAs: () => getShieldPoolPDAs,
94
+ getViewKey: () => getViewKey,
95
+ hexToBigint: () => hexToBigint,
96
+ hexToBytes: () => hexToBytes,
97
+ importKeys: () => importKeys,
98
+ importWalletKeys: () => importWalletKeys,
99
+ isValidHex: () => isValidHex,
100
+ isValidRpcUrl: () => isValidRpcUrl,
101
+ isValidSolanaAddress: () => isValidSolanaAddress,
102
+ isWithdrawable: () => isWithdrawable,
103
+ keypairToAdapter: () => keypairToAdapter,
104
+ parseAmount: () => parseAmount,
105
+ parseNote: () => parseNote,
106
+ parseTransactionError: () => parseTransactionError,
107
+ poseidonHash: () => poseidonHash,
108
+ prepareEncryptedOutput: () => prepareEncryptedOutput,
109
+ prepareEncryptedOutputForRecipient: () => prepareEncryptedOutputForRecipient,
110
+ proofToBytes: () => proofToBytes,
111
+ pubkeyToLimbs: () => pubkeyToLimbs,
112
+ randomBytes: () => randomBytes,
113
+ scanNotesForWallet: () => scanNotesForWallet,
114
+ sendTransaction: () => sendTransaction,
115
+ serializeNote: () => serializeNote,
116
+ signTransaction: () => signTransaction,
117
+ splitTo2Limbs: () => splitTo2Limbs,
118
+ tryDecryptNote: () => tryDecryptNote,
119
+ updateNoteWithDeposit: () => updateNoteWithDeposit,
120
+ validateDepositParams: () => validateDepositParams,
121
+ validateNote: () => validateNote,
122
+ validateOutputsSum: () => validateOutputsSum,
123
+ validateTransfers: () => validateTransfers,
124
+ validateWalletConnected: () => validateWalletConnected,
125
+ validateWithdrawableNote: () => validateWithdrawableNote
126
+ });
127
+ module.exports = __toCommonJS(index_exports);
128
+
129
+ // src/core/CloakSDK.ts
130
+ var import_web36 = require("@solana/web3.js");
131
+
132
+ // src/core/types.ts
133
+ var CloakError = class extends Error {
134
+ constructor(message, category, retryable = false, originalError) {
135
+ super(message);
136
+ this.category = category;
137
+ this.retryable = retryable;
138
+ this.originalError = originalError;
139
+ this.name = "CloakError";
140
+ }
141
+ };
142
+
143
+ // src/core/keys.ts
144
+ var import_blake3 = require("@noble/hashes/blake3.js");
145
+ var import_tweetnacl = __toESM(require("tweetnacl"), 1);
146
+
147
+ // src/utils/crypto.ts
148
+ var import_web3 = require("@solana/web3.js");
149
+ var import_circomlibjs = require("circomlibjs");
150
+ var poseidon = null;
151
+ async function getPoseidon() {
152
+ if (!poseidon) {
153
+ poseidon = await (0, import_circomlibjs.buildPoseidon)();
154
+ }
155
+ return poseidon;
156
+ }
157
+ async function poseidonHash(inputs) {
158
+ const p = await getPoseidon();
159
+ const hash = p(inputs.map((x) => p.F.e(x)));
160
+ return p.F.toObject(hash);
161
+ }
162
+ function splitTo2Limbs(value) {
163
+ const mask = (1n << 128n) - 1n;
164
+ const lo = value & mask;
165
+ const hi = value >> 128n;
166
+ return [lo, hi];
167
+ }
168
+ function pubkeyToLimbs(pubkey) {
169
+ const bytes = pubkey instanceof import_web3.PublicKey ? pubkey.toBytes() : pubkey;
170
+ const value = BigInt("0x" + Buffer.from(bytes).toString("hex"));
171
+ return splitTo2Limbs(value);
172
+ }
173
+ async function computeMerkleRoot(leaf, pathElements, pathIndices) {
174
+ let current = leaf;
175
+ for (let i = 0; i < pathElements.length; i++) {
176
+ if (pathIndices[i] === 0) {
177
+ current = await poseidonHash([current, pathElements[i]]);
178
+ } else {
179
+ current = await poseidonHash([pathElements[i], current]);
180
+ }
181
+ }
182
+ return current;
183
+ }
184
+ function hexToBigint(hex) {
185
+ const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
186
+ return BigInt("0x" + cleanHex);
187
+ }
188
+ async function computeCommitment(amount, r, sk_spend) {
189
+ const [sk0, sk1] = splitTo2Limbs(sk_spend);
190
+ const [r0, r1] = splitTo2Limbs(r);
191
+ const pk_spend = await poseidonHash([sk0, sk1]);
192
+ return await poseidonHash([amount, r0, r1, pk_spend]);
193
+ }
194
+ async function generateCommitmentAsync(amountLamports, r, skSpend) {
195
+ const amount = BigInt(amountLamports);
196
+ const rValue = hexToBigint(bytesToHex(r));
197
+ const skValue = hexToBigint(bytesToHex(skSpend));
198
+ return await computeCommitment(amount, rValue, skValue);
199
+ }
200
+ function generateCommitment(_amountLamports, _r, _skSpend) {
201
+ throw new Error("generateCommitment is deprecated. Use generateCommitmentAsync instead.");
202
+ }
203
+ async function computeNullifier(sk_spend, leafIndex) {
204
+ const [sk0, sk1] = splitTo2Limbs(sk_spend);
205
+ return await poseidonHash([sk0, sk1, leafIndex]);
206
+ }
207
+ async function computeNullifierAsync(skSpend, leafIndex) {
208
+ const skValue = typeof skSpend === "string" ? hexToBigint(skSpend) : hexToBigint(bytesToHex(skSpend));
209
+ return await computeNullifier(skValue, BigInt(leafIndex));
210
+ }
211
+ function computeNullifierSync(_skSpend, _leafIndex) {
212
+ throw new Error("computeNullifierSync is deprecated. Use computeNullifierAsync instead.");
213
+ }
214
+ async function computeOutputsHashAsync(outputs) {
215
+ let hash = 0n;
216
+ for (const output of outputs) {
217
+ const [lo, hi] = pubkeyToLimbs(output.recipient);
218
+ hash = await poseidonHash([hash, lo, hi, BigInt(output.amount)]);
219
+ }
220
+ return hash;
221
+ }
222
+ async function computeOutputsHash(outAddr, outAmount, outFlags) {
223
+ let hash = 0n;
224
+ for (let i = 0; i < 5; i++) {
225
+ if (outFlags[i] === 1) {
226
+ hash = await poseidonHash([hash, outAddr[i][0], outAddr[i][1], outAmount[i]]);
227
+ }
228
+ }
229
+ return hash;
230
+ }
231
+ function computeOutputsHashSync(_outputs) {
232
+ throw new Error("computeOutputsHashSync is deprecated. Use computeOutputsHashAsync instead.");
233
+ }
234
+ async function computeSwapOutputsHash(inputMintLimbs, outputMintLimbs, recipientAtaLimbs, minOutputAmount, publicAmount) {
235
+ return await poseidonHash([
236
+ inputMintLimbs[0],
237
+ inputMintLimbs[1],
238
+ outputMintLimbs[0],
239
+ outputMintLimbs[1],
240
+ recipientAtaLimbs[0],
241
+ recipientAtaLimbs[1],
242
+ minOutputAmount,
243
+ publicAmount
244
+ ]);
245
+ }
246
+ async function computeSwapOutputsHashAsync(inputMint, outputMint, recipientAta, minOutputAmount, amount) {
247
+ const inputMintLimbs = pubkeyToLimbs(inputMint);
248
+ const outputMintLimbs = pubkeyToLimbs(outputMint);
249
+ const recipientAtaLimbs = pubkeyToLimbs(recipientAta);
250
+ return await computeSwapOutputsHash(
251
+ inputMintLimbs,
252
+ outputMintLimbs,
253
+ recipientAtaLimbs,
254
+ BigInt(minOutputAmount),
255
+ BigInt(amount)
256
+ );
257
+ }
258
+ function computeSwapOutputsHashSync(_outputMint, _recipientAta, _minOutputAmount, _amount) {
259
+ throw new Error("computeSwapOutputsHashSync is deprecated. Use computeSwapOutputsHashAsync instead.");
260
+ }
261
+ function bigintToBytes32(n) {
262
+ const hex = n.toString(16).padStart(64, "0");
263
+ const bytes = new Uint8Array(32);
264
+ for (let i = 0; i < 32; i++) {
265
+ bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
266
+ }
267
+ return bytes;
268
+ }
269
+ function hexToBytes(hex) {
270
+ const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
271
+ const bytes = new Uint8Array(cleanHex.length / 2);
272
+ for (let i = 0; i < cleanHex.length; i += 2) {
273
+ bytes[i / 2] = parseInt(cleanHex.substr(i, 2), 16);
274
+ }
275
+ return bytes;
276
+ }
277
+ function bytesToHex(bytes, prefix = false) {
278
+ const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
279
+ return prefix ? `0x${hex}` : hex;
280
+ }
281
+ function randomBytes(length) {
282
+ const bytes = new Uint8Array(length);
283
+ const g = globalThis;
284
+ try {
285
+ const cryptoObj = g?.crypto || g?.window?.crypto || g?.self?.crypto;
286
+ if (cryptoObj && typeof cryptoObj.getRandomValues === "function") {
287
+ cryptoObj.getRandomValues(bytes);
288
+ return bytes;
289
+ }
290
+ } catch {
291
+ }
292
+ try {
293
+ const nodeCrypto = require("crypto");
294
+ if (nodeCrypto?.randomBytes) {
295
+ const buffer = nodeCrypto.randomBytes(length);
296
+ bytes.set(buffer);
297
+ return bytes;
298
+ }
299
+ if (nodeCrypto?.webcrypto?.getRandomValues) {
300
+ nodeCrypto.webcrypto.getRandomValues(bytes);
301
+ return bytes;
302
+ }
303
+ } catch {
304
+ }
305
+ for (let i = 0; i < length; i++) bytes[i] = Math.floor(Math.random() * 256);
306
+ return bytes;
307
+ }
308
+ function isValidHex(hex, expectedLength) {
309
+ const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
310
+ if (!/^[0-9a-f]*$/i.test(cleanHex)) {
311
+ return false;
312
+ }
313
+ if (cleanHex.length % 2 !== 0) {
314
+ return false;
315
+ }
316
+ if (expectedLength !== void 0) {
317
+ return cleanHex.length === expectedLength * 2;
318
+ }
319
+ return true;
320
+ }
321
+ var BN254_MODULUS = BigInt("0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47");
322
+ function proofToBytes(proof) {
323
+ const pi_a_x = BigInt(proof.pi_a[0]);
324
+ const pi_a_y = BigInt(proof.pi_a[1]);
325
+ const pi_a_y_neg = (BN254_MODULUS - pi_a_y) % BN254_MODULUS;
326
+ const pi_a_x_le = bigintToBytes32LE(pi_a_x);
327
+ const pi_a_y_neg_le = bigintToBytes32LE(pi_a_y_neg);
328
+ const pi_a_le = new Uint8Array(64);
329
+ pi_a_le.set(pi_a_x_le, 0);
330
+ pi_a_le.set(pi_a_y_neg_le, 32);
331
+ const pi_a_be = convertEndianness32(pi_a_le);
332
+ const pi_b_x1 = BigInt(proof.pi_b[0][0]);
333
+ const pi_b_x2 = BigInt(proof.pi_b[0][1]);
334
+ const pi_b_y1 = BigInt(proof.pi_b[1][0]);
335
+ const pi_b_y2 = BigInt(proof.pi_b[1][1]);
336
+ const pi_b_x1_le = bigintToBytes32LE(pi_b_x1);
337
+ const pi_b_x2_le = bigintToBytes32LE(pi_b_x2);
338
+ const pi_b_y1_le = bigintToBytes32LE(pi_b_y1);
339
+ const pi_b_y2_le = bigintToBytes32LE(pi_b_y2);
340
+ const pi_b_le = new Uint8Array(128);
341
+ pi_b_le.set(pi_b_x1_le, 0);
342
+ pi_b_le.set(pi_b_x2_le, 32);
343
+ pi_b_le.set(pi_b_y1_le, 64);
344
+ pi_b_le.set(pi_b_y2_le, 96);
345
+ const pi_b_be = convertEndianness64(pi_b_le);
346
+ const pi_c_x = BigInt(proof.pi_c[0]);
347
+ const pi_c_y = BigInt(proof.pi_c[1]);
348
+ const pi_c_x_le = bigintToBytes32LE(pi_c_x);
349
+ const pi_c_y_le = bigintToBytes32LE(pi_c_y);
350
+ const pi_c_le = new Uint8Array(64);
351
+ pi_c_le.set(pi_c_x_le, 0);
352
+ pi_c_le.set(pi_c_y_le, 32);
353
+ const pi_c_be = convertEndianness32(pi_c_le);
354
+ const result = new Uint8Array(256);
355
+ result.set(pi_a_be, 0);
356
+ result.set(pi_b_be, 64);
357
+ result.set(pi_c_be, 192);
358
+ return result;
359
+ }
360
+ function bigintToBytes32LE(n) {
361
+ const bytes = new Uint8Array(32);
362
+ let value = n % BN254_MODULUS;
363
+ if (value < 0) {
364
+ value = (value + BN254_MODULUS) % BN254_MODULUS;
365
+ }
366
+ for (let i = 0; i < 32; i++) {
367
+ bytes[i] = Number(value & BigInt(255));
368
+ value = value >> BigInt(8);
369
+ }
370
+ return bytes;
371
+ }
372
+ function convertEndianness32(bytes) {
373
+ if (bytes.length !== 64) {
374
+ throw new Error("convertEndianness32 expects 64 bytes");
375
+ }
376
+ const result = new Uint8Array(64);
377
+ for (let i = 0; i < 32; i++) {
378
+ result[i] = bytes[31 - i];
379
+ }
380
+ for (let i = 0; i < 32; i++) {
381
+ result[32 + i] = bytes[63 - i];
382
+ }
383
+ return result;
384
+ }
385
+ function convertEndianness64(bytes) {
386
+ if (bytes.length !== 128) {
387
+ throw new Error("convertEndianness64 expects 128 bytes");
388
+ }
389
+ const result = new Uint8Array(128);
390
+ for (let i = 0; i < 64; i++) {
391
+ result[i] = bytes[63 - i];
392
+ }
393
+ for (let i = 0; i < 64; i++) {
394
+ result[64 + i] = bytes[127 - i];
395
+ }
396
+ return result;
397
+ }
398
+ function buildPublicInputsBytes(root, nullifier, outputsHash, publicAmount) {
399
+ const result = new Uint8Array(104);
400
+ result.set(bigintToBytes32(root), 0);
401
+ result.set(bigintToBytes32(nullifier), 32);
402
+ result.set(bigintToBytes32(outputsHash), 64);
403
+ const amountBytes = new Uint8Array(8);
404
+ let amt = publicAmount;
405
+ for (let i = 7; i >= 0; i--) {
406
+ amountBytes[i] = Number(amt & 0xffn);
407
+ amt = amt >> 8n;
408
+ }
409
+ result.set(amountBytes, 96);
410
+ return result;
411
+ }
412
+
413
+ // src/core/keys.ts
414
+ function generateMasterSeed() {
415
+ const seed = new Uint8Array(32);
416
+ const g = globalThis;
417
+ const cryptoObj = g?.crypto || g?.window?.crypto || g?.self?.crypto;
418
+ if (cryptoObj && typeof cryptoObj.getRandomValues === "function") {
419
+ cryptoObj.getRandomValues(seed);
420
+ } else {
421
+ try {
422
+ const nodeCrypto = require("crypto");
423
+ const buffer = nodeCrypto.randomBytes(32);
424
+ seed.set(buffer);
425
+ } catch {
426
+ throw new Error("No secure random number generator available");
427
+ }
428
+ }
429
+ return {
430
+ seed,
431
+ seedHex: bytesToHex(seed)
432
+ };
433
+ }
434
+ function deriveSpendKey(masterSeed) {
435
+ const context = new TextEncoder().encode("cloak_spend_key");
436
+ const preimage = new Uint8Array(masterSeed.length + context.length);
437
+ preimage.set(masterSeed, 0);
438
+ preimage.set(context, masterSeed.length);
439
+ const sk_spend = (0, import_blake3.blake3)(preimage);
440
+ const pk_spend = (0, import_blake3.blake3)(sk_spend);
441
+ return {
442
+ sk_spend,
443
+ pk_spend,
444
+ sk_spend_hex: bytesToHex(sk_spend),
445
+ pk_spend_hex: bytesToHex(pk_spend)
446
+ };
447
+ }
448
+ function deriveViewKey(sk_spend) {
449
+ const context = new TextEncoder().encode("cloak_view_key_secret");
450
+ const preimage = new Uint8Array(sk_spend.length + context.length);
451
+ preimage.set(sk_spend, 0);
452
+ preimage.set(context, sk_spend.length);
453
+ const vk_secret = (0, import_blake3.blake3)(preimage);
454
+ const x25519Keypair = import_tweetnacl.default.box.keyPair.fromSecretKey(vk_secret);
455
+ return {
456
+ vk_secret,
457
+ pvk: x25519Keypair.publicKey,
458
+ vk_secret_hex: bytesToHex(vk_secret),
459
+ pvk_hex: bytesToHex(x25519Keypair.publicKey)
460
+ };
461
+ }
462
+ function generateCloakKeys(masterSeed) {
463
+ const master = masterSeed ? { seed: masterSeed, seedHex: bytesToHex(masterSeed) } : generateMasterSeed();
464
+ const spend = deriveSpendKey(master.seed);
465
+ const view = deriveViewKey(spend.sk_spend);
466
+ return {
467
+ master,
468
+ spend,
469
+ view
470
+ };
471
+ }
472
+ function encryptNoteForRecipient(noteData, recipientPvk) {
473
+ const ephemeralKeypair = import_tweetnacl.default.box.keyPair();
474
+ const sharedSecret = import_tweetnacl.default.box.before(recipientPvk, ephemeralKeypair.secretKey);
475
+ const plaintext = new TextEncoder().encode(JSON.stringify(noteData));
476
+ const nonce = import_tweetnacl.default.randomBytes(import_tweetnacl.default.secretbox.nonceLength);
477
+ const ciphertext = import_tweetnacl.default.secretbox(plaintext, nonce, sharedSecret);
478
+ return {
479
+ ephemeral_pk: bytesToHex(ephemeralKeypair.publicKey),
480
+ ciphertext: bytesToHex(ciphertext),
481
+ nonce: bytesToHex(nonce)
482
+ };
483
+ }
484
+ function tryDecryptNote(encryptedNote, viewKey) {
485
+ try {
486
+ const ephemeralPk = hexToBytes(encryptedNote.ephemeral_pk);
487
+ const ciphertext = hexToBytes(encryptedNote.ciphertext);
488
+ const nonce = hexToBytes(encryptedNote.nonce);
489
+ const x25519Secret = viewKey.vk_secret;
490
+ const sharedSecret = import_tweetnacl.default.box.before(ephemeralPk, x25519Secret);
491
+ const plaintext = import_tweetnacl.default.secretbox.open(ciphertext, nonce, sharedSecret);
492
+ if (!plaintext) {
493
+ return null;
494
+ }
495
+ const noteData = JSON.parse(new TextDecoder().decode(plaintext));
496
+ return noteData;
497
+ } catch (e) {
498
+ return null;
499
+ }
500
+ }
501
+ function scanNotesForWallet(encryptedOutputs, viewKey) {
502
+ const foundNotes = [];
503
+ for (const encryptedOutput of encryptedOutputs) {
504
+ try {
505
+ const decoded = atob(encryptedOutput);
506
+ const encryptedNote = JSON.parse(decoded);
507
+ const noteData = tryDecryptNote(encryptedNote, viewKey);
508
+ if (noteData) {
509
+ foundNotes.push(noteData);
510
+ }
511
+ } catch (e) {
512
+ continue;
513
+ }
514
+ }
515
+ return foundNotes;
516
+ }
517
+ function exportKeys(keys) {
518
+ return JSON.stringify({
519
+ version: "2.0",
520
+ master_seed: keys.master.seedHex,
521
+ sk_spend: keys.spend.sk_spend_hex,
522
+ pk_spend: keys.spend.pk_spend_hex,
523
+ vk_secret: keys.view.vk_secret_hex,
524
+ pvk: keys.view.pvk_hex
525
+ }, null, 2);
526
+ }
527
+ function importKeys(exported) {
528
+ const parsed = JSON.parse(exported);
529
+ const masterSeed = hexToBytes(parsed.master_seed);
530
+ return generateCloakKeys(masterSeed);
531
+ }
532
+
533
+ // src/utils/network.ts
534
+ function detectNetworkFromRpcUrl(rpcUrl) {
535
+ const url = rpcUrl || process.env.NEXT_PUBLIC_SOLANA_RPC_URL || "";
536
+ const lowerUrl = url.toLowerCase();
537
+ if (lowerUrl.includes("mainnet") || lowerUrl.includes("api.mainnet-beta") || lowerUrl.includes("mainnet-beta")) {
538
+ return "mainnet";
539
+ }
540
+ if (lowerUrl.includes("testnet") || lowerUrl.includes("api.testnet")) {
541
+ return "testnet";
542
+ }
543
+ if (lowerUrl.includes("devnet") || lowerUrl.includes("api.devnet")) {
544
+ return "devnet";
545
+ }
546
+ if (lowerUrl.includes("localhost") || lowerUrl.includes("127.0.0.1") || lowerUrl.includes("local")) {
547
+ return "localnet";
548
+ }
549
+ return "devnet";
550
+ }
551
+ function getRpcUrlForNetwork(network) {
552
+ switch (network) {
553
+ case "mainnet":
554
+ return "https://api.mainnet-beta.solana.com";
555
+ case "testnet":
556
+ return "https://api.testnet.solana.com";
557
+ case "devnet":
558
+ return "https://api.devnet.solana.com";
559
+ case "localnet":
560
+ return "http://localhost:8899";
561
+ default:
562
+ return "https://api.devnet.solana.com";
563
+ }
564
+ }
565
+ function isValidRpcUrl(url) {
566
+ try {
567
+ const parsed = new URL(url);
568
+ return parsed.protocol === "http:" || parsed.protocol === "https:";
569
+ } catch {
570
+ return false;
571
+ }
572
+ }
573
+ function getExplorerUrl(signature, network = "devnet") {
574
+ const cluster = network === "mainnet" ? "" : `?cluster=${network}`;
575
+ return `https://explorer.solana.com/tx/${signature}${cluster}`;
576
+ }
577
+ function getAddressExplorerUrl(address, network = "devnet") {
578
+ const cluster = network === "mainnet" ? "" : `?cluster=${network}`;
579
+ return `https://explorer.solana.com/address/${address}${cluster}`;
580
+ }
581
+
582
+ // src/core/note-manager.ts
583
+ async function generateNote(amountLamports, network) {
584
+ const actualNetwork = network || detectNetworkFromRpcUrl();
585
+ const skSpend = randomBytes(32);
586
+ const rBytes = randomBytes(32);
587
+ const commitmentBigint = await generateCommitmentAsync(amountLamports, rBytes, skSpend);
588
+ const commitmentHex = commitmentBigint.toString(16).padStart(64, "0");
589
+ const skSpendHex = bytesToHex(skSpend);
590
+ const rHex = bytesToHex(rBytes);
591
+ return {
592
+ version: "1.0",
593
+ amount: amountLamports,
594
+ commitment: commitmentHex,
595
+ sk_spend: skSpendHex,
596
+ r: rHex,
597
+ timestamp: Date.now(),
598
+ network: actualNetwork
599
+ };
600
+ }
601
+ async function generateNoteFromWallet(amountLamports, keys, network) {
602
+ const actualNetwork = network || detectNetworkFromRpcUrl();
603
+ const rBytes = randomBytes(32);
604
+ const sk_spend = hexToBytes(keys.spend.sk_spend_hex);
605
+ const commitmentBigint = await generateCommitmentAsync(amountLamports, rBytes, sk_spend);
606
+ const commitmentHex = commitmentBigint.toString(16).padStart(64, "0");
607
+ return {
608
+ version: "2.0",
609
+ amount: amountLamports,
610
+ commitment: commitmentHex,
611
+ sk_spend: keys.spend.sk_spend_hex,
612
+ r: bytesToHex(rBytes),
613
+ timestamp: Date.now(),
614
+ network: actualNetwork
615
+ };
616
+ }
617
+ function parseNote(jsonString) {
618
+ const note = JSON.parse(jsonString);
619
+ if (!note.version || !note.amount || !note.commitment || !note.sk_spend || !note.r) {
620
+ throw new CloakError(
621
+ "Invalid note format: missing required fields",
622
+ "validation",
623
+ false
624
+ );
625
+ }
626
+ if (!/^[0-9a-f]{64}$/i.test(note.commitment)) {
627
+ throw new CloakError("Invalid commitment format", "validation", false);
628
+ }
629
+ if (!/^[0-9a-f]{64}$/i.test(note.sk_spend)) {
630
+ throw new CloakError("Invalid sk_spend format", "validation", false);
631
+ }
632
+ if (!/^[0-9a-f]{64}$/i.test(note.r)) {
633
+ throw new CloakError("Invalid r format", "validation", false);
634
+ }
635
+ return note;
636
+ }
637
+ function exportNote(note, pretty = false) {
638
+ return pretty ? JSON.stringify(note, null, 2) : JSON.stringify(note);
639
+ }
640
+ function isWithdrawable(note) {
641
+ return !!(note.depositSignature && note.leafIndex !== void 0 && note.root && note.merkleProof);
642
+ }
643
+ function updateNoteWithDeposit(note, depositInfo) {
644
+ return {
645
+ ...note,
646
+ depositSignature: depositInfo.signature,
647
+ depositSlot: depositInfo.slot,
648
+ leafIndex: depositInfo.leafIndex,
649
+ root: depositInfo.root,
650
+ merkleProof: depositInfo.merkleProof
651
+ };
652
+ }
653
+ function findNoteByCommitment(notes, commitment) {
654
+ return notes.find((n) => n.commitment === commitment);
655
+ }
656
+ function filterNotesByNetwork(notes, network) {
657
+ return notes.filter((n) => n.network === network);
658
+ }
659
+ function filterWithdrawableNotes(notes) {
660
+ return notes.filter(isWithdrawable);
661
+ }
662
+ function exportWalletKeys(keys) {
663
+ return exportKeys(keys);
664
+ }
665
+ function importWalletKeys(keysJson) {
666
+ return importKeys(keysJson);
667
+ }
668
+ function getPublicViewKey(keys) {
669
+ return keys.view.pvk_hex;
670
+ }
671
+ function getViewKey(keys) {
672
+ return keys.view;
673
+ }
674
+ function calculateFee(amountLamports) {
675
+ const FIXED_FEE_LAMPORTS2 = 25e5;
676
+ const variableFee = Math.floor(amountLamports * 5 / 1e3);
677
+ return FIXED_FEE_LAMPORTS2 + variableFee;
678
+ }
679
+ function getDistributableAmount(amountLamports) {
680
+ return amountLamports - calculateFee(amountLamports);
681
+ }
682
+ function getRecipientAmount(amountLamports) {
683
+ return getDistributableAmount(amountLamports);
684
+ }
685
+
686
+ // src/core/storage.ts
687
+ var MemoryStorageAdapter = class {
688
+ constructor() {
689
+ this.notes = /* @__PURE__ */ new Map();
690
+ this.keys = null;
691
+ }
692
+ saveNote(note) {
693
+ this.notes.set(note.commitment, note);
694
+ }
695
+ loadAllNotes() {
696
+ return Array.from(this.notes.values());
697
+ }
698
+ updateNote(commitment, updates) {
699
+ const existing = this.notes.get(commitment);
700
+ if (existing) {
701
+ this.notes.set(commitment, { ...existing, ...updates });
702
+ }
703
+ }
704
+ deleteNote(commitment) {
705
+ this.notes.delete(commitment);
706
+ }
707
+ clearAllNotes() {
708
+ this.notes.clear();
709
+ }
710
+ saveKeys(keys) {
711
+ this.keys = keys;
712
+ }
713
+ loadKeys() {
714
+ return this.keys;
715
+ }
716
+ deleteKeys() {
717
+ this.keys = null;
718
+ }
719
+ };
720
+ var LocalStorageAdapter = class {
721
+ constructor(notesKey = "cloak_notes", keysKey = "cloak_wallet_keys") {
722
+ this.notesKey = notesKey;
723
+ this.keysKey = keysKey;
724
+ }
725
+ getStorage() {
726
+ if (typeof globalThis !== "undefined" && globalThis.localStorage) {
727
+ return globalThis.localStorage;
728
+ }
729
+ return null;
730
+ }
731
+ saveNote(note) {
732
+ const storage = this.getStorage();
733
+ if (!storage) throw new Error("localStorage not available");
734
+ const notes = this.loadAllNotes();
735
+ notes.push(note);
736
+ storage.setItem(this.notesKey, JSON.stringify(notes));
737
+ }
738
+ loadAllNotes() {
739
+ const storage = this.getStorage();
740
+ if (!storage) return [];
741
+ const stored = storage.getItem(this.notesKey);
742
+ if (!stored) return [];
743
+ try {
744
+ return JSON.parse(stored);
745
+ } catch {
746
+ return [];
747
+ }
748
+ }
749
+ updateNote(commitment, updates) {
750
+ const storage = this.getStorage();
751
+ if (!storage) return;
752
+ const notes = this.loadAllNotes();
753
+ const index = notes.findIndex((n) => n.commitment === commitment);
754
+ if (index !== -1) {
755
+ notes[index] = { ...notes[index], ...updates };
756
+ storage.setItem(this.notesKey, JSON.stringify(notes));
757
+ }
758
+ }
759
+ deleteNote(commitment) {
760
+ const storage = this.getStorage();
761
+ if (!storage) return;
762
+ const notes = this.loadAllNotes();
763
+ const filtered = notes.filter((n) => n.commitment !== commitment);
764
+ storage.setItem(this.notesKey, JSON.stringify(filtered));
765
+ }
766
+ clearAllNotes() {
767
+ const storage = this.getStorage();
768
+ if (storage) {
769
+ storage.removeItem(this.notesKey);
770
+ }
771
+ }
772
+ saveKeys(keys) {
773
+ const storage = this.getStorage();
774
+ if (!storage) throw new Error("localStorage not available");
775
+ storage.setItem(this.keysKey, exportKeys(keys));
776
+ }
777
+ loadKeys() {
778
+ const storage = this.getStorage();
779
+ if (!storage) return null;
780
+ const stored = storage.getItem(this.keysKey);
781
+ if (!stored) return null;
782
+ try {
783
+ return importKeys(stored);
784
+ } catch {
785
+ return null;
786
+ }
787
+ }
788
+ deleteKeys() {
789
+ const storage = this.getStorage();
790
+ if (storage) {
791
+ storage.removeItem(this.keysKey);
792
+ }
793
+ }
794
+ };
795
+
796
+ // src/utils/validation.ts
797
+ var import_web32 = require("@solana/web3.js");
798
+ function isValidSolanaAddress(address) {
799
+ try {
800
+ new import_web32.PublicKey(address);
801
+ return true;
802
+ } catch {
803
+ return false;
804
+ }
805
+ }
806
+ function validateNote(note) {
807
+ if (!note || typeof note !== "object") {
808
+ throw new Error("Note must be an object");
809
+ }
810
+ const requiredFields = ["version", "amount", "commitment", "sk_spend", "r", "timestamp", "network"];
811
+ for (const field of requiredFields) {
812
+ if (!(field in note)) {
813
+ throw new Error(`Missing required field: ${field}`);
814
+ }
815
+ }
816
+ if (typeof note.version !== "string") {
817
+ throw new Error("Version must be a string");
818
+ }
819
+ if (typeof note.amount !== "number" || note.amount <= 0) {
820
+ throw new Error("Amount must be a positive number");
821
+ }
822
+ if (!isValidHex(note.commitment, 32)) {
823
+ throw new Error("Invalid commitment format (expected 64 hex characters)");
824
+ }
825
+ if (!isValidHex(note.sk_spend, 32)) {
826
+ throw new Error("Invalid sk_spend format (expected 64 hex characters)");
827
+ }
828
+ if (!isValidHex(note.r, 32)) {
829
+ throw new Error("Invalid r format (expected 64 hex characters)");
830
+ }
831
+ if (typeof note.timestamp !== "number" || note.timestamp <= 0) {
832
+ throw new Error("Timestamp must be a positive number");
833
+ }
834
+ if (!["localnet", "devnet", "testnet", "mainnet"].includes(note.network)) {
835
+ throw new Error("Network must be localnet, devnet, testnet, or mainnet");
836
+ }
837
+ if (note.depositSignature !== void 0 && typeof note.depositSignature !== "string") {
838
+ throw new Error("Deposit signature must be a string");
839
+ }
840
+ if (note.depositSlot !== void 0 && typeof note.depositSlot !== "number") {
841
+ throw new Error("Deposit slot must be a number");
842
+ }
843
+ if (note.leafIndex !== void 0) {
844
+ if (typeof note.leafIndex !== "number" || note.leafIndex < 0) {
845
+ throw new Error("Leaf index must be a non-negative number");
846
+ }
847
+ }
848
+ if (note.root !== void 0 && !isValidHex(note.root, 32)) {
849
+ throw new Error("Invalid root format (expected 64 hex characters)");
850
+ }
851
+ if (note.merkleProof !== void 0) {
852
+ if (!Array.isArray(note.merkleProof.pathElements)) {
853
+ throw new Error("Merkle proof pathElements must be an array");
854
+ }
855
+ if (!Array.isArray(note.merkleProof.pathIndices)) {
856
+ throw new Error("Merkle proof pathIndices must be an array");
857
+ }
858
+ if (note.merkleProof.pathElements.length !== note.merkleProof.pathIndices.length) {
859
+ throw new Error("Merkle proof pathElements and pathIndices must have same length");
860
+ }
861
+ }
862
+ }
863
+ function parseNote2(jsonString) {
864
+ let parsed;
865
+ try {
866
+ parsed = JSON.parse(jsonString);
867
+ } catch (error) {
868
+ throw new Error("Invalid JSON format");
869
+ }
870
+ validateNote(parsed);
871
+ return parsed;
872
+ }
873
+ function validateWithdrawableNote(note) {
874
+ if (!note.depositSignature) {
875
+ throw new Error("Note must be deposited before withdrawal (missing depositSignature)");
876
+ }
877
+ if (note.leafIndex === void 0) {
878
+ throw new Error("Note must be deposited before withdrawal (missing leafIndex)");
879
+ }
880
+ if (!note.root) {
881
+ throw new Error("Note must have historical root for withdrawal");
882
+ }
883
+ if (!note.merkleProof) {
884
+ throw new Error("Note must have Merkle proof for withdrawal");
885
+ }
886
+ if (note.merkleProof.pathElements.length === 0) {
887
+ throw new Error("Merkle proof is empty");
888
+ }
889
+ }
890
+ function validateTransfers(recipients, totalAmount) {
891
+ if (recipients.length === 0) {
892
+ throw new Error("At least one recipient is required");
893
+ }
894
+ if (recipients.length > 5) {
895
+ throw new Error("Maximum 5 recipients allowed");
896
+ }
897
+ for (let i = 0; i < recipients.length; i++) {
898
+ const transfer = recipients[i];
899
+ if (!transfer.recipient || !(transfer.recipient instanceof import_web32.PublicKey)) {
900
+ throw new Error(`Recipient ${i} must be a PublicKey`);
901
+ }
902
+ if (typeof transfer.amount !== "number" || transfer.amount <= 0) {
903
+ throw new Error(`Recipient ${i} amount must be a positive number`);
904
+ }
905
+ }
906
+ const sum = recipients.reduce((acc, t) => acc + t.amount, 0);
907
+ if (sum !== totalAmount) {
908
+ throw new Error(
909
+ `Recipients sum (${sum}) does not match expected total (${totalAmount})`
910
+ );
911
+ }
912
+ }
913
+
914
+ // src/utils/fees.ts
915
+ var FIXED_FEE_LAMPORTS = 25e5;
916
+ var VARIABLE_FEE_RATE = 5 / 1e3;
917
+ var LAMPORTS_PER_SOL = 1e9;
918
+ function calculateFee2(amountLamports) {
919
+ const variableFee = Math.floor(amountLamports * 5 / 1e3);
920
+ return FIXED_FEE_LAMPORTS + variableFee;
921
+ }
922
+ function getDistributableAmount2(amountLamports) {
923
+ return amountLamports - calculateFee2(amountLamports);
924
+ }
925
+ function formatAmount(lamports, decimals = 9) {
926
+ return (lamports / LAMPORTS_PER_SOL).toFixed(decimals);
927
+ }
928
+ function parseAmount(sol) {
929
+ const num = parseFloat(sol);
930
+ if (isNaN(num) || num < 0) {
931
+ throw new Error(`Invalid SOL amount: ${sol}`);
932
+ }
933
+ return Math.floor(num * LAMPORTS_PER_SOL);
934
+ }
935
+ function validateOutputsSum(outputs, expectedTotal) {
936
+ const sum = outputs.reduce((acc, out) => acc + out.amount, 0);
937
+ return sum === expectedTotal;
938
+ }
939
+ function calculateRelayFee(amountLamports, feeBps) {
940
+ if (feeBps < 0 || feeBps > 1e4) {
941
+ throw new Error("Fee basis points must be between 0 and 10000");
942
+ }
943
+ return Math.floor(amountLamports * feeBps / 1e4);
944
+ }
945
+
946
+ // src/services/IndexerService.ts
947
+ var IndexerService = class {
948
+ /**
949
+ * Create a new Indexer Service client
950
+ *
951
+ * @param baseUrl - Indexer API base URL
952
+ */
953
+ constructor(baseUrl) {
954
+ this.baseUrl = baseUrl.replace(/\/$/, "");
955
+ }
956
+ /**
957
+ * Get current Merkle root and next available index
958
+ *
959
+ * @returns Current root and next index
960
+ *
961
+ * @example
962
+ * ```typescript
963
+ * const { root, next_index } = await indexer.getMerkleRoot();
964
+ * console.log(`Current root: ${root}, Next index: ${next_index}`);
965
+ * ```
966
+ */
967
+ async getMerkleRoot() {
968
+ const response = await fetch(`${this.baseUrl}/api/v1/merkle/root`);
969
+ if (!response.ok) {
970
+ throw new Error(
971
+ `Failed to get Merkle root: ${response.status} ${response.statusText}`
972
+ );
973
+ }
974
+ const json = await response.json();
975
+ return json;
976
+ }
977
+ /**
978
+ * Get Merkle proof for a specific leaf
979
+ *
980
+ * @param leafIndex - Index of the leaf in the tree
981
+ * @returns Merkle proof with path elements and indices
982
+ *
983
+ * @example
984
+ * ```typescript
985
+ * const proof = await indexer.getMerkleProof(42);
986
+ * console.log(`Proof has ${proof.pathElements.length} siblings`);
987
+ * ```
988
+ */
989
+ async getMerkleProof(leafIndex) {
990
+ const response = await fetch(
991
+ `${this.baseUrl}/api/v1/merkle/proof/${leafIndex}`
992
+ );
993
+ if (!response.ok) {
994
+ throw new Error(
995
+ `Failed to get Merkle proof: ${response.status} ${response.statusText}`
996
+ );
997
+ }
998
+ const data = await response.json();
999
+ return {
1000
+ pathElements: data.pathElements ?? data.path_elements,
1001
+ pathIndices: data.pathIndices ?? data.path_indices,
1002
+ root: data.root
1003
+ };
1004
+ }
1005
+ /**
1006
+ * Get notes in a specific range
1007
+ *
1008
+ * Useful for scanning the tree or fetching notes in batches.
1009
+ *
1010
+ * @param start - Start index (inclusive)
1011
+ * @param end - End index (inclusive)
1012
+ * @param limit - Maximum number of notes to return (default: 100)
1013
+ * @returns Notes in the range
1014
+ *
1015
+ * @example
1016
+ * ```typescript
1017
+ * const { notes, has_more } = await indexer.getNotesRange(0, 99, 100);
1018
+ * console.log(`Fetched ${notes.length} notes`);
1019
+ * ```
1020
+ */
1021
+ async getNotesRange(start, end, limit = 100) {
1022
+ const url = new URL(`${this.baseUrl}/api/v1/notes/range`);
1023
+ url.searchParams.set("start", start.toString());
1024
+ url.searchParams.set("end", end.toString());
1025
+ url.searchParams.set("limit", limit.toString());
1026
+ const response = await fetch(url.toString());
1027
+ if (!response.ok) {
1028
+ throw new Error(
1029
+ `Failed to get notes range: ${response.status} ${response.statusText}`
1030
+ );
1031
+ }
1032
+ const json = await response.json();
1033
+ return json;
1034
+ }
1035
+ /**
1036
+ * Get all notes from the tree
1037
+ *
1038
+ * Fetches all notes in batches. Use with caution for large trees.
1039
+ *
1040
+ * @param batchSize - Size of each batch (default: 100)
1041
+ * @returns All encrypted notes
1042
+ *
1043
+ * @example
1044
+ * ```typescript
1045
+ * const allNotes = await indexer.getAllNotes();
1046
+ * console.log(`Total notes: ${allNotes.length}`);
1047
+ * ```
1048
+ */
1049
+ async getAllNotes(batchSize = 100) {
1050
+ const rootResponse = await this.getMerkleRoot();
1051
+ const totalNotes = rootResponse.next_index;
1052
+ if (totalNotes === 0) {
1053
+ return [];
1054
+ }
1055
+ const allNotes = [];
1056
+ for (let start = 0; start < totalNotes; start += batchSize) {
1057
+ const end = Math.min(start + batchSize - 1, totalNotes - 1);
1058
+ const response = await this.getNotesRange(start, end, batchSize);
1059
+ allNotes.push(...response.notes);
1060
+ }
1061
+ return allNotes;
1062
+ }
1063
+ /**
1064
+ * Submit a deposit to the indexer
1065
+ *
1066
+ * Registers a new deposit transaction with the indexer, which will
1067
+ * return the leaf index and current root.
1068
+ *
1069
+ * @param params - Deposit parameters
1070
+ * @returns Success response with leaf index and root
1071
+ *
1072
+ * @example
1073
+ * ```typescript
1074
+ * const result = await indexer.submitDeposit({
1075
+ * leafCommit: note.commitment,
1076
+ * encryptedOutput: btoa(JSON.stringify(noteData)),
1077
+ * txSignature: signature,
1078
+ * slot: txSlot
1079
+ * });
1080
+ * console.log(`Leaf index: ${result.leafIndex}`);
1081
+ * ```
1082
+ */
1083
+ async submitDeposit(params) {
1084
+ const response = await fetch(`${this.baseUrl}/api/v1/deposit`, {
1085
+ method: "POST",
1086
+ headers: {
1087
+ "Content-Type": "application/json"
1088
+ },
1089
+ body: JSON.stringify({
1090
+ leaf_commit: params.leafCommit,
1091
+ encrypted_output: params.encryptedOutput,
1092
+ tx_signature: params.txSignature,
1093
+ slot: params.slot
1094
+ })
1095
+ });
1096
+ let responseData;
1097
+ try {
1098
+ responseData = await response.json();
1099
+ } catch {
1100
+ try {
1101
+ const text = await response.text();
1102
+ responseData = text ? { error: text } : null;
1103
+ } catch {
1104
+ responseData = null;
1105
+ }
1106
+ }
1107
+ if (!response.ok) {
1108
+ let errorMessage = `${response.status} ${response.statusText}`;
1109
+ if (responseData) {
1110
+ if (typeof responseData === "string") {
1111
+ errorMessage = responseData;
1112
+ } else {
1113
+ errorMessage = responseData?.error || responseData?.message || errorMessage;
1114
+ if (responseData?.details) {
1115
+ errorMessage += ` (${JSON.stringify(responseData.details)})`;
1116
+ }
1117
+ }
1118
+ }
1119
+ throw new Error(`Failed to submit deposit: ${errorMessage}`);
1120
+ }
1121
+ const data = responseData;
1122
+ return {
1123
+ success: data.success ?? true,
1124
+ leafIndex: data.leafIndex ?? data.leaf_index,
1125
+ root: data.root
1126
+ };
1127
+ }
1128
+ /**
1129
+ * Check indexer health
1130
+ *
1131
+ * @returns Health status
1132
+ */
1133
+ async healthCheck() {
1134
+ const response = await fetch(`${this.baseUrl}/health`);
1135
+ if (!response.ok) {
1136
+ throw new Error(
1137
+ `Health check failed: ${response.status} ${response.statusText}`
1138
+ );
1139
+ }
1140
+ const json = await response.json();
1141
+ return json;
1142
+ }
1143
+ };
1144
+
1145
+ // src/services/ArtifactProverService.ts
1146
+ var ArtifactProverService = class {
1147
+ /**
1148
+ * Create a new Artifact Prover Service client
1149
+ *
1150
+ * @param indexerUrl - Indexer service base URL
1151
+ * @param timeout - Proof generation timeout in ms (default: 5 minutes)
1152
+ * @param pollInterval - Polling interval for status checks (default: 2 seconds)
1153
+ */
1154
+ constructor(indexerUrl, timeout = 5 * 60 * 1e3, pollInterval = 2e3) {
1155
+ this.indexerUrl = indexerUrl.replace(/\/$/, "");
1156
+ this.timeout = timeout;
1157
+ this.pollInterval = pollInterval;
1158
+ }
1159
+ /**
1160
+ * Generate a zero-knowledge proof using artifact-based flow
1161
+ *
1162
+ * This process typically takes 30-180 seconds depending on the TEE.
1163
+ * Private inputs are uploaded directly to TEE, never passing through backend.
1164
+ *
1165
+ * @param inputs - Circuit inputs (private + public + outputs)
1166
+ * @param options - Optional progress tracking and callbacks
1167
+ * @returns Proof result with hex-encoded proof and public inputs
1168
+ *
1169
+ * @example
1170
+ * ```typescript
1171
+ * const result = await prover.generateProof(inputs);
1172
+ * if (result.success) {
1173
+ * console.log(`Proof: ${result.proof}`);
1174
+ * }
1175
+ * ```
1176
+ */
1177
+ async generateProof(inputs, options) {
1178
+ const startTime = Date.now();
1179
+ const actualTimeout = options?.timeout || this.timeout;
1180
+ const pollInterval = options?.pollInterval || this.pollInterval;
1181
+ options?.onStart?.();
1182
+ options?.onProgress?.(5);
1183
+ try {
1184
+ options?.onProgress?.(10);
1185
+ const artifactResponse = await fetch(`${this.indexerUrl}/api/v1/tee/artifact`, {
1186
+ method: "POST",
1187
+ headers: {
1188
+ "Content-Type": "application/json"
1189
+ },
1190
+ body: JSON.stringify({
1191
+ program_id: null
1192
+ // Optional, can be null
1193
+ })
1194
+ });
1195
+ if (!artifactResponse.ok) {
1196
+ const errorText = await artifactResponse.text();
1197
+ const errorMessage2 = `Failed to create artifact: ${errorText}`;
1198
+ options?.onError?.(errorMessage2);
1199
+ return {
1200
+ success: false,
1201
+ generationTimeMs: Date.now() - startTime,
1202
+ error: errorMessage2
1203
+ };
1204
+ }
1205
+ const artifactData = await artifactResponse.json();
1206
+ const { artifact_id, upload_url } = artifactData;
1207
+ if (!artifact_id || !upload_url) {
1208
+ const errorMessage2 = "Invalid artifact response: missing artifact_id or upload_url";
1209
+ options?.onError?.(errorMessage2);
1210
+ return {
1211
+ success: false,
1212
+ generationTimeMs: Date.now() - startTime,
1213
+ error: errorMessage2
1214
+ };
1215
+ }
1216
+ const fullUploadUrl = upload_url.startsWith("http") ? upload_url : `${this.indexerUrl}${upload_url}`;
1217
+ options?.onProgress?.(20);
1218
+ const stdinPayload = JSON.stringify({
1219
+ private: inputs.privateInputs,
1220
+ public: inputs.publicInputs,
1221
+ outputs: inputs.outputs,
1222
+ // Include swap_params if present (for swap transactions)
1223
+ ...inputs.swapParams && { swap_params: inputs.swapParams }
1224
+ });
1225
+ const uploadResponse = await fetch(fullUploadUrl, {
1226
+ method: "POST",
1227
+ headers: {
1228
+ "Content-Type": "application/json"
1229
+ },
1230
+ body: stdinPayload
1231
+ });
1232
+ if (!uploadResponse.ok) {
1233
+ const errorText = await uploadResponse.text();
1234
+ const errorMessage2 = `Failed to upload stdin to TEE: ${errorText}`;
1235
+ options?.onError?.(errorMessage2);
1236
+ return {
1237
+ success: false,
1238
+ generationTimeMs: Date.now() - startTime,
1239
+ error: errorMessage2
1240
+ };
1241
+ }
1242
+ options?.onProgress?.(30);
1243
+ const requestProofBody = {
1244
+ artifact_id,
1245
+ program_id: null,
1246
+ public_inputs: JSON.stringify(inputs.publicInputs)
1247
+ };
1248
+ const requestProofResponse = await fetch(
1249
+ `${this.indexerUrl}/api/v1/tee/request-proof`,
1250
+ {
1251
+ method: "POST",
1252
+ headers: {
1253
+ "Content-Type": "application/json"
1254
+ },
1255
+ body: JSON.stringify(requestProofBody)
1256
+ }
1257
+ );
1258
+ if (!requestProofResponse.ok) {
1259
+ const errorText = await requestProofResponse.text();
1260
+ const errorMessage2 = `Failed to request proof: ${errorText}`;
1261
+ options?.onError?.(errorMessage2);
1262
+ return {
1263
+ success: false,
1264
+ generationTimeMs: Date.now() - startTime,
1265
+ error: errorMessage2
1266
+ };
1267
+ }
1268
+ const requestProofData = await requestProofResponse.json();
1269
+ const { request_id } = requestProofData;
1270
+ if (!request_id) {
1271
+ const errorMessage2 = "Invalid proof request response: missing request_id";
1272
+ options?.onError?.(errorMessage2);
1273
+ return {
1274
+ success: false,
1275
+ generationTimeMs: Date.now() - startTime,
1276
+ error: errorMessage2
1277
+ };
1278
+ }
1279
+ options?.onProgress?.(40);
1280
+ const pollStartTime = Date.now();
1281
+ let lastProgress = 40;
1282
+ while (Date.now() - pollStartTime < actualTimeout) {
1283
+ const statusResponse = await fetch(
1284
+ `${this.indexerUrl}/api/v1/tee/proof-status?request_id=${request_id}`,
1285
+ {
1286
+ method: "GET"
1287
+ }
1288
+ );
1289
+ if (!statusResponse.ok) {
1290
+ const errorText = await statusResponse.text();
1291
+ const errorMessage2 = `Failed to check proof status: ${errorText}`;
1292
+ options?.onError?.(errorMessage2);
1293
+ return {
1294
+ success: false,
1295
+ generationTimeMs: Date.now() - startTime,
1296
+ error: errorMessage2
1297
+ };
1298
+ }
1299
+ const statusData = await statusResponse.json();
1300
+ const { status, proof, public_inputs, generation_time_ms, error } = statusData;
1301
+ if (status === "ready") {
1302
+ options?.onProgress?.(100);
1303
+ if (!proof || !public_inputs) {
1304
+ const errorMessage2 = "Proof status is 'ready' but proof or public_inputs is missing";
1305
+ options?.onError?.(errorMessage2);
1306
+ return {
1307
+ success: false,
1308
+ generationTimeMs: Date.now() - startTime,
1309
+ error: errorMessage2
1310
+ };
1311
+ }
1312
+ const result = {
1313
+ success: true,
1314
+ proof,
1315
+ publicInputs: public_inputs,
1316
+ generationTimeMs: generation_time_ms || Date.now() - startTime
1317
+ };
1318
+ options?.onSuccess?.(result);
1319
+ return result;
1320
+ }
1321
+ if (status === "failed") {
1322
+ const errorMessage2 = error || "Proof generation failed";
1323
+ options?.onError?.(errorMessage2);
1324
+ return {
1325
+ success: false,
1326
+ generationTimeMs: Date.now() - startTime,
1327
+ error: errorMessage2
1328
+ };
1329
+ }
1330
+ const elapsed = Date.now() - pollStartTime;
1331
+ const progress = Math.min(90, 40 + Math.floor(elapsed / actualTimeout * 50));
1332
+ if (progress > lastProgress) {
1333
+ lastProgress = progress;
1334
+ options?.onProgress?.(progress);
1335
+ }
1336
+ await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
1337
+ }
1338
+ const errorMessage = `Proof generation timed out after ${actualTimeout}ms`;
1339
+ options?.onError?.(errorMessage);
1340
+ return {
1341
+ success: false,
1342
+ generationTimeMs: Date.now() - startTime,
1343
+ error: errorMessage
1344
+ };
1345
+ } catch (error) {
1346
+ const totalTime = Date.now() - startTime;
1347
+ let errorMessage;
1348
+ if (error instanceof Error && error.name === "AbortError") {
1349
+ errorMessage = `Proof generation timed out after ${actualTimeout}ms`;
1350
+ } else {
1351
+ errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
1352
+ }
1353
+ options?.onError?.(errorMessage);
1354
+ return {
1355
+ success: false,
1356
+ generationTimeMs: totalTime,
1357
+ error: errorMessage
1358
+ };
1359
+ }
1360
+ }
1361
+ /**
1362
+ * Check if the artifact prover service is available
1363
+ *
1364
+ * @returns True if service is healthy
1365
+ */
1366
+ async healthCheck() {
1367
+ try {
1368
+ const response = await fetch(`${this.indexerUrl}/health`, {
1369
+ method: "GET"
1370
+ });
1371
+ return response.ok;
1372
+ } catch {
1373
+ return false;
1374
+ }
1375
+ }
1376
+ /**
1377
+ * Get the configured timeout
1378
+ */
1379
+ getTimeout() {
1380
+ return this.timeout;
1381
+ }
1382
+ /**
1383
+ * Set a new timeout
1384
+ */
1385
+ setTimeout(timeout) {
1386
+ if (timeout <= 0) {
1387
+ throw new Error("Timeout must be positive");
1388
+ }
1389
+ this.timeout = timeout;
1390
+ }
1391
+ };
1392
+
1393
+ // src/services/RelayService.ts
1394
+ var RelayService = class {
1395
+ /**
1396
+ * Create a new Relay Service client
1397
+ *
1398
+ * @param baseUrl - Relay service base URL
1399
+ */
1400
+ constructor(baseUrl) {
1401
+ this.baseUrl = baseUrl.replace(/\/$/, "");
1402
+ }
1403
+ /**
1404
+ * Submit a withdrawal transaction via relay
1405
+ *
1406
+ * The relay service will validate the proof, pay for transaction fees,
1407
+ * and submit the transaction on-chain.
1408
+ *
1409
+ * @param params - Withdrawal parameters
1410
+ * @param onStatusUpdate - Optional callback for status updates
1411
+ * @returns Transaction signature when completed
1412
+ *
1413
+ * @example
1414
+ * ```typescript
1415
+ * const signature = await relay.submitWithdraw({
1416
+ * proof: proofHex,
1417
+ * publicInputs: { root, nf, outputs_hash, amount },
1418
+ * outputs: [{ recipient: addr, amount: lamports }],
1419
+ * feeBps: 50
1420
+ * }, (status) => console.log(`Status: ${status}`));
1421
+ * console.log(`Transaction: ${signature}`);
1422
+ * ```
1423
+ */
1424
+ async submitWithdraw(params, onStatusUpdate) {
1425
+ const proofBytes = hexToBytes(params.proof);
1426
+ const proofBase64 = this.bytesToBase64(proofBytes);
1427
+ const requestBody = {
1428
+ outputs: params.outputs,
1429
+ policy: {
1430
+ fee_bps: params.feeBps
1431
+ },
1432
+ public_inputs: {
1433
+ root: params.publicInputs.root,
1434
+ nf: params.publicInputs.nf,
1435
+ amount: params.publicInputs.amount,
1436
+ fee_bps: params.feeBps,
1437
+ outputs_hash: params.publicInputs.outputs_hash
1438
+ },
1439
+ proof_bytes: proofBase64
1440
+ };
1441
+ const response = await fetch(`${this.baseUrl}/withdraw`, {
1442
+ method: "POST",
1443
+ headers: {
1444
+ "Content-Type": "application/json"
1445
+ },
1446
+ body: JSON.stringify(requestBody)
1447
+ });
1448
+ if (!response.ok) {
1449
+ let errorMessage = `${response.status} ${response.statusText}`;
1450
+ try {
1451
+ const errorText = await response.text();
1452
+ errorMessage = errorText || errorMessage;
1453
+ } catch {
1454
+ }
1455
+ throw new Error(`Relay withdraw failed: ${errorMessage}`);
1456
+ }
1457
+ const json = await response.json();
1458
+ if (!json.success) {
1459
+ throw new Error(json.error || "Relay withdraw failed");
1460
+ }
1461
+ const requestId = json.data?.request_id;
1462
+ if (!requestId) {
1463
+ throw new Error("Relay response missing request_id");
1464
+ }
1465
+ return this.pollForCompletion(requestId, onStatusUpdate);
1466
+ }
1467
+ /**
1468
+ * Poll for withdrawal completion
1469
+ *
1470
+ * @param requestId - Request ID from relay service
1471
+ * @param onStatusUpdate - Optional callback for status updates
1472
+ * @returns Transaction signature when completed
1473
+ */
1474
+ async pollForCompletion(requestId, onStatusUpdate) {
1475
+ let attempts = 0;
1476
+ const maxAttempts = 120;
1477
+ const pollInterval = 5e3;
1478
+ while (attempts < maxAttempts) {
1479
+ await this.sleep(pollInterval);
1480
+ attempts++;
1481
+ try {
1482
+ const statusResp = await fetch(`${this.baseUrl}/status/${requestId}`);
1483
+ if (!statusResp.ok) {
1484
+ continue;
1485
+ }
1486
+ const statusJson = await statusResp.json();
1487
+ const statusData = statusJson.data;
1488
+ const status = statusData?.status;
1489
+ if (onStatusUpdate && status) {
1490
+ onStatusUpdate(status);
1491
+ }
1492
+ if (status === "completed") {
1493
+ const txId = statusData?.tx_id;
1494
+ if (!txId) {
1495
+ throw new Error("Relay completed without tx_id");
1496
+ }
1497
+ return txId;
1498
+ }
1499
+ if (status === "failed") {
1500
+ throw new Error(statusData?.error || "Relay job failed");
1501
+ }
1502
+ } catch (error) {
1503
+ if (error instanceof Error && error.message.includes("failed")) {
1504
+ throw error;
1505
+ }
1506
+ }
1507
+ }
1508
+ throw new Error(
1509
+ `Withdrawal polling timed out after ${maxAttempts * pollInterval}ms`
1510
+ );
1511
+ }
1512
+ /**
1513
+ * Submit a swap transaction via relay
1514
+ *
1515
+ * Similar to submitWithdraw but includes swap parameters for token swaps.
1516
+ * The relay service will validate the proof, execute the swap, pay for fees,
1517
+ * and submit the transaction on-chain.
1518
+ *
1519
+ * @param params - Swap parameters
1520
+ * @param onStatusUpdate - Optional callback for status updates
1521
+ * @returns Transaction signature when completed
1522
+ *
1523
+ * @example
1524
+ * ```typescript
1525
+ * const signature = await relay.submitSwap({
1526
+ * proof: proofHex,
1527
+ * publicInputs: { root, nf, outputs_hash, amount },
1528
+ * outputs: [{ recipient: addr, amount: lamports }],
1529
+ * feeBps: 50,
1530
+ * swap: {
1531
+ * output_mint: tokenMint.toBase58(),
1532
+ * slippage_bps: 100,
1533
+ * min_output_amount: minAmount
1534
+ * }
1535
+ * }, (status) => console.log(`Status: ${status}`));
1536
+ * console.log(`Transaction: ${signature}`);
1537
+ * ```
1538
+ */
1539
+ async submitSwap(params, onStatusUpdate) {
1540
+ const proofBytes = hexToBytes(params.proof);
1541
+ const proofBase64 = this.bytesToBase64(proofBytes);
1542
+ const requestBody = {
1543
+ outputs: params.outputs,
1544
+ swap: params.swap,
1545
+ policy: {
1546
+ fee_bps: params.feeBps
1547
+ },
1548
+ public_inputs: {
1549
+ root: params.publicInputs.root,
1550
+ nf: params.publicInputs.nf,
1551
+ amount: params.publicInputs.amount,
1552
+ fee_bps: params.feeBps,
1553
+ outputs_hash: params.publicInputs.outputs_hash
1554
+ },
1555
+ proof_bytes: proofBase64
1556
+ };
1557
+ const response = await fetch(`${this.baseUrl}/withdraw`, {
1558
+ method: "POST",
1559
+ headers: {
1560
+ "Content-Type": "application/json"
1561
+ },
1562
+ body: JSON.stringify(requestBody)
1563
+ });
1564
+ if (!response.ok) {
1565
+ let errorMessage = `${response.status} ${response.statusText}`;
1566
+ try {
1567
+ const errorText = await response.text();
1568
+ errorMessage = errorText || errorMessage;
1569
+ } catch {
1570
+ }
1571
+ throw new Error(`Relay swap failed: ${errorMessage}`);
1572
+ }
1573
+ const json = await response.json();
1574
+ if (!json.success) {
1575
+ throw new Error(json.error || "Relay swap failed");
1576
+ }
1577
+ const requestId = json.data?.request_id;
1578
+ if (!requestId) {
1579
+ throw new Error("Relay response missing request_id");
1580
+ }
1581
+ return this.pollForCompletion(requestId, onStatusUpdate);
1582
+ }
1583
+ /**
1584
+ * Get transaction status
1585
+ *
1586
+ * @param requestId - Request ID from previous submission
1587
+ * @returns Current status
1588
+ *
1589
+ * @example
1590
+ * ```typescript
1591
+ * const status = await relay.getStatus(requestId);
1592
+ * console.log(`Status: ${status.status}`);
1593
+ * if (status.status === 'completed') {
1594
+ * console.log(`TX: ${status.txId}`);
1595
+ * }
1596
+ * ```
1597
+ */
1598
+ async getStatus(requestId) {
1599
+ const response = await fetch(`${this.baseUrl}/status/${requestId}`);
1600
+ if (!response.ok) {
1601
+ throw new Error(
1602
+ `Failed to get status: ${response.status} ${response.statusText}`
1603
+ );
1604
+ }
1605
+ const json = await response.json();
1606
+ const data = json.data;
1607
+ return {
1608
+ status: data?.status || "pending",
1609
+ txId: data?.tx_id,
1610
+ error: data?.error
1611
+ };
1612
+ }
1613
+ /**
1614
+ * Convert bytes to base64 string
1615
+ */
1616
+ bytesToBase64(bytes) {
1617
+ if (typeof Buffer !== "undefined") {
1618
+ return Buffer.from(bytes).toString("base64");
1619
+ } else if (typeof btoa !== "undefined") {
1620
+ const binary = Array.from(bytes).map((b) => String.fromCharCode(b)).join("");
1621
+ return btoa(binary);
1622
+ } else {
1623
+ throw new Error("No base64 encoding method available");
1624
+ }
1625
+ }
1626
+ /**
1627
+ * Sleep utility
1628
+ */
1629
+ sleep(ms) {
1630
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
1631
+ }
1632
+ };
1633
+
1634
+ // src/services/DepositRecoveryService.ts
1635
+ var import_web33 = require("@solana/web3.js");
1636
+
1637
+ // src/helpers/encrypted-output.ts
1638
+ function prepareEncryptedOutput(note, cloakKeys) {
1639
+ const noteData = {
1640
+ amount: note.amount,
1641
+ r: note.r,
1642
+ sk_spend: note.sk_spend,
1643
+ commitment: note.commitment
1644
+ };
1645
+ const encrypted = encryptNoteForRecipient(noteData, cloakKeys.view.pvk);
1646
+ return btoa(JSON.stringify(encrypted));
1647
+ }
1648
+ function prepareEncryptedOutputForRecipient(note, recipientPvkHex) {
1649
+ const noteData = {
1650
+ amount: note.amount,
1651
+ r: note.r,
1652
+ sk_spend: note.sk_spend,
1653
+ commitment: note.commitment
1654
+ };
1655
+ const recipientPvk = hexToBytes(recipientPvkHex);
1656
+ const encrypted = encryptNoteForRecipient(noteData, recipientPvk);
1657
+ return btoa(JSON.stringify(encrypted));
1658
+ }
1659
+ function encodeNoteSimple(note) {
1660
+ const data = {
1661
+ amount: note.amount,
1662
+ r: note.r,
1663
+ sk_spend: note.sk_spend,
1664
+ commitment: note.commitment
1665
+ };
1666
+ const json = JSON.stringify(data);
1667
+ if (typeof Buffer !== "undefined") {
1668
+ return Buffer.from(json).toString("base64");
1669
+ } else if (typeof btoa !== "undefined") {
1670
+ return btoa(json);
1671
+ } else {
1672
+ throw new Error("No base64 encoding method available");
1673
+ }
1674
+ }
1675
+
1676
+ // src/services/DepositRecoveryService.ts
1677
+ var DepositRecoveryService = class {
1678
+ constructor(indexer, apiUrl) {
1679
+ this.indexer = indexer;
1680
+ this.apiUrl = apiUrl;
1681
+ }
1682
+ /**
1683
+ * Recover a deposit that completed on-chain but failed to register
1684
+ *
1685
+ * @param options Recovery options
1686
+ * @returns Recovery result with updated note
1687
+ */
1688
+ async recoverDeposit(options) {
1689
+ const { signature, commitment, note, onProgress } = options;
1690
+ try {
1691
+ onProgress?.("Validating inputs...");
1692
+ if (!/^[1-9A-HJ-NP-Za-km-z]{87,88}$/.test(signature)) {
1693
+ throw new CloakError(
1694
+ "Invalid transaction signature format",
1695
+ "validation",
1696
+ false
1697
+ );
1698
+ }
1699
+ if (!/^[0-9a-f]{64}$/i.test(commitment)) {
1700
+ throw new CloakError(
1701
+ "Invalid commitment format",
1702
+ "validation",
1703
+ false
1704
+ );
1705
+ }
1706
+ onProgress?.("Checking if deposit is already registered...");
1707
+ try {
1708
+ const existingInfo = await this.checkExistingDeposit(commitment);
1709
+ if (existingInfo) {
1710
+ onProgress?.("Deposit already registered!");
1711
+ return {
1712
+ success: true,
1713
+ ...existingInfo,
1714
+ note: note ? updateNoteWithDeposit(note, {
1715
+ signature,
1716
+ slot: existingInfo.slot,
1717
+ leafIndex: existingInfo.leafIndex,
1718
+ root: existingInfo.root,
1719
+ merkleProof: existingInfo.merkleProof
1720
+ }) : void 0
1721
+ };
1722
+ }
1723
+ } catch (e) {
1724
+ }
1725
+ onProgress?.("Fetching transaction details...");
1726
+ const connection = new import_web33.Connection(
1727
+ process.env.NEXT_PUBLIC_SOLANA_RPC_URL || "https://api.devnet.solana.com"
1728
+ );
1729
+ const txDetails = await connection.getTransaction(signature, {
1730
+ commitment: "confirmed",
1731
+ maxSupportedTransactionVersion: 0
1732
+ });
1733
+ if (!txDetails) {
1734
+ throw new CloakError(
1735
+ "Transaction not found on blockchain",
1736
+ "validation",
1737
+ false
1738
+ );
1739
+ }
1740
+ const slot = txDetails.slot;
1741
+ onProgress?.("Preparing encrypted output...");
1742
+ let encryptedOutput = "";
1743
+ if (note) {
1744
+ encryptedOutput = encodeNoteSimple(note);
1745
+ } else {
1746
+ encryptedOutput = btoa(JSON.stringify({ commitment }));
1747
+ }
1748
+ onProgress?.("Registering deposit with indexer...");
1749
+ const depositResponse = await this.indexer.submitDeposit({
1750
+ leafCommit: commitment,
1751
+ encryptedOutput,
1752
+ txSignature: signature,
1753
+ slot
1754
+ });
1755
+ if (!depositResponse.success) {
1756
+ throw new CloakError(
1757
+ "Failed to register deposit with indexer",
1758
+ "indexer",
1759
+ true
1760
+ );
1761
+ }
1762
+ const leafIndex = depositResponse.leafIndex;
1763
+ const root = depositResponse.root;
1764
+ onProgress?.("Fetching Merkle proof...");
1765
+ const merkleProof = await this.indexer.getMerkleProof(leafIndex);
1766
+ onProgress?.("Recovery complete!");
1767
+ const updatedNote = note ? updateNoteWithDeposit(note, {
1768
+ signature,
1769
+ slot,
1770
+ leafIndex,
1771
+ root,
1772
+ merkleProof: {
1773
+ pathElements: merkleProof.pathElements,
1774
+ pathIndices: merkleProof.pathIndices
1775
+ }
1776
+ }) : void 0;
1777
+ return {
1778
+ success: true,
1779
+ leafIndex,
1780
+ root,
1781
+ slot,
1782
+ merkleProof: {
1783
+ pathElements: merkleProof.pathElements,
1784
+ pathIndices: merkleProof.pathIndices
1785
+ },
1786
+ note: updatedNote
1787
+ };
1788
+ } catch (error) {
1789
+ const errorMessage = error instanceof Error ? error.message : String(error);
1790
+ if (errorMessage.includes("duplicate key") || errorMessage.includes("already exists")) {
1791
+ try {
1792
+ const existingInfo = await this.checkExistingDeposit(commitment);
1793
+ if (existingInfo) {
1794
+ return {
1795
+ success: true,
1796
+ ...existingInfo,
1797
+ note: note ? updateNoteWithDeposit(note, {
1798
+ signature,
1799
+ slot: existingInfo.slot,
1800
+ leafIndex: existingInfo.leafIndex,
1801
+ root: existingInfo.root,
1802
+ merkleProof: existingInfo.merkleProof
1803
+ }) : void 0
1804
+ };
1805
+ }
1806
+ } catch (e) {
1807
+ }
1808
+ }
1809
+ return {
1810
+ success: false,
1811
+ error: errorMessage
1812
+ };
1813
+ }
1814
+ }
1815
+ /**
1816
+ * Check if a deposit already exists in the indexer
1817
+ *
1818
+ * @private
1819
+ */
1820
+ async checkExistingDeposit(_commitment) {
1821
+ try {
1822
+ const { next_index } = await this.indexer.getMerkleRoot();
1823
+ const batchSize = 100;
1824
+ for (let i = 0; i < next_index; i += batchSize) {
1825
+ const end = Math.min(i + batchSize - 1, next_index - 1);
1826
+ const { notes } = await this.indexer.getNotesRange(i, end, batchSize);
1827
+ for (let j = 0; j < notes.length; j++) {
1828
+ try {
1829
+ return null;
1830
+ } catch (e) {
1831
+ continue;
1832
+ }
1833
+ }
1834
+ }
1835
+ return null;
1836
+ } catch (error) {
1837
+ return null;
1838
+ }
1839
+ }
1840
+ /**
1841
+ * Finalize a deposit via server API (alternative recovery method)
1842
+ *
1843
+ * This method calls a server-side endpoint that can handle
1844
+ * the recovery process with elevated permissions.
1845
+ */
1846
+ async finalizeDepositViaServer(signature, commitment, encryptedOutput) {
1847
+ try {
1848
+ const response = await fetch(`${this.apiUrl}/api/deposit/finalize`, {
1849
+ method: "POST",
1850
+ headers: { "Content-Type": "application/json" },
1851
+ body: JSON.stringify({
1852
+ tx_signature: signature,
1853
+ commitment,
1854
+ encrypted_output: encryptedOutput || btoa(JSON.stringify({ commitment }))
1855
+ })
1856
+ });
1857
+ if (!response.ok) {
1858
+ const errorText = await response.text();
1859
+ throw new Error(`Recovery failed: ${errorText}`);
1860
+ }
1861
+ const data = await response.json();
1862
+ if (!data.success) {
1863
+ throw new Error(data.error || "Recovery failed");
1864
+ }
1865
+ if (!data.leaf_index || !data.root || data.slot === void 0 || !data.merkle_proof) {
1866
+ throw new Error("Recovery response missing required fields");
1867
+ }
1868
+ return {
1869
+ success: true,
1870
+ leafIndex: data.leaf_index,
1871
+ root: data.root,
1872
+ slot: data.slot,
1873
+ merkleProof: {
1874
+ pathElements: data.merkle_proof.path_elements,
1875
+ pathIndices: data.merkle_proof.path_indices
1876
+ }
1877
+ };
1878
+ } catch (error) {
1879
+ return {
1880
+ success: false,
1881
+ error: error instanceof Error ? error.message : String(error)
1882
+ };
1883
+ }
1884
+ }
1885
+ };
1886
+
1887
+ // src/solana/instructions.ts
1888
+ var import_web34 = require("@solana/web3.js");
1889
+ function createDepositInstruction(params) {
1890
+ if (params.commitment.length !== 32) {
1891
+ throw new Error(
1892
+ `Invalid commitment length: ${params.commitment.length} (expected 32 bytes)`
1893
+ );
1894
+ }
1895
+ if (params.amount <= 0) {
1896
+ throw new Error("Amount must be positive");
1897
+ }
1898
+ const discriminant = new Uint8Array([1]);
1899
+ const amountBytes = new Uint8Array(8);
1900
+ new DataView(amountBytes.buffer).setBigUint64(
1901
+ 0,
1902
+ BigInt(params.amount),
1903
+ true
1904
+ // little-endian
1905
+ );
1906
+ const data = new Uint8Array(41);
1907
+ data.set(discriminant, 0);
1908
+ data.set(amountBytes, 1);
1909
+ data.set(params.commitment, 9);
1910
+ return new import_web34.TransactionInstruction({
1911
+ programId: params.programId,
1912
+ keys: [
1913
+ // Account 0: Payer (signer, writable) - pays for transaction
1914
+ { pubkey: params.payer, isSigner: true, isWritable: true },
1915
+ // Account 1: Pool (writable) - receives SOL
1916
+ { pubkey: params.pool, isSigner: false, isWritable: true },
1917
+ // Account 2: System Program (readonly) - for transfers
1918
+ { pubkey: import_web34.SystemProgram.programId, isSigner: false, isWritable: false },
1919
+ // Account 3: Merkle Tree (writable) - stores on-chain Merkle tree
1920
+ { pubkey: params.merkleTree, isSigner: false, isWritable: true }
1921
+ ],
1922
+ data: Buffer.from(data)
1923
+ });
1924
+ }
1925
+ function validateDepositParams(params) {
1926
+ if (!(params.programId instanceof import_web34.PublicKey)) {
1927
+ throw new Error("programId must be a PublicKey");
1928
+ }
1929
+ if (!(params.payer instanceof import_web34.PublicKey)) {
1930
+ throw new Error("payer must be a PublicKey");
1931
+ }
1932
+ if (!(params.pool instanceof import_web34.PublicKey)) {
1933
+ throw new Error("pool must be a PublicKey");
1934
+ }
1935
+ if (!(params.merkleTree instanceof import_web34.PublicKey)) {
1936
+ throw new Error("merkleTree must be a PublicKey");
1937
+ }
1938
+ if (typeof params.amount !== "number" || params.amount <= 0) {
1939
+ throw new Error("amount must be a positive number");
1940
+ }
1941
+ if (!(params.commitment instanceof Uint8Array)) {
1942
+ throw new Error("commitment must be a Uint8Array");
1943
+ }
1944
+ if (params.commitment.length !== 32) {
1945
+ throw new Error(
1946
+ `commitment must be 32 bytes (got ${params.commitment.length})`
1947
+ );
1948
+ }
1949
+ }
1950
+
1951
+ // src/utils/pda.ts
1952
+ var import_web35 = require("@solana/web3.js");
1953
+ function getShieldPoolPDAs(programId, mint) {
1954
+ const pid = programId || CLOAK_PROGRAM_ID;
1955
+ const mintKey = mint ?? new import_web35.PublicKey(
1956
+ // 32 zero bytes, matching Rust `Pubkey::default()`
1957
+ new Uint8Array(32)
1958
+ );
1959
+ const [pool] = import_web35.PublicKey.findProgramAddressSync(
1960
+ [Buffer.from("pool"), mintKey.toBytes()],
1961
+ pid
1962
+ );
1963
+ const [merkleTree] = import_web35.PublicKey.findProgramAddressSync(
1964
+ [Buffer.from("merkle_tree"), mintKey.toBytes()],
1965
+ pid
1966
+ );
1967
+ const [commitments] = import_web35.PublicKey.findProgramAddressSync(
1968
+ [Buffer.from("commitments"), mintKey.toBytes()],
1969
+ pid
1970
+ );
1971
+ const [rootsRing] = import_web35.PublicKey.findProgramAddressSync(
1972
+ [Buffer.from("roots_ring"), mintKey.toBytes()],
1973
+ pid
1974
+ );
1975
+ const [nullifierShard] = import_web35.PublicKey.findProgramAddressSync(
1976
+ [Buffer.from("nullifier_shard"), mintKey.toBytes()],
1977
+ pid
1978
+ );
1979
+ const [treasury] = import_web35.PublicKey.findProgramAddressSync(
1980
+ [Buffer.from("treasury"), mintKey.toBytes()],
1981
+ pid
1982
+ );
1983
+ return {
1984
+ pool,
1985
+ merkleTree,
1986
+ commitments,
1987
+ rootsRing,
1988
+ nullifierShard,
1989
+ treasury
1990
+ };
1991
+ }
1992
+
1993
+ // src/utils/proof-generation.ts
1994
+ var snarkjs = __toESM(require("snarkjs"), 1);
1995
+ var path = __toESM(require("path"), 1);
1996
+ var fs = __toESM(require("fs"), 1);
1997
+ async function generateWithdrawRegularProof(inputs, circuitsPath) {
1998
+ const wasmPath = path.join(circuitsPath, "build", "withdraw_regular_js", "withdraw_regular.wasm");
1999
+ const zkeyPath = path.join(circuitsPath, "build", "withdraw_regular_final.zkey");
2000
+ if (!fs.existsSync(wasmPath)) {
2001
+ throw new Error(`Circuit WASM not found at ${wasmPath}. Run 'just circuits-compile' in packages-new/circuits first.`);
2002
+ }
2003
+ if (!fs.existsSync(zkeyPath)) {
2004
+ throw new Error(`Circuit zkey not found at ${zkeyPath}. Run circuit setup first.`);
2005
+ }
2006
+ const circuitInputs = {
2007
+ // Public signals
2008
+ root: inputs.root.toString(),
2009
+ nullifier: inputs.nullifier.toString(),
2010
+ outputs_hash: inputs.outputs_hash.toString(),
2011
+ public_amount: inputs.public_amount.toString(),
2012
+ // Private common inputs
2013
+ amount: inputs.amount.toString(),
2014
+ leaf_index: inputs.leaf_index.toString(),
2015
+ sk: inputs.sk.map((x) => x.toString()),
2016
+ r: inputs.r.map((x) => x.toString()),
2017
+ pathElements: inputs.pathElements.map((x) => x.toString()),
2018
+ pathIndices: inputs.pathIndices,
2019
+ // Outputs
2020
+ num_outputs: inputs.num_outputs,
2021
+ out_addr: inputs.out_addr.map((addr) => addr.map((x) => x.toString())),
2022
+ out_amount: inputs.out_amount.map((x) => x.toString()),
2023
+ out_flags: inputs.out_flags,
2024
+ // Fee calculation
2025
+ var_fee: inputs.var_fee.toString(),
2026
+ rem: inputs.rem.toString()
2027
+ };
2028
+ const { proof, publicSignals } = await snarkjs.groth16.fullProve(
2029
+ circuitInputs,
2030
+ wasmPath,
2031
+ zkeyPath
2032
+ );
2033
+ const proofBytes = proofToBytes(proof);
2034
+ return {
2035
+ proof,
2036
+ publicSignals,
2037
+ proofBytes,
2038
+ publicInputsBytes: new Uint8Array(0)
2039
+ // Not used, kept for compatibility
2040
+ };
2041
+ }
2042
+ async function generateWithdrawSwapProof(inputs, circuitsPath) {
2043
+ const wasmPath = path.join(circuitsPath, "build", "withdraw_swap_js", "withdraw_swap.wasm");
2044
+ const zkeyPath = path.join(circuitsPath, "build", "withdraw_swap_final.zkey");
2045
+ if (!fs.existsSync(wasmPath)) {
2046
+ throw new Error(`Circuit WASM not found at ${wasmPath}. Run 'just circuits-compile' in packages-new/circuits first.`);
2047
+ }
2048
+ if (!fs.existsSync(zkeyPath)) {
2049
+ throw new Error(`Circuit zkey not found at ${zkeyPath}. Run circuit setup first.`);
2050
+ }
2051
+ const sk = splitTo2Limbs(inputs.sk_spend);
2052
+ const r = splitTo2Limbs(inputs.r);
2053
+ const circuitInputs = {
2054
+ // Public signals (must be provided for witness generation)
2055
+ root: inputs.root.toString(),
2056
+ nullifier: inputs.nullifier.toString(),
2057
+ outputs_hash: inputs.outputs_hash.toString(),
2058
+ public_amount: inputs.public_amount.toString(),
2059
+ // Private inputs
2060
+ sk: sk.map((x) => x.toString()),
2061
+ r: r.map((x) => x.toString()),
2062
+ amount: inputs.amount.toString(),
2063
+ leaf_index: inputs.leaf_index.toString(),
2064
+ pathElements: inputs.path_elements.map((x) => x.toString()),
2065
+ pathIndices: inputs.path_indices,
2066
+ input_mint: inputs.input_mint.map((x) => x.toString()),
2067
+ output_mint: inputs.output_mint.map((x) => x.toString()),
2068
+ recipient_ata: inputs.recipient_ata.map((x) => x.toString()),
2069
+ min_output_amount: inputs.min_output_amount.toString(),
2070
+ // Note: swap circuit computes fees internally:
2071
+ // - fixed_fee is hardcoded to 2500000 in the circuit (not an input)
2072
+ // - var_fee and rem are computed from amount * 5 / 1000
2073
+ var_fee: inputs.var_fee.toString(),
2074
+ rem: inputs.rem.toString()
2075
+ };
2076
+ const { proof, publicSignals } = await snarkjs.groth16.fullProve(
2077
+ circuitInputs,
2078
+ wasmPath,
2079
+ zkeyPath
2080
+ );
2081
+ const proofBytes = proofToBytes(proof);
2082
+ return {
2083
+ proof,
2084
+ publicSignals,
2085
+ proofBytes,
2086
+ publicInputsBytes: new Uint8Array(0)
2087
+ // Not used, kept for compatibility
2088
+ };
2089
+ }
2090
+ function areCircuitsAvailable(circuitsPath) {
2091
+ const wasmPath = path.join(circuitsPath, "build", "withdraw_regular_js", "withdraw_regular.wasm");
2092
+ const zkeyPath = path.join(circuitsPath, "build", "withdraw_regular_final.zkey");
2093
+ return fs.existsSync(wasmPath) && fs.existsSync(zkeyPath);
2094
+ }
2095
+ function getDefaultCircuitsPath() {
2096
+ const possiblePaths = [
2097
+ path.resolve(process.cwd(), "../../packages-new/circuits"),
2098
+ path.resolve(process.cwd(), "../packages-new/circuits"),
2099
+ path.resolve(process.cwd(), "packages-new/circuits"),
2100
+ path.resolve(process.cwd(), "../../circuits"),
2101
+ path.resolve(process.cwd(), "../circuits")
2102
+ ];
2103
+ for (const p of possiblePaths) {
2104
+ if (areCircuitsAvailable(p)) {
2105
+ return p;
2106
+ }
2107
+ }
2108
+ return possiblePaths[0];
2109
+ }
2110
+
2111
+ // src/core/CloakSDK.ts
2112
+ var CLOAK_PROGRAM_ID = new import_web36.PublicKey("c1oak6tetxYnNfvXKFkpn1d98FxtK7B68vBQLYQpWKp");
2113
+ var CLOAK_API_URL = "https://api.cloak.ag";
2114
+ var CloakSDK = class {
2115
+ /**
2116
+ * Create a new Cloak SDK client
2117
+ *
2118
+ * @param config - Client configuration
2119
+ *
2120
+ * @example
2121
+ * ```typescript
2122
+ * const sdk = new CloakSDK({
2123
+ * keypairBytes: keypair.secretKey,
2124
+ * network: "devnet"
2125
+ * });
2126
+ */
2127
+ constructor(config) {
2128
+ this.keypair = import_web36.Keypair.fromSecretKey(config.keypairBytes);
2129
+ this.cloakKeys = config.cloakKeys;
2130
+ this.storage = config.storage || new MemoryStorageAdapter();
2131
+ const indexerUrl = config.indexerUrl || process.env.CLOAK_INDEXER_URL || CLOAK_API_URL;
2132
+ const relayUrl = config.relayUrl || process.env.CLOAK_RELAY_URL || CLOAK_API_URL;
2133
+ this.indexer = new IndexerService(indexerUrl);
2134
+ this.artifactProver = new ArtifactProverService(indexerUrl, 5 * 60 * 1e3, 2e3);
2135
+ this.relay = new RelayService(relayUrl);
2136
+ this.depositRecovery = new DepositRecoveryService(this.indexer, indexerUrl);
2137
+ if (!this.cloakKeys) {
2138
+ const storedKeys = this.storage.loadKeys();
2139
+ if (storedKeys && !(storedKeys instanceof Promise)) {
2140
+ this.cloakKeys = storedKeys;
2141
+ }
2142
+ }
2143
+ const programId = config.programId || CLOAK_PROGRAM_ID;
2144
+ const { pool, merkleTree, commitments, rootsRing, nullifierShard, treasury } = getShieldPoolPDAs(programId);
2145
+ this.config = {
2146
+ network: config.network || "devnet",
2147
+ keypairBytes: config.keypairBytes,
2148
+ cloakKeys: config.cloakKeys,
2149
+ apiUrl: indexerUrl,
2150
+ // Store indexer URL as apiUrl for backwards compatibility
2151
+ programId,
2152
+ poolAddress: pool,
2153
+ merkleTreeAddress: merkleTree,
2154
+ commitmentsAddress: commitments,
2155
+ rootsRingAddress: rootsRing,
2156
+ nullifierShardAddress: nullifierShard,
2157
+ treasuryAddress: treasury
2158
+ };
2159
+ }
2160
+ /**
2161
+ * Deposit SOL into the Cloak protocol
2162
+ *
2163
+ * Creates a new note (or uses a provided one), submits a deposit transaction,
2164
+ * and registers with the indexer.
2165
+ *
2166
+ * @param connection - Solana connection
2167
+ * @param payer - Payer wallet with sendTransaction method
2168
+ * @param amountOrNote - Amount in lamports OR an existing note to deposit
2169
+ * @param options - Optional configuration
2170
+ * @returns Deposit result with note and transaction info
2171
+ *
2172
+ * @example
2173
+ * ```typescript
2174
+ * // Generate and deposit in one step
2175
+ * const result = await client.deposit(
2176
+ * connection,
2177
+ * wallet,
2178
+ * 1_000_000_000,
2179
+ * {
2180
+ * onProgress: (status) => console.log(status)
2181
+ * }
2182
+ * );
2183
+ *
2184
+ * // Or deposit a pre-generated note
2185
+ * const note = client.generateNote(1_000_000_000);
2186
+ * const result = await client.deposit(connection, wallet, note);
2187
+ * ```
2188
+ */
2189
+ async deposit(connection, amountOrNote, options) {
2190
+ try {
2191
+ let note;
2192
+ if (typeof amountOrNote === "number") {
2193
+ note = await generateNote(amountOrNote, this.config.network);
2194
+ } else {
2195
+ note = amountOrNote;
2196
+ if (note.depositSignature) {
2197
+ throw new Error("Note has already been deposited");
2198
+ }
2199
+ }
2200
+ const balance = await connection.getBalance(this.keypair.publicKey);
2201
+ const requiredAmount = note.amount + 5e3;
2202
+ if (balance < requiredAmount) {
2203
+ throw new Error(
2204
+ `Insufficient balance. Required: ${requiredAmount} lamports (${note.amount} + fees), Available: ${balance} lamports`
2205
+ );
2206
+ }
2207
+ const commitmentBytes = hexToBytes(note.commitment);
2208
+ const programId = this.config.programId || CLOAK_PROGRAM_ID;
2209
+ const depositIx = createDepositInstruction({
2210
+ programId,
2211
+ payer: this.keypair.publicKey,
2212
+ pool: this.config.poolAddress,
2213
+ merkleTree: this.config.merkleTreeAddress,
2214
+ amount: note.amount,
2215
+ commitment: commitmentBytes
2216
+ });
2217
+ const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
2218
+ const transaction = new import_web36.Transaction({
2219
+ feePayer: this.keypair.publicKey,
2220
+ blockhash,
2221
+ lastValidBlockHeight
2222
+ }).add(depositIx);
2223
+ if (!options?.skipPreflight) {
2224
+ const simulation = await connection.simulateTransaction(transaction);
2225
+ if (simulation.value.err) {
2226
+ const logs = simulation.value.logs?.join("\n") || "No logs";
2227
+ throw new Error(
2228
+ `Transaction simulation failed: ${JSON.stringify(simulation.value.err)}
2229
+ Logs:
2230
+ ${logs}`
2231
+ );
2232
+ }
2233
+ }
2234
+ const signature = await connection.sendTransaction(transaction, [this.keypair], {
2235
+ skipPreflight: options?.skipPreflight || false,
2236
+ preflightCommitment: "confirmed",
2237
+ maxRetries: 3
2238
+ });
2239
+ const confirmation = await connection.confirmTransaction({
2240
+ signature,
2241
+ blockhash,
2242
+ lastValidBlockHeight
2243
+ });
2244
+ if (confirmation.value.err) {
2245
+ throw new Error(
2246
+ `Transaction failed: ${JSON.stringify(confirmation.value.err)}`
2247
+ );
2248
+ }
2249
+ const txDetails = await connection.getTransaction(signature, {
2250
+ commitment: "confirmed",
2251
+ maxSupportedTransactionVersion: 0
2252
+ });
2253
+ const depositSlot = txDetails?.slot ?? 0;
2254
+ let leafIndex = null;
2255
+ let root = null;
2256
+ try {
2257
+ if (txDetails?.meta?.logMessages) {
2258
+ for (const log of txDetails.meta.logMessages) {
2259
+ if (log.includes("Program data:")) {
2260
+ try {
2261
+ const parts = log.split("Program data:");
2262
+ if (parts.length > 1) {
2263
+ const base64Data = parts[1].trim();
2264
+ const eventData = Buffer.from(base64Data, "base64");
2265
+ if (eventData.length >= 81 && eventData[0] === 1) {
2266
+ const parsedLeafIndex = Number(eventData.readBigUInt64LE(1));
2267
+ const loggedCommitment = eventData.slice(9, 41);
2268
+ if (Buffer.compare(loggedCommitment, Buffer.from(commitmentBytes)) === 0) {
2269
+ leafIndex = parsedLeafIndex;
2270
+ const loggedRoot = eventData.slice(49, 81);
2271
+ root = Buffer.from(loggedRoot).toString("hex");
2272
+ break;
2273
+ }
2274
+ }
2275
+ }
2276
+ } catch (e) {
2277
+ continue;
2278
+ }
2279
+ }
2280
+ }
2281
+ }
2282
+ } catch (e) {
2283
+ }
2284
+ if (leafIndex === null) {
2285
+ const commitmentHex = Buffer.from(commitmentBytes).toString("hex").toLowerCase();
2286
+ const maxRetries2 = 60;
2287
+ const initialRetryDelay2 = 500;
2288
+ for (let attempt = 0; attempt < maxRetries2; attempt++) {
2289
+ try {
2290
+ const indexerBaseUrl = this.config.apiUrl || CLOAK_API_URL;
2291
+ const response = await fetch(`${indexerBaseUrl}/api/v1/deposit/${commitmentHex}`);
2292
+ if (response.ok) {
2293
+ const data = await response.json();
2294
+ leafIndex = data.leaf_index;
2295
+ const rootResponse = await this.indexer.getMerkleRoot();
2296
+ root = rootResponse.root;
2297
+ break;
2298
+ } else if (response.status === 404 && attempt < maxRetries2 - 1) {
2299
+ const delay = Math.min(initialRetryDelay2 * (attempt + 1), 2e3);
2300
+ await new Promise((resolve2) => setTimeout(resolve2, delay));
2301
+ continue;
2302
+ }
2303
+ } catch (e) {
2304
+ if (attempt < maxRetries2 - 1) {
2305
+ const delay = Math.min(initialRetryDelay2 * (attempt + 1), 2e3);
2306
+ await new Promise((resolve2) => setTimeout(resolve2, delay));
2307
+ continue;
2308
+ }
2309
+ }
2310
+ }
2311
+ }
2312
+ if (leafIndex === null) {
2313
+ const programId2 = this.config.programId || CLOAK_PROGRAM_ID;
2314
+ const mintForSOL = new import_web36.PublicKey(new Uint8Array(32));
2315
+ const { merkleTree } = getShieldPoolPDAs(programId2, mintForSOL);
2316
+ const merkleTreeAccount = await connection.getAccountInfo(merkleTree);
2317
+ if (!merkleTreeAccount) {
2318
+ throw new Error("Failed to fetch merkle tree account");
2319
+ }
2320
+ const nextIndex = Number(merkleTreeAccount.data.readBigUInt64LE(32));
2321
+ leafIndex = nextIndex - 1;
2322
+ const rootBytes = merkleTreeAccount.data.slice(1064, 1096);
2323
+ root = Buffer.from(rootBytes).toString("hex");
2324
+ }
2325
+ if (leafIndex === null || root === null) {
2326
+ throw new Error("Failed to get leaf index and root from transaction, indexer, or on-chain state");
2327
+ }
2328
+ let merkleProof = null;
2329
+ const maxRetries = 60;
2330
+ const initialRetryDelay = 500;
2331
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
2332
+ try {
2333
+ merkleProof = await this.indexer.getMerkleProof(leafIndex);
2334
+ break;
2335
+ } catch (error) {
2336
+ const errorMessage = error instanceof Error ? error.message : String(error);
2337
+ if (errorMessage.includes("404") && attempt < maxRetries - 1) {
2338
+ const delay = Math.min(initialRetryDelay * (attempt + 1), 2e3);
2339
+ await new Promise((resolve2) => setTimeout(resolve2, delay));
2340
+ continue;
2341
+ }
2342
+ throw error;
2343
+ }
2344
+ }
2345
+ if (!merkleProof) {
2346
+ throw new Error("Failed to get Merkle proof after retries");
2347
+ }
2348
+ const updatedNote = updateNoteWithDeposit(note, {
2349
+ signature,
2350
+ slot: depositSlot,
2351
+ leafIndex,
2352
+ root,
2353
+ merkleProof: {
2354
+ pathElements: merkleProof.pathElements,
2355
+ pathIndices: merkleProof.pathIndices
2356
+ }
2357
+ });
2358
+ return {
2359
+ note: updatedNote,
2360
+ signature,
2361
+ leafIndex,
2362
+ root
2363
+ };
2364
+ } catch (error) {
2365
+ throw this.wrapError(error, "Deposit failed");
2366
+ }
2367
+ }
2368
+ /**
2369
+ * Private transfer with up to 5 recipients
2370
+ *
2371
+ * Handles the complete private transfer flow:
2372
+ * 1. If note is not deposited, deposits it first and waits for confirmation
2373
+ * 2. Generates a zero-knowledge proof
2374
+ * 3. Submits the withdrawal via relay service to recipients
2375
+ *
2376
+ * This is the main method for performing private transfers - it handles everything!
2377
+ *
2378
+ * @param connection - Solana connection (required for deposit if not already deposited)
2379
+ * @param payer - Payer wallet (required for deposit if not already deposited)
2380
+ * @param note - Note to spend (can be deposited or not)
2381
+ * @param recipients - Array of 1-5 recipients with amounts
2382
+ * @param options - Optional configuration
2383
+ * @returns Transfer result with signature and outputs
2384
+ *
2385
+ * @example
2386
+ * ```typescript
2387
+ * // Create a note (not deposited yet)
2388
+ * const note = client.generateNote(1_000_000_000);
2389
+ *
2390
+ * // privateTransfer handles the full flow: deposit + withdraw
2391
+ * const result = await client.privateTransfer(
2392
+ * connection,
2393
+ * wallet,
2394
+ * note,
2395
+ * [
2396
+ * { recipient: new PublicKey("..."), amount: 500_000_000 },
2397
+ * { recipient: new PublicKey("..."), amount: 492_500_000 }
2398
+ * ],
2399
+ * {
2400
+ * relayFeeBps: 50, // 0.5%
2401
+ * onProgress: (status) => console.log(status),
2402
+ * onProofProgress: (pct) => console.log(`Proof: ${pct}%`)
2403
+ * }
2404
+ * );
2405
+ * console.log(`Success! TX: ${result.signature}`);
2406
+ * ```
2407
+ */
2408
+ async privateTransfer(connection, note, recipients, options) {
2409
+ if (!isWithdrawable(note)) {
2410
+ const depositResult = await this.deposit(connection, note, {
2411
+ skipPreflight: false
2412
+ });
2413
+ note = depositResult.note;
2414
+ }
2415
+ const protocolFee = note.amount - getDistributableAmount2(note.amount);
2416
+ const feeBps = Math.ceil(protocolFee * 1e4 / note.amount);
2417
+ const distributableAmount = getDistributableAmount2(note.amount);
2418
+ validateTransfers(recipients, distributableAmount);
2419
+ let merkleProof;
2420
+ let merkleRoot;
2421
+ if (note.merkleProof && note.root) {
2422
+ merkleProof = {
2423
+ pathElements: note.merkleProof.pathElements,
2424
+ pathIndices: note.merkleProof.pathIndices
2425
+ };
2426
+ merkleRoot = note.root;
2427
+ } else {
2428
+ merkleProof = await this.indexer.getMerkleProof(note.leafIndex);
2429
+ merkleRoot = merkleProof.root || (await this.indexer.getMerkleRoot()).root;
2430
+ }
2431
+ const nullifier = await computeNullifierAsync(note.sk_spend, note.leafIndex);
2432
+ const nullifierHex = nullifier.toString(16).padStart(64, "0");
2433
+ const outputsHash = await computeOutputsHashAsync(recipients);
2434
+ const outputsHashHex = outputsHash.toString(16).padStart(64, "0");
2435
+ if (!note.leafIndex && note.leafIndex !== 0) {
2436
+ throw new Error("Note must have a leaf index (note must be deposited)");
2437
+ }
2438
+ if (!merkleProof.pathElements || merkleProof.pathElements.length === 0) {
2439
+ throw new Error("Merkle proof is invalid: missing path elements");
2440
+ }
2441
+ if (merkleProof.pathElements.length !== merkleProof.pathIndices.length) {
2442
+ throw new Error("Merkle proof is invalid: path elements and indices length mismatch");
2443
+ }
2444
+ for (let i = 0; i < merkleProof.pathIndices.length; i++) {
2445
+ const idx = merkleProof.pathIndices[i];
2446
+ if (idx !== 0 && idx !== 1) {
2447
+ throw new Error(`Merkle proof path index at position ${i} must be 0 or 1, got ${idx}`);
2448
+ }
2449
+ }
2450
+ if (!isValidHex(note.r, 32)) {
2451
+ throw new Error("Note r must be 64 hex characters (32 bytes)");
2452
+ }
2453
+ if (!isValidHex(note.sk_spend, 32)) {
2454
+ throw new Error("Note sk_spend must be 64 hex characters (32 bytes)");
2455
+ }
2456
+ if (!isValidHex(merkleRoot, 32)) {
2457
+ throw new Error("Merkle root must be 64 hex characters (32 bytes)");
2458
+ }
2459
+ for (let i = 0; i < merkleProof.pathElements.length; i++) {
2460
+ const element = merkleProof.pathElements[i];
2461
+ if (typeof element !== "string" || !isValidHex(element, 32)) {
2462
+ throw new Error(`Merkle proof path element at position ${i} must be 64 hex characters (32 bytes)`);
2463
+ }
2464
+ }
2465
+ const circuitsPath = process.env.CIRCUITS_PATH || getDefaultCircuitsPath();
2466
+ const useDirectProof = areCircuitsAvailable(circuitsPath);
2467
+ let proofHex;
2468
+ let finalPublicInputs;
2469
+ if (useDirectProof) {
2470
+ const sk_spend_bigint = BigInt("0x" + note.sk_spend);
2471
+ const r_bigint = BigInt("0x" + note.r);
2472
+ const root_bigint = BigInt("0x" + merkleRoot);
2473
+ const nullifier_bigint = BigInt("0x" + nullifierHex);
2474
+ const outputs_hash_bigint = BigInt("0x" + outputsHashHex);
2475
+ const amount_bigint = BigInt(note.amount);
2476
+ const pathElements = merkleProof.pathElements.map((p) => BigInt("0x" + p));
2477
+ const outAddr = [];
2478
+ for (let i = 0; i < 5; i++) {
2479
+ if (i < recipients.length) {
2480
+ const [lo, hi] = pubkeyToLimbs(recipients[i].recipient.toBytes());
2481
+ outAddr.push([lo, hi]);
2482
+ } else {
2483
+ outAddr.push([0n, 0n]);
2484
+ }
2485
+ }
2486
+ const t = amount_bigint * 5n;
2487
+ const varFee = t / 1000n;
2488
+ const rem = t % 1000n;
2489
+ const outAmount = [];
2490
+ const outFlags = [];
2491
+ for (let i = 0; i < 5; i++) {
2492
+ if (i < recipients.length) {
2493
+ outAmount.push(BigInt(recipients[i].amount));
2494
+ outFlags.push(1);
2495
+ } else {
2496
+ outAmount.push(0n);
2497
+ outFlags.push(0);
2498
+ }
2499
+ }
2500
+ const sk = splitTo2Limbs(sk_spend_bigint);
2501
+ const r = splitTo2Limbs(r_bigint);
2502
+ const proofInputs = {
2503
+ root: root_bigint,
2504
+ nullifier: nullifier_bigint,
2505
+ outputs_hash: outputs_hash_bigint,
2506
+ public_amount: amount_bigint,
2507
+ amount: amount_bigint,
2508
+ leaf_index: BigInt(note.leafIndex),
2509
+ sk,
2510
+ r,
2511
+ pathElements,
2512
+ pathIndices: merkleProof.pathIndices,
2513
+ num_outputs: recipients.length,
2514
+ out_addr: outAddr,
2515
+ out_amount: outAmount,
2516
+ out_flags: outFlags,
2517
+ var_fee: varFee,
2518
+ rem
2519
+ };
2520
+ const proofResult = await generateWithdrawRegularProof(proofInputs, circuitsPath);
2521
+ proofHex = Buffer.from(proofResult.proofBytes).toString("hex");
2522
+ finalPublicInputs = {
2523
+ root: merkleRoot,
2524
+ nf: nullifierHex,
2525
+ outputs_hash: outputsHashHex,
2526
+ amount: note.amount
2527
+ };
2528
+ } else {
2529
+ const proofInputs = {
2530
+ privateInputs: {
2531
+ amount: note.amount,
2532
+ r: note.r,
2533
+ sk_spend: note.sk_spend,
2534
+ leaf_index: note.leafIndex,
2535
+ merkle_path: {
2536
+ path_elements: merkleProof.pathElements,
2537
+ path_indices: merkleProof.pathIndices
2538
+ }
2539
+ },
2540
+ publicInputs: {
2541
+ root: merkleRoot,
2542
+ nf: nullifierHex,
2543
+ outputs_hash: outputsHashHex,
2544
+ amount: note.amount
2545
+ },
2546
+ outputs: recipients.map((r) => ({
2547
+ address: r.recipient.toBase58(),
2548
+ amount: r.amount
2549
+ }))
2550
+ };
2551
+ const proofResult = await this.artifactProver.generateProof(proofInputs, {
2552
+ onProgress: options?.onProofProgress,
2553
+ onError: options?.onProgress ? (error) => options.onProgress?.(`Proof generation error: ${error}`) : void 0
2554
+ });
2555
+ if (!proofResult.success || !proofResult.proof) {
2556
+ let errorMessage = proofResult.error || "Proof generation failed";
2557
+ if (errorMessage.startsWith("Proof generation failed: ")) {
2558
+ errorMessage = errorMessage.substring("Proof generation failed: ".length);
2559
+ }
2560
+ errorMessage += `
2561
+ Note details: leafIndex=${note.leafIndex}, root=${merkleRoot.slice(0, 16)}..., nullifier=${nullifierHex.slice(0, 16)}...`;
2562
+ throw new Error(errorMessage);
2563
+ }
2564
+ proofHex = proofResult.proof;
2565
+ finalPublicInputs = proofInputs.publicInputs;
2566
+ }
2567
+ const signature = await this.relay.submitWithdraw(
2568
+ {
2569
+ proof: proofHex,
2570
+ publicInputs: finalPublicInputs,
2571
+ outputs: recipients.map((r) => ({
2572
+ recipient: r.recipient.toBase58(),
2573
+ amount: r.amount
2574
+ })),
2575
+ feeBps
2576
+ // Use calculated protocol fee BPS
2577
+ },
2578
+ options?.onProgress
2579
+ );
2580
+ return {
2581
+ signature,
2582
+ outputs: recipients.map((r) => ({
2583
+ recipient: r.recipient.toBase58(),
2584
+ amount: r.amount
2585
+ })),
2586
+ nullifier: nullifierHex,
2587
+ root: merkleRoot
2588
+ };
2589
+ }
2590
+ /**
2591
+ * Withdraw to a single recipient
2592
+ *
2593
+ * Convenience method for withdrawing to one address.
2594
+ * Handles the complete flow: deposits if needed, then withdraws.
2595
+ *
2596
+ * @param connection - Solana connection
2597
+ * @param payer - Payer wallet
2598
+ * @param note - Note to spend
2599
+ * @param recipient - Recipient address
2600
+ * @param options - Optional configuration
2601
+ * @returns Transfer result
2602
+ *
2603
+ * @example
2604
+ * ```typescript
2605
+ * const note = client.generateNote(1_000_000_000);
2606
+ * const result = await client.withdraw(
2607
+ * connection,
2608
+ * wallet,
2609
+ * note,
2610
+ * new PublicKey("..."),
2611
+ * { withdrawAll: true }
2612
+ * );
2613
+ * ```
2614
+ */
2615
+ async withdraw(connection, note, recipient, options) {
2616
+ const withdrawAll = options?.withdrawAll ?? true;
2617
+ const amount = withdrawAll ? getDistributableAmount2(note.amount) : options?.amount || note.amount;
2618
+ if (!withdrawAll && !options?.amount) {
2619
+ throw new Error("Must specify amount or set withdrawAll: true");
2620
+ }
2621
+ return this.privateTransfer(
2622
+ connection,
2623
+ note,
2624
+ [{ recipient, amount }],
2625
+ options
2626
+ );
2627
+ }
2628
+ /**
2629
+ * Send SOL privately to multiple recipients
2630
+ *
2631
+ * Convenience method that wraps privateTransfer with a simpler API.
2632
+ * Handles the complete flow: deposits if needed, then sends to recipients.
2633
+ *
2634
+ * @param connection - Solana connection
2635
+ * @param note - Note to spend
2636
+ * @param recipients - Array of 1-5 recipients with amounts
2637
+ * @param options - Optional configuration
2638
+ * @returns Transfer result
2639
+ *
2640
+ * @example
2641
+ * ```typescript
2642
+ * const note = client.generateNote(1_000_000_000);
2643
+ * const result = await client.send(
2644
+ * connection,
2645
+ * note,
2646
+ * [
2647
+ * { recipient: new PublicKey("..."), amount: 500_000_000 },
2648
+ * { recipient: new PublicKey("..."), amount: 492_500_000 }
2649
+ * ]
2650
+ * );
2651
+ * ```
2652
+ */
2653
+ async send(connection, note, recipients, options) {
2654
+ return this.privateTransfer(connection, note, recipients, options);
2655
+ }
2656
+ /**
2657
+ * Swap SOL for tokens privately
2658
+ *
2659
+ * Withdraws SOL from a note and swaps it for tokens via the relay service.
2660
+ * Handles the complete flow: deposits if needed, generates proof, and submits swap.
2661
+ *
2662
+ * @param connection - Solana connection
2663
+ * @param note - Note to spend
2664
+ * @param recipient - Recipient address (will receive tokens)
2665
+ * @param options - Swap configuration
2666
+ * @returns Swap result with transaction signature
2667
+ *
2668
+ * @example
2669
+ * ```typescript
2670
+ * const note = client.generateNote(1_000_000_000);
2671
+ * const result = await client.swap(
2672
+ * connection,
2673
+ * note,
2674
+ * new PublicKey("..."), // recipient
2675
+ * {
2676
+ * outputMint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC
2677
+ * slippageBps: 100, // 1%
2678
+ * getQuote: async (amount, mint, slippage) => {
2679
+ * // Fetch quote from your swap API
2680
+ * const quote = await fetchSwapQuote(amount, mint, slippage);
2681
+ * return {
2682
+ * outAmount: quote.outAmount,
2683
+ * minOutputAmount: quote.minOutputAmount
2684
+ * };
2685
+ * }
2686
+ * }
2687
+ * );
2688
+ * ```
2689
+ */
2690
+ async swap(connection, note, recipient, options) {
2691
+ try {
2692
+ if (!isWithdrawable(note)) {
2693
+ const depositResult = await this.deposit(connection, note, {
2694
+ skipPreflight: false
2695
+ });
2696
+ note = depositResult.note;
2697
+ }
2698
+ const variableFee = Math.floor(note.amount * 5 / 1e3);
2699
+ const feeBps = note.amount === 0 ? 0 : Math.min(Math.floor((variableFee * 1e4 + note.amount - 1) / note.amount), 65535);
2700
+ const withdrawAmountLamports = getDistributableAmount2(note.amount);
2701
+ if (withdrawAmountLamports <= 0) {
2702
+ throw new Error("Amount too small after fees");
2703
+ }
2704
+ let minOutputAmount;
2705
+ if (options.minOutputAmount !== void 0) {
2706
+ minOutputAmount = options.minOutputAmount;
2707
+ } else if (options.getQuote) {
2708
+ const quote = await options.getQuote(
2709
+ withdrawAmountLamports,
2710
+ options.outputMint,
2711
+ options.slippageBps || 100
2712
+ );
2713
+ minOutputAmount = quote.minOutputAmount;
2714
+ } else {
2715
+ throw new Error(
2716
+ "Must provide either minOutputAmount or getQuote function"
2717
+ );
2718
+ }
2719
+ let recipientAta;
2720
+ try {
2721
+ const splTokenModule = await import("@solana/spl-token");
2722
+ const getAssociatedTokenAddress = splTokenModule.getAssociatedTokenAddress;
2723
+ if (!getAssociatedTokenAddress) {
2724
+ throw new Error("getAssociatedTokenAddress not found");
2725
+ }
2726
+ const outputMint2 = new import_web36.PublicKey(options.outputMint);
2727
+ recipientAta = await getAssociatedTokenAddress(outputMint2, recipient);
2728
+ } catch (error) {
2729
+ throw new Error(
2730
+ `Failed to get associated token account: ${error instanceof Error ? error.message : String(error)}. Please install @solana/spl-token or provide recipientAta in options.`
2731
+ );
2732
+ }
2733
+ let merkleProof;
2734
+ let merkleRoot;
2735
+ if (note.merkleProof && note.root) {
2736
+ merkleProof = {
2737
+ pathElements: note.merkleProof.pathElements,
2738
+ pathIndices: note.merkleProof.pathIndices
2739
+ };
2740
+ merkleRoot = note.root;
2741
+ } else {
2742
+ merkleProof = await this.indexer.getMerkleProof(note.leafIndex);
2743
+ merkleRoot = merkleProof.root || (await this.indexer.getMerkleRoot()).root;
2744
+ }
2745
+ const nullifier = await computeNullifierAsync(note.sk_spend, note.leafIndex);
2746
+ const nullifierHex = nullifier.toString(16).padStart(64, "0");
2747
+ const inputMint = new import_web36.PublicKey("11111111111111111111111111111111");
2748
+ const outputMint = new import_web36.PublicKey(options.outputMint);
2749
+ const outputsHash = await computeSwapOutputsHashAsync(
2750
+ inputMint,
2751
+ outputMint,
2752
+ recipientAta,
2753
+ minOutputAmount,
2754
+ note.amount
2755
+ );
2756
+ const outputsHashHex = outputsHash.toString(16).padStart(64, "0");
2757
+ if (!note.leafIndex && note.leafIndex !== 0) {
2758
+ throw new Error("Note must have a leaf index (note must be deposited)");
2759
+ }
2760
+ if (!merkleProof.pathElements || merkleProof.pathElements.length === 0) {
2761
+ throw new Error("Merkle proof is invalid: missing path elements");
2762
+ }
2763
+ const circuitsPath = process.env.CIRCUITS_PATH || getDefaultCircuitsPath();
2764
+ const useDirectProof = areCircuitsAvailable(circuitsPath);
2765
+ let proofHex;
2766
+ let finalPublicInputs;
2767
+ if (useDirectProof) {
2768
+ const sk_spend_bigint = BigInt("0x" + note.sk_spend);
2769
+ const r_bigint = BigInt("0x" + note.r);
2770
+ const root_bigint = BigInt("0x" + merkleRoot);
2771
+ const nullifier_bigint = BigInt("0x" + nullifierHex);
2772
+ const outputs_hash_bigint = BigInt("0x" + outputsHashHex);
2773
+ const amount_bigint = BigInt(note.amount);
2774
+ const pathElements = merkleProof.pathElements.map((p) => BigInt("0x" + p));
2775
+ const inputMintLimbs = pubkeyToLimbs(inputMint.toBytes());
2776
+ const outputMintLimbs = pubkeyToLimbs(outputMint.toBytes());
2777
+ const recipientAtaLimbs = pubkeyToLimbs(recipientAta.toBytes());
2778
+ const t = amount_bigint * 5n;
2779
+ const varFee = t / 1000n;
2780
+ const rem = t % 1000n;
2781
+ const proofInputs = {
2782
+ sk_spend: sk_spend_bigint,
2783
+ r: r_bigint,
2784
+ amount: amount_bigint,
2785
+ leaf_index: BigInt(note.leafIndex),
2786
+ path_elements: pathElements,
2787
+ path_indices: merkleProof.pathIndices,
2788
+ root: root_bigint,
2789
+ nullifier: nullifier_bigint,
2790
+ outputs_hash: outputs_hash_bigint,
2791
+ public_amount: amount_bigint,
2792
+ input_mint: inputMintLimbs,
2793
+ output_mint: outputMintLimbs,
2794
+ recipient_ata: recipientAtaLimbs,
2795
+ min_output_amount: BigInt(minOutputAmount),
2796
+ var_fee: varFee,
2797
+ rem
2798
+ };
2799
+ const proofResult = await generateWithdrawSwapProof(proofInputs, circuitsPath);
2800
+ proofHex = Buffer.from(proofResult.proofBytes).toString("hex");
2801
+ finalPublicInputs = {
2802
+ root: merkleRoot,
2803
+ nf: nullifierHex,
2804
+ outputs_hash: outputsHashHex,
2805
+ amount: note.amount
2806
+ };
2807
+ } else {
2808
+ const proofInputs = {
2809
+ privateInputs: {
2810
+ amount: note.amount,
2811
+ r: note.r,
2812
+ sk_spend: note.sk_spend,
2813
+ leaf_index: note.leafIndex,
2814
+ merkle_path: {
2815
+ path_elements: merkleProof.pathElements,
2816
+ path_indices: merkleProof.pathIndices
2817
+ }
2818
+ },
2819
+ publicInputs: {
2820
+ root: merkleRoot,
2821
+ nf: nullifierHex,
2822
+ outputs_hash: outputsHashHex,
2823
+ amount: note.amount
2824
+ },
2825
+ outputs: [],
2826
+ // Empty for swaps
2827
+ swapParams: {
2828
+ output_mint: options.outputMint,
2829
+ recipient_ata: recipientAta.toBase58(),
2830
+ min_output_amount: minOutputAmount
2831
+ }
2832
+ };
2833
+ const proofResult = await this.artifactProver.generateProof(proofInputs, {
2834
+ onProgress: options.onProofProgress,
2835
+ onError: options.onProgress ? (error) => options.onProgress?.(`Proof generation error: ${error}`) : void 0
2836
+ });
2837
+ if (!proofResult.success || !proofResult.proof) {
2838
+ let errorMessage = proofResult.error || "Proof generation failed";
2839
+ if (errorMessage.startsWith("Proof generation failed: ")) {
2840
+ errorMessage = errorMessage.substring("Proof generation failed: ".length);
2841
+ }
2842
+ errorMessage += `
2843
+ Note details: leafIndex=${note.leafIndex}, root=${merkleRoot.slice(0, 16)}..., nullifier=${nullifierHex.slice(0, 16)}...`;
2844
+ throw new Error(errorMessage);
2845
+ }
2846
+ proofHex = proofResult.proof;
2847
+ finalPublicInputs = proofInputs.publicInputs;
2848
+ }
2849
+ const signature = await this.relay.submitSwap(
2850
+ {
2851
+ proof: proofHex,
2852
+ publicInputs: finalPublicInputs,
2853
+ outputs: [
2854
+ {
2855
+ recipient: recipient.toBase58(),
2856
+ amount: withdrawAmountLamports
2857
+ }
2858
+ ],
2859
+ feeBps,
2860
+ swap: {
2861
+ output_mint: options.outputMint,
2862
+ slippage_bps: options.slippageBps || 100,
2863
+ min_output_amount: minOutputAmount
2864
+ }
2865
+ },
2866
+ options.onProgress
2867
+ );
2868
+ return {
2869
+ signature,
2870
+ outputs: [
2871
+ {
2872
+ recipient: recipient.toBase58(),
2873
+ amount: withdrawAmountLamports
2874
+ }
2875
+ ],
2876
+ nullifier: nullifierHex,
2877
+ root: merkleRoot,
2878
+ outputMint: options.outputMint,
2879
+ minOutputAmount
2880
+ };
2881
+ } catch (error) {
2882
+ throw this.wrapError(error, "Swap failed");
2883
+ }
2884
+ }
2885
+ /**
2886
+ * Generate a new note without depositing
2887
+ *
2888
+ * @param amountLamports - Amount for the note
2889
+ * @param useWalletKeys - Whether to use wallet keys (v2.0 recommended)
2890
+ * @returns New note (not yet deposited)
2891
+ */
2892
+ async generateNote(amountLamports, useWalletKeys = false) {
2893
+ if (useWalletKeys && this.cloakKeys) {
2894
+ return await generateNoteFromWallet(amountLamports, this.cloakKeys, this.config.network);
2895
+ } else if (useWalletKeys) {
2896
+ const keys = generateCloakKeys();
2897
+ this.cloakKeys = keys;
2898
+ const result = this.storage.saveKeys(keys);
2899
+ if (result instanceof Promise) {
2900
+ result.catch(() => {
2901
+ });
2902
+ }
2903
+ return await generateNoteFromWallet(amountLamports, keys, this.config.network);
2904
+ }
2905
+ return await generateNote(amountLamports, this.config.network);
2906
+ }
2907
+ /**
2908
+ * Parse a note from JSON string
2909
+ *
2910
+ * @param jsonString - JSON representation
2911
+ * @returns Parsed note
2912
+ */
2913
+ parseNote(jsonString) {
2914
+ return parseNote2(jsonString);
2915
+ }
2916
+ /**
2917
+ * Export a note to JSON string
2918
+ *
2919
+ * @param note - Note to export
2920
+ * @param pretty - Format with indentation
2921
+ * @returns JSON string
2922
+ */
2923
+ exportNote(note, pretty = false) {
2924
+ return pretty ? JSON.stringify(note, null, 2) : JSON.stringify(note);
2925
+ }
2926
+ /**
2927
+ * Check if a note is ready for withdrawal
2928
+ *
2929
+ * @param note - Note to check
2930
+ * @returns True if withdrawable
2931
+ */
2932
+ isWithdrawable(note) {
2933
+ return isWithdrawable(note);
2934
+ }
2935
+ /**
2936
+ * Get Merkle proof for a leaf index
2937
+ *
2938
+ * @param leafIndex - Leaf index in tree
2939
+ * @returns Merkle proof
2940
+ */
2941
+ async getMerkleProof(leafIndex) {
2942
+ return this.indexer.getMerkleProof(leafIndex);
2943
+ }
2944
+ /**
2945
+ * Get current Merkle root
2946
+ *
2947
+ * @returns Current root hash
2948
+ */
2949
+ async getCurrentRoot() {
2950
+ const response = await this.indexer.getMerkleRoot();
2951
+ return response.root;
2952
+ }
2953
+ /**
2954
+ * Get transaction status from relay service
2955
+ *
2956
+ * @param requestId - Request ID from previous submission
2957
+ * @returns Current status
2958
+ */
2959
+ async getTransactionStatus(requestId) {
2960
+ return this.relay.getStatus(requestId);
2961
+ }
2962
+ /**
2963
+ * Recover a deposit that completed on-chain but failed to register
2964
+ *
2965
+ * Use this when a deposit transaction succeeded but the browser crashed
2966
+ * or lost connection before the indexer registration completed.
2967
+ *
2968
+ * @param signature - Transaction signature
2969
+ * @param commitment - Note commitment hash
2970
+ * @param note - Optional: The full note if available
2971
+ * @returns Recovery result with updated note
2972
+ *
2973
+ * @example
2974
+ * ```typescript
2975
+ * const result = await sdk.recoverDeposit({
2976
+ * signature: "5Kn4...",
2977
+ * commitment: "abc123...",
2978
+ * note: myNote // optional if you have it
2979
+ * });
2980
+ *
2981
+ * if (result.success) {
2982
+ * console.log(`Recovered! Leaf index: ${result.leafIndex}`);
2983
+ * }
2984
+ * ```
2985
+ */
2986
+ async recoverDeposit(options) {
2987
+ return this.depositRecovery.recoverDeposit(options);
2988
+ }
2989
+ /**
2990
+ * Load all notes from storage
2991
+ *
2992
+ * @returns Array of saved notes
2993
+ */
2994
+ async loadNotes() {
2995
+ const notes = this.storage.loadAllNotes();
2996
+ return Array.isArray(notes) ? notes : await notes;
2997
+ }
2998
+ /**
2999
+ * Save a note to storage
3000
+ *
3001
+ * @param note - Note to save
3002
+ */
3003
+ async saveNote(note) {
3004
+ const result = this.storage.saveNote(note);
3005
+ if (result instanceof Promise) {
3006
+ await result;
3007
+ }
3008
+ }
3009
+ /**
3010
+ * Find a note by its commitment
3011
+ *
3012
+ * @param commitment - Commitment hash
3013
+ * @returns Note if found
3014
+ */
3015
+ async findNote(commitment) {
3016
+ const notes = await this.loadNotes();
3017
+ return findNoteByCommitment(notes, commitment);
3018
+ }
3019
+ /**
3020
+ * Import wallet keys from JSON
3021
+ *
3022
+ * @param keysJson - JSON string containing keys
3023
+ */
3024
+ async importWalletKeys(keysJson) {
3025
+ const keys = importWalletKeys(keysJson);
3026
+ this.cloakKeys = keys;
3027
+ const result = this.storage.saveKeys(keys);
3028
+ if (result instanceof Promise) {
3029
+ await result;
3030
+ }
3031
+ }
3032
+ /**
3033
+ * Export wallet keys to JSON
3034
+ *
3035
+ * WARNING: This exports secret keys! Store securely.
3036
+ *
3037
+ * @returns JSON string with keys
3038
+ */
3039
+ exportWalletKeys() {
3040
+ if (!this.cloakKeys) {
3041
+ throw new CloakError("No wallet keys available", "wallet", false);
3042
+ }
3043
+ return exportWalletKeys(this.cloakKeys);
3044
+ }
3045
+ /**
3046
+ * Get the configuration
3047
+ */
3048
+ getConfig() {
3049
+ return { ...this.config };
3050
+ }
3051
+ /**
3052
+ * Scan blockchain for notes belonging to this wallet (v2.0 feature)
3053
+ *
3054
+ * Requires Cloak keys to be configured in the SDK.
3055
+ * Fetches encrypted outputs from the indexer and decrypts notes
3056
+ * that belong to this wallet.
3057
+ *
3058
+ * @param options - Scanning options
3059
+ * @returns Array of discovered notes with metadata
3060
+ *
3061
+ * @example
3062
+ * ```typescript
3063
+ * const notes = await sdk.scanNotes({
3064
+ * onProgress: (current, total) => {
3065
+ * console.log(`Scanning: ${current}/${total}`);
3066
+ * }
3067
+ * });
3068
+ *
3069
+ * console.log(`Found ${notes.length} notes!`);
3070
+ * const totalBalance = notes.reduce((sum, n) => sum + n.amount, 0);
3071
+ * ```
3072
+ */
3073
+ async scanNotes(options) {
3074
+ if (!this.cloakKeys) {
3075
+ throw new CloakError(
3076
+ "Note scanning requires Cloak keys. Initialize SDK with: cloakKeys: generateCloakKeys()",
3077
+ "validation",
3078
+ false
3079
+ );
3080
+ }
3081
+ const startIndex = options?.startIndex ?? 0;
3082
+ const batchSize = options?.batchSize ?? 100;
3083
+ const { next_index: totalNotes } = await this.indexer.getMerkleRoot();
3084
+ const endIndex = options?.endIndex ?? (totalNotes > 0 ? totalNotes - 1 : 0);
3085
+ if (totalNotes === 0 || endIndex < startIndex) {
3086
+ return [];
3087
+ }
3088
+ const allEncryptedOutputs = [];
3089
+ for (let start = startIndex; start <= endIndex; start += batchSize) {
3090
+ const end = Math.min(start + batchSize - 1, endIndex);
3091
+ options?.onProgress?.(start, totalNotes);
3092
+ const { notes } = await this.indexer.getNotesRange(start, end, batchSize);
3093
+ allEncryptedOutputs.push(...notes);
3094
+ }
3095
+ options?.onProgress?.(totalNotes, totalNotes);
3096
+ const foundNoteData = scanNotesForWallet(
3097
+ allEncryptedOutputs,
3098
+ this.cloakKeys.view
3099
+ );
3100
+ const scannedNotes = foundNoteData.map((noteData) => ({
3101
+ version: "2.0",
3102
+ amount: noteData.amount,
3103
+ commitment: noteData.commitment,
3104
+ sk_spend: noteData.sk_spend,
3105
+ r: noteData.r,
3106
+ timestamp: Date.now(),
3107
+ network: this.config.network || "devnet",
3108
+ scannedAt: Date.now()
3109
+ }));
3110
+ return scannedNotes;
3111
+ }
3112
+ /**
3113
+ * Wrap errors with better categorization and user-friendly messages
3114
+ *
3115
+ * @private
3116
+ */
3117
+ wrapError(error, context) {
3118
+ if (error instanceof CloakError) {
3119
+ return error;
3120
+ }
3121
+ const errorMessage = error instanceof Error ? error.message : String(error);
3122
+ if (errorMessage.includes("duplicate key") || errorMessage.includes("already deposited")) {
3123
+ return new CloakError(
3124
+ "This note was already deposited. The transaction succeeded but the indexer has it recorded. Generate a new note or scan for existing notes.",
3125
+ "indexer",
3126
+ false,
3127
+ error instanceof Error ? error : void 0
3128
+ );
3129
+ }
3130
+ if (errorMessage.includes("insufficient funds") || errorMessage.includes("insufficient lamports")) {
3131
+ return new CloakError(
3132
+ "Insufficient funds for this transaction. Please check your wallet balance.",
3133
+ "wallet",
3134
+ false,
3135
+ error instanceof Error ? error : void 0
3136
+ );
3137
+ }
3138
+ if (errorMessage.includes("Merkle tree") && errorMessage.includes("inconsistent")) {
3139
+ return new CloakError(
3140
+ "Indexer is temporarily unavailable. Please try again in a moment.",
3141
+ "indexer",
3142
+ true,
3143
+ error instanceof Error ? error : void 0
3144
+ );
3145
+ }
3146
+ if (errorMessage.includes("timeout") || errorMessage.includes("timed out")) {
3147
+ return new CloakError(
3148
+ "Network timeout. Please check your connection and try again.",
3149
+ "network",
3150
+ true,
3151
+ error instanceof Error ? error : void 0
3152
+ );
3153
+ }
3154
+ if (errorMessage.includes("not connected") || errorMessage.includes("wallet")) {
3155
+ return new CloakError(
3156
+ "Wallet not connected. Please connect your wallet first.",
3157
+ "wallet",
3158
+ false,
3159
+ error instanceof Error ? error : void 0
3160
+ );
3161
+ }
3162
+ if (errorMessage.includes("proof") && (errorMessage.includes("failed") || errorMessage.includes("error"))) {
3163
+ return new CloakError(
3164
+ "Zero-knowledge proof generation failed. This is usually temporary - please try again.",
3165
+ "prover",
3166
+ true,
3167
+ error instanceof Error ? error : void 0
3168
+ );
3169
+ }
3170
+ if (errorMessage.includes("relay") || errorMessage.includes("withdraw")) {
3171
+ return new CloakError(
3172
+ "Relay service error. Please try again later.",
3173
+ "relay",
3174
+ true,
3175
+ error instanceof Error ? error : void 0
3176
+ );
3177
+ }
3178
+ return new CloakError(
3179
+ `${context}: ${errorMessage}`,
3180
+ "network",
3181
+ false,
3182
+ error instanceof Error ? error : void 0
3183
+ );
3184
+ }
3185
+ };
3186
+
3187
+ // src/core/note.ts
3188
+ function serializeNote(note, pretty = false) {
3189
+ validateNote(note);
3190
+ return pretty ? JSON.stringify(note, null, 2) : JSON.stringify(note);
3191
+ }
3192
+ function downloadNote(note, filename) {
3193
+ const g = globalThis;
3194
+ const doc = g?.document;
3195
+ const URL_ = g?.URL;
3196
+ const Blob_ = g?.Blob;
3197
+ if (!doc || !URL_ || !Blob_) {
3198
+ throw new Error("downloadNote is only available in browser environments");
3199
+ }
3200
+ const json = serializeNote(note, true);
3201
+ const blob = new Blob_([json], { type: "application/json" });
3202
+ const url = URL_.createObjectURL(blob);
3203
+ const defaultFilename = `cloak-note-${note.commitment.slice(0, 8)}.json`;
3204
+ const link = doc.createElement("a");
3205
+ link.href = url;
3206
+ link.download = filename || defaultFilename;
3207
+ link.click();
3208
+ URL_.revokeObjectURL(url);
3209
+ }
3210
+ async function copyNoteToClipboard(note) {
3211
+ const g = globalThis;
3212
+ const nav = g?.navigator;
3213
+ if (!nav || !nav.clipboard) {
3214
+ throw new Error("Clipboard API not available");
3215
+ }
3216
+ const json = serializeNote(note, true);
3217
+ await nav.clipboard.writeText(json);
3218
+ }
3219
+
3220
+ // src/utils/errors.ts
3221
+ var PROGRAM_ERRORS = {
3222
+ // Nullifier errors
3223
+ "NullifierAlreadyUsed": "This note has already been withdrawn. Each note can only be spent once.",
3224
+ "0x1770": "This note has already been withdrawn.",
3225
+ // Proof verification errors
3226
+ "ProofVerificationFailed": "Zero-knowledge proof verification failed. Please try again.",
3227
+ "0x1771": "Invalid proof. Please regenerate and try again.",
3228
+ "InvalidProof": "The provided proof is invalid.",
3229
+ // Root errors
3230
+ "RootNotFound": "The Merkle root is outdated or invalid. Please refresh and try again.",
3231
+ "InvalidRoot": "Invalid Merkle root. The tree may have been updated.",
3232
+ "0x1772": "Merkle root not found in history.",
3233
+ // Amount errors
3234
+ "InvalidAmount": "Invalid amount. Please check your input.",
3235
+ "InsufficientFunds": "Insufficient funds for this transaction.",
3236
+ "AmountMismatch": "Output amounts don't match the note amount.",
3237
+ // Fee errors
3238
+ "InvalidFee": "Invalid fee calculation. Please try again.",
3239
+ "FeeTooHigh": "Fee exceeds maximum allowed.",
3240
+ // Output errors
3241
+ "InvalidOutputs": "Invalid recipient configuration.",
3242
+ "TooManyOutputs": "Maximum 5 recipients allowed per transaction.",
3243
+ "OutputHashMismatch": "Output hash doesn't match proof.",
3244
+ // General errors
3245
+ "Unauthorized": "You don't have permission to perform this action.",
3246
+ "AccountNotFound": "Required account not found.",
3247
+ "InvalidInstruction": "Invalid instruction data."
3248
+ };
3249
+ function parseTransactionError(error) {
3250
+ if (!error) return "An unknown error occurred";
3251
+ const errorStr = typeof error === "string" ? error : error.message || error.toString();
3252
+ const hexMatch = errorStr.match(/0x[0-9a-f]{4}/i);
3253
+ if (hexMatch) {
3254
+ const errorCode = hexMatch[0];
3255
+ if (PROGRAM_ERRORS[errorCode]) {
3256
+ return PROGRAM_ERRORS[errorCode];
3257
+ }
3258
+ }
3259
+ for (const [key, message] of Object.entries(PROGRAM_ERRORS)) {
3260
+ if (errorStr.includes(key)) {
3261
+ return message;
3262
+ }
3263
+ }
3264
+ if (errorStr.includes("insufficient funds") || errorStr.includes("insufficient lamports")) {
3265
+ return "Insufficient SOL balance. Please add funds to your wallet.";
3266
+ }
3267
+ if (errorStr.includes("blockhash not found")) {
3268
+ return "Transaction expired. Please try again.";
3269
+ }
3270
+ if (errorStr.includes("already been processed")) {
3271
+ return "This transaction has already been processed.";
3272
+ }
3273
+ if (errorStr.includes("signature verification failed")) {
3274
+ return "Transaction signature verification failed. Please try again.";
3275
+ }
3276
+ if (errorStr.includes("account does not exist")) {
3277
+ return "Required account not found. The program may need to be initialized.";
3278
+ }
3279
+ if (errorStr.includes("fetch") || errorStr.includes("network")) {
3280
+ return "Network error. Please check your connection and try again.";
3281
+ }
3282
+ if (errorStr.includes("timeout")) {
3283
+ return "Request timed out. Please try again.";
3284
+ }
3285
+ if (errorStr.includes("relay") || errorStr.includes("withdraw")) {
3286
+ if (errorStr.includes("in progress")) {
3287
+ return "A withdrawal is already in progress. Please wait for it to complete.";
3288
+ }
3289
+ if (errorStr.includes("rate limit")) {
3290
+ return "Too many requests. Please wait a moment and try again.";
3291
+ }
3292
+ return "Relay service error. Please try again later.";
3293
+ }
3294
+ if (errorStr.includes("proof") && errorStr.includes("generation")) {
3295
+ return "Failed to generate zero-knowledge proof. Please try again.";
3296
+ }
3297
+ if (errorStr.includes("indexer") || errorStr.includes("merkle")) {
3298
+ if (errorStr.includes("inconsistent")) {
3299
+ return "The indexer is temporarily unavailable. Please try again in a moment.";
3300
+ }
3301
+ if (errorStr.includes("not found")) {
3302
+ return "Note not found in the indexer. It may not be confirmed yet.";
3303
+ }
3304
+ return "Indexer service error. Please try again later.";
3305
+ }
3306
+ let cleanError = errorStr.replace(/Error:\s*/gi, "").replace(/\s+at\s+.*$/g, "").replace(/\[.*?\]/g, "").trim();
3307
+ if (cleanError.length > 200) {
3308
+ cleanError = cleanError.substring(0, 197) + "...";
3309
+ }
3310
+ return cleanError || "Transaction failed. Please try again.";
3311
+ }
3312
+ function createCloakError(error, _context) {
3313
+ if (error instanceof CloakError) {
3314
+ return error;
3315
+ }
3316
+ const errorMessage = error instanceof Error ? error.message : String(error);
3317
+ const userMessage = parseTransactionError(error);
3318
+ let category = "network";
3319
+ let retryable = false;
3320
+ if (errorMessage.includes("insufficient") || errorMessage.includes("balance")) {
3321
+ category = "wallet";
3322
+ retryable = false;
3323
+ } else if (errorMessage.includes("proof")) {
3324
+ category = "prover";
3325
+ retryable = true;
3326
+ } else if (errorMessage.includes("indexer") || errorMessage.includes("merkle")) {
3327
+ category = "indexer";
3328
+ retryable = errorMessage.includes("inconsistent") || errorMessage.includes("temporary");
3329
+ } else if (errorMessage.includes("relay")) {
3330
+ category = "relay";
3331
+ retryable = true;
3332
+ } else if (errorMessage.includes("timeout") || errorMessage.includes("network")) {
3333
+ category = "network";
3334
+ retryable = true;
3335
+ } else if (errorMessage.includes("validation") || errorMessage.includes("invalid")) {
3336
+ category = "validation";
3337
+ retryable = false;
3338
+ }
3339
+ return new CloakError(
3340
+ userMessage,
3341
+ category,
3342
+ retryable,
3343
+ error instanceof Error ? error : void 0
3344
+ );
3345
+ }
3346
+ function formatErrorForLogging(error) {
3347
+ if (error instanceof CloakError) {
3348
+ return `[${error.category}] ${error.message}${error.retryable ? " (retryable)" : ""}`;
3349
+ }
3350
+ if (error instanceof Error) {
3351
+ return `${error.name}: ${error.message}`;
3352
+ }
3353
+ return String(error);
3354
+ }
3355
+
3356
+ // src/services/ProverService.ts
3357
+ var ProverService = class {
3358
+ /**
3359
+ * Create a new Prover Service client
3360
+ *
3361
+ * @param indexerUrl - Indexer/Prover service base URL
3362
+ * @param timeout - Proof generation timeout in ms (default: 5 minutes)
3363
+ */
3364
+ constructor(indexerUrl, timeout = 5 * 60 * 1e3) {
3365
+ this.indexerUrl = indexerUrl.replace(/\/$/, "");
3366
+ this.timeout = timeout;
3367
+ }
3368
+ /**
3369
+ * Generate a zero-knowledge proof for withdrawal
3370
+ *
3371
+ * This process typically takes 30-180 seconds depending on the backend.
3372
+ *
3373
+ * @param inputs - Circuit inputs (private + public + outputs)
3374
+ * @param options - Optional progress tracking and callbacks
3375
+ * @returns Proof result with hex-encoded proof and public inputs
3376
+ *
3377
+ * @example
3378
+ * ```typescript
3379
+ * const result = await prover.generateProof(inputs);
3380
+ * if (result.success) {
3381
+ * console.log(`Proof: ${result.proof}`);
3382
+ * }
3383
+ * ```
3384
+ *
3385
+ * @example
3386
+ * ```typescript
3387
+ * // With progress tracking
3388
+ * const result = await prover.generateProof(inputs, {
3389
+ * onProgress: (progress) => console.log(`Progress: ${progress}%`),
3390
+ * onStart: () => console.log("Starting proof generation..."),
3391
+ * onSuccess: (result) => console.log("Proof generated!"),
3392
+ * onError: (error) => console.error("Failed:", error)
3393
+ * });
3394
+ * ```
3395
+ */
3396
+ async generateProof(inputs, options) {
3397
+ const startTime = Date.now();
3398
+ const actualTimeout = options?.timeout || this.timeout;
3399
+ options?.onStart?.();
3400
+ let progressInterval;
3401
+ try {
3402
+ const requestBody = {
3403
+ private_inputs: JSON.stringify(inputs.privateInputs),
3404
+ public_inputs: JSON.stringify(inputs.publicInputs),
3405
+ outputs: JSON.stringify(inputs.outputs)
3406
+ };
3407
+ if (inputs.swapParams) {
3408
+ requestBody.swap_params = inputs.swapParams;
3409
+ }
3410
+ const controller = new AbortController();
3411
+ const timeoutId = setTimeout(() => controller.abort(), actualTimeout);
3412
+ if (options?.onProgress) {
3413
+ let progress = 0;
3414
+ progressInterval = setInterval(() => {
3415
+ progress = Math.min(90, progress + Math.random() * 10);
3416
+ options.onProgress(Math.floor(progress));
3417
+ }, 2e3);
3418
+ }
3419
+ const response = await fetch(`${this.indexerUrl}/api/v1/prove`, {
3420
+ method: "POST",
3421
+ headers: {
3422
+ "Content-Type": "application/json"
3423
+ },
3424
+ body: JSON.stringify(requestBody),
3425
+ signal: controller.signal
3426
+ });
3427
+ clearTimeout(timeoutId);
3428
+ if (progressInterval) clearInterval(progressInterval);
3429
+ if (!response.ok) {
3430
+ let errorMessage = `${response.status} ${response.statusText}`;
3431
+ try {
3432
+ const errorText = await response.text();
3433
+ try {
3434
+ const errorJson = JSON.parse(errorText);
3435
+ errorMessage = errorJson.error || errorJson.message || errorText;
3436
+ } catch {
3437
+ errorMessage = errorText || errorMessage;
3438
+ }
3439
+ } catch {
3440
+ }
3441
+ options?.onError?.(errorMessage);
3442
+ return {
3443
+ success: false,
3444
+ generationTimeMs: Date.now() - startTime,
3445
+ error: errorMessage
3446
+ };
3447
+ }
3448
+ options?.onProgress?.(100);
3449
+ const rawData = await response.json();
3450
+ const result = {
3451
+ success: rawData.success,
3452
+ proof: rawData.proof,
3453
+ publicInputs: rawData.public_inputs,
3454
+ // Map snake_case
3455
+ generationTimeMs: rawData.generation_time_ms || Date.now() - startTime,
3456
+ error: rawData.error
3457
+ };
3458
+ if (!result.success && rawData.execution_report) {
3459
+ }
3460
+ if (!result.success && result.error) {
3461
+ try {
3462
+ const errorObj = typeof result.error === "string" ? JSON.parse(result.error) : result.error;
3463
+ if (errorObj?.error && typeof errorObj.error === "string") {
3464
+ result.error = errorObj.error;
3465
+ } else if (typeof errorObj === "string") {
3466
+ result.error = errorObj;
3467
+ }
3468
+ if (errorObj?.execution_report && typeof errorObj.execution_report === "string") {
3469
+ result.error += `
3470
+ Execution report: ${errorObj.execution_report}`;
3471
+ }
3472
+ if (errorObj?.total_cycles !== void 0) {
3473
+ result.error += `
3474
+ Total cycles: ${errorObj.total_cycles}`;
3475
+ }
3476
+ if (errorObj?.total_syscalls !== void 0) {
3477
+ result.error += `
3478
+ Total syscalls: ${errorObj.total_syscalls}`;
3479
+ }
3480
+ } catch {
3481
+ }
3482
+ }
3483
+ if (result.success) {
3484
+ options?.onSuccess?.(result);
3485
+ } else if (result.error) {
3486
+ options?.onError?.(result.error);
3487
+ }
3488
+ return result;
3489
+ } catch (error) {
3490
+ const totalTime = Date.now() - startTime;
3491
+ if (progressInterval) clearInterval(progressInterval);
3492
+ let errorMessage;
3493
+ if (error instanceof Error && error.name === "AbortError") {
3494
+ errorMessage = `Proof generation timed out after ${actualTimeout}ms`;
3495
+ } else {
3496
+ errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
3497
+ }
3498
+ options?.onError?.(errorMessage);
3499
+ return {
3500
+ success: false,
3501
+ generationTimeMs: totalTime,
3502
+ error: errorMessage
3503
+ };
3504
+ }
3505
+ }
3506
+ /**
3507
+ * Check if the prover service is available
3508
+ *
3509
+ * @returns True if service is healthy
3510
+ */
3511
+ async healthCheck() {
3512
+ try {
3513
+ const response = await fetch(`${this.indexerUrl}/health`, {
3514
+ method: "GET"
3515
+ });
3516
+ return response.ok;
3517
+ } catch {
3518
+ return false;
3519
+ }
3520
+ }
3521
+ /**
3522
+ * Get the configured timeout
3523
+ */
3524
+ getTimeout() {
3525
+ return this.timeout;
3526
+ }
3527
+ /**
3528
+ * Set a new timeout
3529
+ */
3530
+ setTimeout(timeout) {
3531
+ if (timeout <= 0) {
3532
+ throw new Error("Timeout must be positive");
3533
+ }
3534
+ this.timeout = timeout;
3535
+ }
3536
+ };
3537
+
3538
+ // src/helpers/wallet-integration.ts
3539
+ var import_web37 = require("@solana/web3.js");
3540
+ function validateWalletConnected(wallet) {
3541
+ if (wallet instanceof import_web37.Keypair) {
3542
+ return;
3543
+ }
3544
+ if (!wallet.publicKey) {
3545
+ throw new CloakError(
3546
+ "Wallet not connected. Please connect your wallet first.",
3547
+ "wallet",
3548
+ false
3549
+ );
3550
+ }
3551
+ }
3552
+ function getPublicKey(wallet) {
3553
+ if (wallet instanceof import_web37.Keypair) {
3554
+ return wallet.publicKey;
3555
+ }
3556
+ if (!wallet.publicKey) {
3557
+ throw new CloakError(
3558
+ "Wallet not connected",
3559
+ "wallet",
3560
+ false
3561
+ );
3562
+ }
3563
+ return wallet.publicKey;
3564
+ }
3565
+ async function sendTransaction(transaction, wallet, connection, options) {
3566
+ if (wallet instanceof import_web37.Keypair) {
3567
+ return await connection.sendTransaction(transaction, [wallet], options);
3568
+ }
3569
+ if (wallet.sendTransaction) {
3570
+ return await wallet.sendTransaction(transaction, connection, options);
3571
+ } else if (wallet.signTransaction) {
3572
+ const signed = await wallet.signTransaction(transaction);
3573
+ return await connection.sendRawTransaction(signed.serialize(), options);
3574
+ } else {
3575
+ throw new CloakError(
3576
+ "Wallet does not support transaction signing",
3577
+ "wallet",
3578
+ false
3579
+ );
3580
+ }
3581
+ }
3582
+ async function signTransaction(transaction, wallet) {
3583
+ if (wallet instanceof import_web37.Keypair) {
3584
+ transaction.sign(wallet);
3585
+ return transaction;
3586
+ }
3587
+ if (!wallet.signTransaction) {
3588
+ throw new CloakError(
3589
+ "Wallet does not support transaction signing",
3590
+ "wallet",
3591
+ false
3592
+ );
3593
+ }
3594
+ return await wallet.signTransaction(transaction);
3595
+ }
3596
+ function keypairToAdapter(keypair) {
3597
+ return {
3598
+ publicKey: keypair.publicKey,
3599
+ signTransaction: async (tx) => {
3600
+ tx.sign(keypair);
3601
+ return tx;
3602
+ },
3603
+ signAllTransactions: async (txs) => {
3604
+ txs.forEach((tx) => tx.sign(keypair));
3605
+ return txs;
3606
+ }
3607
+ };
3608
+ }
3609
+
3610
+ // src/index.ts
3611
+ var VERSION = "1.0.0";
3612
+ // Annotate the CommonJS export names for ESM import in node:
3613
+ 0 && (module.exports = {
3614
+ ArtifactProverService,
3615
+ CLOAK_PROGRAM_ID,
3616
+ CloakError,
3617
+ CloakSDK,
3618
+ DepositRecoveryService,
3619
+ FIXED_FEE_LAMPORTS,
3620
+ IndexerService,
3621
+ LAMPORTS_PER_SOL,
3622
+ LocalStorageAdapter,
3623
+ MemoryStorageAdapter,
3624
+ ProverService,
3625
+ RelayService,
3626
+ VARIABLE_FEE_RATE,
3627
+ VERSION,
3628
+ bigintToBytes32,
3629
+ buildPublicInputsBytes,
3630
+ bytesToHex,
3631
+ calculateFee,
3632
+ calculateRelayFee,
3633
+ computeCommitment,
3634
+ computeMerkleRoot,
3635
+ computeNullifier,
3636
+ computeNullifierAsync,
3637
+ computeNullifierSync,
3638
+ computeOutputsHash,
3639
+ computeOutputsHashAsync,
3640
+ computeOutputsHashSync,
3641
+ computeSwapOutputsHash,
3642
+ computeSwapOutputsHashAsync,
3643
+ computeSwapOutputsHashSync,
3644
+ copyNoteToClipboard,
3645
+ createCloakError,
3646
+ createDepositInstruction,
3647
+ deriveSpendKey,
3648
+ deriveViewKey,
3649
+ detectNetworkFromRpcUrl,
3650
+ downloadNote,
3651
+ encodeNoteSimple,
3652
+ encryptNoteForRecipient,
3653
+ exportKeys,
3654
+ exportNote,
3655
+ exportWalletKeys,
3656
+ filterNotesByNetwork,
3657
+ filterWithdrawableNotes,
3658
+ findNoteByCommitment,
3659
+ formatAmount,
3660
+ formatErrorForLogging,
3661
+ generateCloakKeys,
3662
+ generateCommitment,
3663
+ generateCommitmentAsync,
3664
+ generateMasterSeed,
3665
+ generateNote,
3666
+ generateNoteFromWallet,
3667
+ getAddressExplorerUrl,
3668
+ getDistributableAmount,
3669
+ getExplorerUrl,
3670
+ getPublicKey,
3671
+ getPublicViewKey,
3672
+ getRecipientAmount,
3673
+ getRpcUrlForNetwork,
3674
+ getShieldPoolPDAs,
3675
+ getViewKey,
3676
+ hexToBigint,
3677
+ hexToBytes,
3678
+ importKeys,
3679
+ importWalletKeys,
3680
+ isValidHex,
3681
+ isValidRpcUrl,
3682
+ isValidSolanaAddress,
3683
+ isWithdrawable,
3684
+ keypairToAdapter,
3685
+ parseAmount,
3686
+ parseNote,
3687
+ parseTransactionError,
3688
+ poseidonHash,
3689
+ prepareEncryptedOutput,
3690
+ prepareEncryptedOutputForRecipient,
3691
+ proofToBytes,
3692
+ pubkeyToLimbs,
3693
+ randomBytes,
3694
+ scanNotesForWallet,
3695
+ sendTransaction,
3696
+ serializeNote,
3697
+ signTransaction,
3698
+ splitTo2Limbs,
3699
+ tryDecryptNote,
3700
+ updateNoteWithDeposit,
3701
+ validateDepositParams,
3702
+ validateNote,
3703
+ validateOutputsSum,
3704
+ validateTransfers,
3705
+ validateWalletConnected,
3706
+ validateWithdrawableNote
3707
+ });