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