@cloak.dev/sdk 0.1.6 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -36,8 +36,8 @@ __export(utxo_exports, {
36
36
  NATIVE_SOL_MINT: () => NATIVE_SOL_MINT,
37
37
  bigintToBytes32: () => bigintToBytes322,
38
38
  bigintToHex: () => bigintToHex,
39
- computeCommitment: () => computeCommitment,
40
- computeNullifier: () => computeNullifier,
39
+ computeCommitment: () => computeCommitment2,
40
+ computeNullifier: () => computeNullifier2,
41
41
  computeSignature: () => computeSignature,
42
42
  createUtxo: () => createUtxo,
43
43
  createZeroUtxo: () => createZeroUtxo,
@@ -112,7 +112,7 @@ async function createUtxo(amount, keypair, mintAddress = NATIVE_SOL_MINT) {
112
112
  blinding,
113
113
  mintAddress
114
114
  };
115
- utxo.commitment = await computeCommitment(utxo);
115
+ utxo.commitment = await computeCommitment2(utxo);
116
116
  return utxo;
117
117
  }
118
118
  async function createZeroUtxo(mintAddress = NATIVE_SOL_MINT, salt) {
@@ -127,7 +127,7 @@ async function createZeroUtxo(mintAddress = NATIVE_SOL_MINT, salt) {
127
127
  blinding: BigInt(0),
128
128
  mintAddress
129
129
  };
130
- utxo.commitment = await computeCommitment(utxo);
130
+ utxo.commitment = await computeCommitment2(utxo);
131
131
  utxo.index = 0;
132
132
  return utxo;
133
133
  }
@@ -139,7 +139,7 @@ function pubkeyToFieldElement(pubkey) {
139
139
  }
140
140
  return value % FIELD_MODULUS;
141
141
  }
142
- async function computeCommitment(utxo) {
142
+ async function computeCommitment2(utxo) {
143
143
  const poseidon3 = await getPoseidon2();
144
144
  const mintField = pubkeyToFieldElement(utxo.mintAddress);
145
145
  const hash = poseidon3([
@@ -155,12 +155,12 @@ async function computeSignature(privateKey, commitment, pathIndex) {
155
155
  const hash = poseidon3([privateKey, commitment, pathIndex]);
156
156
  return poseidon3.F.toObject(hash);
157
157
  }
158
- async function computeNullifier(utxo) {
158
+ async function computeNullifier2(utxo) {
159
159
  if (utxo.index === void 0) {
160
160
  throw new Error("UTXO must have an index to compute nullifier");
161
161
  }
162
162
  const poseidon3 = await getPoseidon2();
163
- const commitment = utxo.commitment ?? await computeCommitment(utxo);
163
+ const commitment = utxo.commitment ?? await computeCommitment2(utxo);
164
164
  const pathIndex = BigInt(utxo.index);
165
165
  const signature = await computeSignature(
166
166
  utxo.keypair.privateKey,
@@ -202,7 +202,7 @@ async function deserializeUtxo(bytes) {
202
202
  const blindingBytes = bytes.slice(40, 72);
203
203
  const blinding = bytesToBigint(blindingBytes);
204
204
  const mintBytes = bytes.slice(72, 104);
205
- const mintAddress = new import_web3.PublicKey(mintBytes);
205
+ const mintAddress = new import_web33.PublicKey(mintBytes);
206
206
  const index = view.getUint32(104, true);
207
207
  const utxo = {
208
208
  amount,
@@ -211,7 +211,7 @@ async function deserializeUtxo(bytes) {
211
211
  mintAddress,
212
212
  index: index > 0 ? index : void 0
213
213
  };
214
- utxo.commitment = await computeCommitment(utxo);
214
+ utxo.commitment = await computeCommitment2(utxo);
215
215
  return utxo;
216
216
  }
217
217
  function bigintToBytes(value, length) {
@@ -247,8 +247,8 @@ function bigintToBytes322(value) {
247
247
  return result;
248
248
  }
249
249
  async function utxoEquals(a, b) {
250
- const commitmentA = a.commitment ?? await computeCommitment(a);
251
- const commitmentB = b.commitment ?? await computeCommitment(b);
250
+ const commitmentA = a.commitment ?? await computeCommitment2(a);
251
+ const commitmentB = b.commitment ?? await computeCommitment2(b);
252
252
  return commitmentA === commitmentB;
253
253
  }
254
254
  function sumUtxoAmounts(utxos) {
@@ -270,16 +270,16 @@ function selectUtxos(available, targetAmount) {
270
270
  }
271
271
  return selected;
272
272
  }
273
- var import_web3, import_circomlibjs2, import_blake32, FIELD_MODULUS, UTXO_KEY_DOMAIN, NATIVE_SOL_MINT, poseidonInstance;
273
+ var import_web33, import_circomlibjs2, import_blake32, FIELD_MODULUS, UTXO_KEY_DOMAIN, NATIVE_SOL_MINT, poseidonInstance;
274
274
  var init_utxo = __esm({
275
275
  "src/core/utxo.ts"() {
276
276
  "use strict";
277
- import_web3 = require("@solana/web3.js");
277
+ import_web33 = require("@solana/web3.js");
278
278
  import_circomlibjs2 = require("circomlibjs");
279
279
  import_blake32 = require("@noble/hashes/blake3");
280
280
  FIELD_MODULUS = BigInt("21888242871839275222246405745257275088548364400416034343698204186575808495617");
281
281
  UTXO_KEY_DOMAIN = new TextEncoder().encode("cloak_utxo_priv_v1");
282
- NATIVE_SOL_MINT = new import_web3.PublicKey("So11111111111111111111111111111111111111112");
282
+ NATIVE_SOL_MINT = new import_web33.PublicKey("So11111111111111111111111111111111111111112");
283
283
  poseidonInstance = null;
284
284
  }
285
285
  });
@@ -290,7 +290,9 @@ __export(index_exports, {
290
290
  CLOAK_PROGRAM_ID: () => CLOAK_PROGRAM_ID,
291
291
  CloakError: () => CloakError,
292
292
  CloakSDK: () => CloakSDK,
293
+ DEFAULT_CIRCUITS_URL: () => DEFAULT_CIRCUITS_URL,
293
294
  DEFAULT_TRANSACTION_CIRCUITS_URL: () => DEFAULT_TRANSACTION_CIRCUITS_URL,
295
+ EXPECTED_CIRCUIT_HASHES: () => EXPECTED_CIRCUIT_HASHES,
294
296
  FIXED_FEE_LAMPORTS: () => FIXED_FEE_LAMPORTS,
295
297
  LAMPORTS_PER_SOL: () => LAMPORTS_PER_SOL,
296
298
  LocalStorageAdapter: () => LocalStorageAdapter,
@@ -313,6 +315,7 @@ __export(index_exports, {
313
315
  VARIABLE_FEE_NUMERATOR: () => VARIABLE_FEE_NUMERATOR,
314
316
  VARIABLE_FEE_RATE: () => VARIABLE_FEE_RATE,
315
317
  VERSION: () => VERSION,
318
+ areCircuitsAvailable: () => areCircuitsAvailable,
316
319
  bigintToBytes32: () => bigintToBytes32,
317
320
  bigintToHex: () => bigintToHex,
318
321
  buildMerkleTree: () => buildMerkleTree,
@@ -326,15 +329,30 @@ __export(index_exports, {
326
329
  chainNoteFromBase64: () => chainNoteFromBase64,
327
330
  chainNoteToBase64: () => chainNoteToBase64,
328
331
  classifyRelayError: () => classifyRelayError,
332
+ cleanupStalePendingOperations: () => cleanupStalePendingOperations,
333
+ clearPendingDeposits: () => clearPendingDeposits,
334
+ clearPendingWithdrawals: () => clearPendingWithdrawals,
329
335
  computeChainNoteHash: () => computeChainNoteHash,
336
+ computeCommitment: () => computeCommitment,
330
337
  computeExtDataHash: () => computeExtDataHash,
331
338
  computeMerkleRoot: () => computeMerkleRoot,
339
+ computeNullifier: () => computeNullifier,
340
+ computeNullifierAsync: () => computeNullifierAsync,
341
+ computeNullifierSync: () => computeNullifierSync,
342
+ computeOutputsHash: () => computeOutputsHash,
343
+ computeOutputsHashAsync: () => computeOutputsHashAsync,
344
+ computeOutputsHashSync: () => computeOutputsHashSync,
332
345
  computeProofForLatestDeposit: () => computeProofForLatestDeposit,
333
346
  computeProofFromChain: () => computeProofFromChain,
334
347
  computeSignature: () => computeSignature,
335
- computeUtxoCommitment: () => computeCommitment,
336
- computeUtxoNullifier: () => computeNullifier,
348
+ computeSwapOutputsHash: () => computeSwapOutputsHash,
349
+ computeSwapOutputsHashAsync: () => computeSwapOutputsHashAsync,
350
+ computeSwapOutputsHashSync: () => computeSwapOutputsHashSync,
351
+ computeUtxoCommitment: () => computeCommitment2,
352
+ computeUtxoNullifier: () => computeNullifier2,
353
+ copyNoteToClipboard: () => copyNoteToClipboard,
337
354
  createCloakError: () => createCloakError,
355
+ createDepositInstruction: () => createDepositInstruction,
338
356
  createLogger: () => createLogger,
339
357
  createUtxo: () => createUtxo,
340
358
  createZeroUtxo: () => createZeroUtxo,
@@ -354,52 +372,78 @@ __export(index_exports, {
354
372
  deriveViewingKeyFromUtxoPrivateKey: () => deriveViewingKeyFromUtxoPrivateKey,
355
373
  deserializeUtxo: () => deserializeUtxo,
356
374
  detectNetworkFromRpcUrl: () => detectNetworkFromRpcUrl,
375
+ downloadNote: () => downloadNote,
376
+ encodeNoteSimple: () => encodeNoteSimple,
357
377
  encryptCompactChainNote: () => encryptCompactChainNote,
358
378
  encryptNoteForRecipient: () => encryptNoteForRecipient,
359
379
  encryptTransactionMetadata: () => encryptTransactionMetadata,
360
380
  encryptTransactionMetadataBundle: () => encryptTransactionMetadataBundle,
361
381
  expandSpendKey: () => expandSpendKey,
362
382
  exportKeys: () => exportKeys,
383
+ exportNote: () => exportNote,
384
+ exportWalletKeys: () => exportWalletKeys,
363
385
  fetchCommitments: () => fetchCommitments,
364
386
  fetchRiskQuoteInstruction: () => fetchRiskQuoteInstruction,
365
387
  fetchRiskQuoteIx: () => fetchRiskQuoteIx,
388
+ filterNotesByNetwork: () => filterNotesByNetwork,
389
+ filterWithdrawableNotes: () => filterWithdrawableNotes,
390
+ findNoteByCommitment: () => findNoteByCommitment,
366
391
  formatAmount: () => formatAmount,
367
392
  formatComplianceCsv: () => formatComplianceCsv,
368
393
  formatErrorForLogging: () => formatErrorForLogging,
369
394
  formatSol: () => formatSol,
370
395
  fullWithdraw: () => fullWithdraw,
371
396
  generateCloakKeys: () => generateCloakKeys,
397
+ generateCommitment: () => generateCommitment,
398
+ generateCommitmentAsync: () => generateCommitmentAsync,
372
399
  generateMasterSeed: () => generateMasterSeed,
400
+ generateNote: () => generateNote,
401
+ generateNoteFromWallet: () => generateNoteFromWallet,
373
402
  generateUtxoKeypair: () => generateUtxoKeypair,
374
403
  generateViewingKeyPair: () => generateViewingKeyPair,
404
+ generateWithdrawRegularProof: () => generateWithdrawRegularProof,
405
+ generateWithdrawSwapProof: () => generateWithdrawSwapProof,
375
406
  getAddressExplorerUrl: () => getAddressExplorerUrl,
376
407
  getCircuitsPath: () => getCircuitsPath,
408
+ getDefaultCircuitsPath: () => getDefaultCircuitsPath,
377
409
  getDistributableAmount: () => getDistributableAmount,
378
410
  getExplorerUrl: () => getExplorerUrl,
379
411
  getNkFromUtxoPrivateKey: () => getNkFromUtxoPrivateKey,
380
412
  getNullifierPDA: () => getNullifierPDA,
413
+ getPendingOperationsSummary: () => getPendingOperationsSummary,
381
414
  getPublicKey: () => getPublicKey,
415
+ getPublicViewKey: () => getPublicViewKey,
416
+ getRecipientAmount: () => getRecipientAmount,
382
417
  getRpcUrlForNetwork: () => getRpcUrlForNetwork,
383
418
  getShieldPoolPDAs: () => getShieldPoolPDAs,
384
419
  getSwapStatePDA: () => getSwapStatePDA,
420
+ getViewKey: () => getViewKey,
421
+ hasPendingOperations: () => hasPendingOperations,
385
422
  hexToBigint: () => hexToBigint,
386
423
  hexToBytes: () => hexToBytes,
387
424
  importKeys: () => importKeys,
425
+ importWalletKeys: () => importWalletKeys,
388
426
  isDebugEnabled: () => isDebugEnabled,
389
427
  isRootNotFoundError: () => isRootNotFoundError,
390
428
  isValidHex: () => isValidHex,
391
429
  isValidRpcUrl: () => isValidRpcUrl,
392
430
  isValidSolanaAddress: () => isValidSolanaAddress,
393
431
  isWithdrawAmountSufficient: () => isWithdrawAmountSufficient,
432
+ isWithdrawable: () => isWithdrawable,
394
433
  keypairToAdapter: () => keypairToAdapter,
434
+ loadPendingDeposits: () => loadPendingDeposits,
435
+ loadPendingWithdrawals: () => loadPendingWithdrawals,
395
436
  parseAmount: () => parseAmount,
396
437
  parseError: () => parseError,
438
+ parseNote: () => parseNote,
397
439
  parseRelayErrorResponse: () => parseRelayErrorResponse,
398
440
  parseTransactionError: () => parseTransactionError,
399
441
  partialWithdraw: () => partialWithdraw,
400
442
  poseidonHash: () => poseidonHash,
401
443
  preflightCheck: () => preflightCheck,
402
444
  preflightNullifiers: () => preflightNullifiers,
445
+ prepareEncryptedOutput: () => prepareEncryptedOutput,
446
+ prepareEncryptedOutputForRecipient: () => prepareEncryptedOutputForRecipient,
403
447
  proofToBytes: () => proofToBytes,
404
448
  pubkeyToFieldElement: () => pubkeyToFieldElement,
405
449
  pubkeyToLimbs: () => pubkeyToLimbs,
@@ -407,11 +451,16 @@ __export(index_exports, {
407
451
  randomFieldElement: () => randomFieldElement,
408
452
  readMerkleTreeState: () => readMerkleTreeState,
409
453
  registerViewingKey: () => registerViewingKey,
454
+ removePendingDeposit: () => removePendingDeposit,
455
+ removePendingWithdrawal: () => removePendingWithdrawal,
456
+ savePendingDeposit: () => savePendingDeposit,
457
+ savePendingWithdrawal: () => savePendingWithdrawal,
410
458
  scanNotesForWallet: () => scanNotesForWallet,
411
459
  scanTransactions: () => scanTransactions,
412
460
  sdkLogger: () => sdkLogger,
413
461
  selectUtxos: () => selectUtxos,
414
462
  sendTransaction: () => sendTransaction,
463
+ serializeNote: () => serializeNote,
415
464
  serializeUtxo: () => serializeUtxo,
416
465
  setCircuitsPath: () => setCircuitsPath,
417
466
  setDebugMode: () => setDebugMode,
@@ -425,12 +474,21 @@ __export(index_exports, {
425
474
  transfer: () => transfer,
426
475
  truncate: () => truncate,
427
476
  tryDecryptNote: () => tryDecryptNote,
477
+ updateNoteWithDeposit: () => updateNoteWithDeposit,
478
+ updatePendingDeposit: () => updatePendingDeposit,
479
+ updatePendingWithdrawal: () => updatePendingWithdrawal,
428
480
  utxoBigintToBytes32: () => bigintToBytes322,
429
481
  utxoEquals: () => utxoEquals,
430
482
  utxoHexToBigint: () => hexToBigint2,
483
+ validateDepositParams: () => validateDepositParams,
484
+ validateNote: () => validateNote,
431
485
  validateOutputsSum: () => validateOutputsSum,
432
486
  validateRoot: () => validateRoot,
487
+ validateTransfers: () => validateTransfers,
433
488
  validateWalletConnected: () => validateWalletConnected,
489
+ validateWithdrawableNote: () => validateWithdrawableNote,
490
+ verifyAllCircuits: () => verifyAllCircuits,
491
+ verifyCircuitIntegrity: () => verifyCircuitIntegrity,
434
492
  verifyUtxos: () => verifyUtxos,
435
493
  waitForRoot: () => waitForRoot,
436
494
  withTiming: () => withTiming
@@ -438,7 +496,7 @@ __export(index_exports, {
438
496
  module.exports = __toCommonJS(index_exports);
439
497
 
440
498
  // src/core/CloakSDK.ts
441
- var import_web33 = require("@solana/web3.js");
499
+ var import_web35 = require("@solana/web3.js");
442
500
 
443
501
  // src/core/types.ts
444
502
  var CloakError = class extends Error {
@@ -495,6 +553,79 @@ function hexToBigint(hex) {
495
553
  const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
496
554
  return BigInt("0x" + cleanHex);
497
555
  }
556
+ async function computeCommitment(amount, r, sk_spend) {
557
+ const [sk0, sk1] = splitTo2Limbs(sk_spend);
558
+ const [r0, r1] = splitTo2Limbs(r);
559
+ const pk_spend = await poseidonHash([sk0, sk1]);
560
+ return await poseidonHash([amount, r0, r1, pk_spend]);
561
+ }
562
+ async function generateCommitmentAsync(amountLamports, r, skSpend) {
563
+ const amount = BigInt(amountLamports);
564
+ const rValue = hexToBigint(bytesToHex(r));
565
+ const skValue = hexToBigint(bytesToHex(skSpend));
566
+ return await computeCommitment(amount, rValue, skValue);
567
+ }
568
+ function generateCommitment(_amountLamports, _r, _skSpend) {
569
+ throw new Error("generateCommitment is deprecated. Use generateCommitmentAsync instead.");
570
+ }
571
+ async function computeNullifier(sk_spend, leafIndex) {
572
+ const [sk0, sk1] = splitTo2Limbs(sk_spend);
573
+ return await poseidonHash([sk0, sk1, leafIndex]);
574
+ }
575
+ async function computeNullifierAsync(skSpend, leafIndex) {
576
+ const skValue = typeof skSpend === "string" ? hexToBigint(skSpend) : hexToBigint(bytesToHex(skSpend));
577
+ return await computeNullifier(skValue, BigInt(leafIndex));
578
+ }
579
+ function computeNullifierSync(_skSpend, _leafIndex) {
580
+ throw new Error("computeNullifierSync is deprecated. Use computeNullifierAsync instead.");
581
+ }
582
+ async function computeOutputsHashAsync(outputs) {
583
+ let hash = 0n;
584
+ for (const output of outputs) {
585
+ const [lo, hi] = pubkeyToLimbs(output.recipient);
586
+ hash = await poseidonHash([hash, lo, hi, BigInt(output.amount)]);
587
+ }
588
+ return hash;
589
+ }
590
+ async function computeOutputsHash(outAddr, outAmount, outFlags) {
591
+ let hash = 0n;
592
+ for (let i = 0; i < 5; i++) {
593
+ if (outFlags[i] === 1) {
594
+ hash = await poseidonHash([hash, outAddr[i][0], outAddr[i][1], outAmount[i]]);
595
+ }
596
+ }
597
+ return hash;
598
+ }
599
+ function computeOutputsHashSync(_outputs) {
600
+ throw new Error("computeOutputsHashSync is deprecated. Use computeOutputsHashAsync instead.");
601
+ }
602
+ async function computeSwapOutputsHash(inputMintLimbs, outputMintLimbs, recipientAtaLimbs, minOutputAmount, publicAmount) {
603
+ return await poseidonHash([
604
+ inputMintLimbs[0],
605
+ inputMintLimbs[1],
606
+ outputMintLimbs[0],
607
+ outputMintLimbs[1],
608
+ recipientAtaLimbs[0],
609
+ recipientAtaLimbs[1],
610
+ minOutputAmount,
611
+ publicAmount
612
+ ]);
613
+ }
614
+ async function computeSwapOutputsHashAsync(inputMint, outputMint, recipientAta, minOutputAmount, amount) {
615
+ const inputMintLimbs = pubkeyToLimbs(inputMint);
616
+ const outputMintLimbs = pubkeyToLimbs(outputMint);
617
+ const recipientAtaLimbs = pubkeyToLimbs(recipientAta);
618
+ return await computeSwapOutputsHash(
619
+ inputMintLimbs,
620
+ outputMintLimbs,
621
+ recipientAtaLimbs,
622
+ BigInt(minOutputAmount),
623
+ BigInt(amount)
624
+ );
625
+ }
626
+ function computeSwapOutputsHashSync(_outputMint, _recipientAta, _minOutputAmount, _amount) {
627
+ throw new Error("computeSwapOutputsHashSync is deprecated. Use computeSwapOutputsHashAsync instead.");
628
+ }
498
629
  function bigintToBytes32(n) {
499
630
  const hex = n.toString(16).padStart(64, "0");
500
631
  const bytes = new Uint8Array(32);
@@ -767,11 +898,223 @@ function importKeys(exported) {
767
898
  return generateCloakKeys(masterSeed);
768
899
  }
769
900
 
901
+ // src/utils/network.ts
902
+ function detectNetworkFromRpcUrl(rpcUrl) {
903
+ const url = rpcUrl || process.env.NEXT_PUBLIC_SOLANA_RPC_URL || "";
904
+ const lowerUrl = url.toLowerCase();
905
+ if (lowerUrl.includes("mainnet") || lowerUrl.includes("api.mainnet-beta") || lowerUrl.includes("mainnet-beta")) {
906
+ return "mainnet";
907
+ }
908
+ if (lowerUrl.includes("testnet") || lowerUrl.includes("api.testnet")) {
909
+ return "testnet";
910
+ }
911
+ if (lowerUrl.includes("devnet") || lowerUrl.includes("api.devnet")) {
912
+ return "devnet";
913
+ }
914
+ if (lowerUrl.includes("localhost") || lowerUrl.includes("127.0.0.1") || lowerUrl.includes("local")) {
915
+ return "localnet";
916
+ }
917
+ return "mainnet";
918
+ }
919
+ function getRpcUrlForNetwork(network) {
920
+ switch (network) {
921
+ case "mainnet":
922
+ return "https://api.mainnet-beta.solana.com";
923
+ case "devnet":
924
+ return "https://api.devnet.solana.com";
925
+ case "localnet":
926
+ return "http://localhost:8899";
927
+ default:
928
+ return "https://api.mainnet-beta.solana.com";
929
+ }
930
+ }
931
+ function isValidRpcUrl(url) {
932
+ try {
933
+ const parsed = new URL(url);
934
+ return parsed.protocol === "http:" || parsed.protocol === "https:";
935
+ } catch {
936
+ return false;
937
+ }
938
+ }
939
+ function getExplorerUrl(signature, network = "devnet") {
940
+ const cluster = network === "mainnet" ? "" : `?cluster=${network}`;
941
+ return `https://explorer.solana.com/tx/${signature}${cluster}`;
942
+ }
943
+ function getAddressExplorerUrl(address, network = "devnet") {
944
+ const cluster = network === "mainnet" ? "" : `?cluster=${network}`;
945
+ return `https://explorer.solana.com/address/${address}${cluster}`;
946
+ }
947
+
948
+ // src/utils/fees.ts
949
+ var LAMPORTS_PER_SOL = 1e9;
950
+ var FIXED_FEE_LAMPORTS = 5e6;
951
+ var VARIABLE_FEE_NUMERATOR = 3;
952
+ var VARIABLE_FEE_DENOMINATOR = 1e3;
953
+ var VARIABLE_FEE_RATE = VARIABLE_FEE_NUMERATOR / VARIABLE_FEE_DENOMINATOR;
954
+ var MIN_DEPOSIT_LAMPORTS = 1e7;
955
+ function calculateFee(amountLamports) {
956
+ const variableFee = Math.floor(amountLamports * VARIABLE_FEE_NUMERATOR / VARIABLE_FEE_DENOMINATOR);
957
+ return FIXED_FEE_LAMPORTS + variableFee;
958
+ }
959
+ function calculateFeeBigint(amountLamports) {
960
+ const fixed = BigInt(FIXED_FEE_LAMPORTS);
961
+ const variable = amountLamports * BigInt(VARIABLE_FEE_NUMERATOR) / BigInt(VARIABLE_FEE_DENOMINATOR);
962
+ return fixed + variable;
963
+ }
964
+ function isWithdrawAmountSufficient(amountLamports) {
965
+ return amountLamports > calculateFeeBigint(amountLamports);
966
+ }
967
+ function getDistributableAmount(amountLamports) {
968
+ return amountLamports - calculateFee(amountLamports);
969
+ }
970
+ function formatAmount(lamports, decimals = 9) {
971
+ return (lamports / LAMPORTS_PER_SOL).toFixed(decimals);
972
+ }
973
+ function parseAmount(sol) {
974
+ const num = parseFloat(sol);
975
+ if (isNaN(num) || num < 0) {
976
+ throw new Error(`Invalid SOL amount: ${sol}`);
977
+ }
978
+ return Math.floor(num * LAMPORTS_PER_SOL);
979
+ }
980
+ function validateOutputsSum(outputs, expectedTotal) {
981
+ const sum = outputs.reduce((acc, out) => acc + out.amount, 0);
982
+ return sum === expectedTotal;
983
+ }
984
+ function calculateRelayFee(amountLamports, feeBps) {
985
+ if (feeBps < 0 || feeBps > 1e4) {
986
+ throw new Error("Fee basis points must be between 0 and 10000");
987
+ }
988
+ return Math.floor(amountLamports * feeBps / 1e4);
989
+ }
990
+
991
+ // src/core/note-manager.ts
992
+ async function generateNote(amountLamports, network) {
993
+ const actualNetwork = network || detectNetworkFromRpcUrl();
994
+ const skSpend = randomBytes(32);
995
+ const rBytes = randomBytes(32);
996
+ const commitmentBigint = await generateCommitmentAsync(amountLamports, rBytes, skSpend);
997
+ const commitmentHex = commitmentBigint.toString(16).padStart(64, "0");
998
+ const skSpendHex = bytesToHex(skSpend);
999
+ const rHex = bytesToHex(rBytes);
1000
+ return {
1001
+ version: "1.0",
1002
+ amount: amountLamports,
1003
+ commitment: commitmentHex,
1004
+ sk_spend: skSpendHex,
1005
+ r: rHex,
1006
+ timestamp: Date.now(),
1007
+ network: actualNetwork
1008
+ };
1009
+ }
1010
+ async function generateNoteFromWallet(amountLamports, keys, network) {
1011
+ const actualNetwork = network || detectNetworkFromRpcUrl();
1012
+ const rBytes = randomBytes(32);
1013
+ const sk_spend = hexToBytes(keys.spend.sk_spend_hex);
1014
+ const commitmentBigint = await generateCommitmentAsync(amountLamports, rBytes, sk_spend);
1015
+ const commitmentHex = commitmentBigint.toString(16).padStart(64, "0");
1016
+ return {
1017
+ version: "2.0",
1018
+ amount: amountLamports,
1019
+ commitment: commitmentHex,
1020
+ sk_spend: keys.spend.sk_spend_hex,
1021
+ r: bytesToHex(rBytes),
1022
+ timestamp: Date.now(),
1023
+ network: actualNetwork
1024
+ };
1025
+ }
1026
+ function parseNote(jsonString) {
1027
+ const note = JSON.parse(jsonString);
1028
+ if (!note.version || !note.amount || !note.commitment || !note.sk_spend || !note.r) {
1029
+ throw new CloakError(
1030
+ "Invalid note format: missing required fields",
1031
+ "validation",
1032
+ false
1033
+ );
1034
+ }
1035
+ if (!/^[0-9a-f]{64}$/i.test(note.commitment)) {
1036
+ throw new CloakError("Invalid commitment format", "validation", false);
1037
+ }
1038
+ if (!/^[0-9a-f]{64}$/i.test(note.sk_spend)) {
1039
+ throw new CloakError("Invalid sk_spend format", "validation", false);
1040
+ }
1041
+ if (!/^[0-9a-f]{64}$/i.test(note.r)) {
1042
+ throw new CloakError("Invalid r format", "validation", false);
1043
+ }
1044
+ return note;
1045
+ }
1046
+ function exportNote(note, pretty = false) {
1047
+ return pretty ? JSON.stringify(note, null, 2) : JSON.stringify(note);
1048
+ }
1049
+ function isWithdrawable(note) {
1050
+ return !!(note.depositSignature && note.leafIndex !== void 0 && note.root);
1051
+ }
1052
+ function updateNoteWithDeposit(note, depositInfo) {
1053
+ const updated = {
1054
+ ...note,
1055
+ depositSignature: depositInfo.signature,
1056
+ depositSlot: depositInfo.slot,
1057
+ leafIndex: depositInfo.leafIndex,
1058
+ root: depositInfo.root
1059
+ };
1060
+ if (depositInfo.merkleProof) {
1061
+ updated.merkleProof = depositInfo.merkleProof;
1062
+ }
1063
+ return updated;
1064
+ }
1065
+ function findNoteByCommitment(notes, commitment) {
1066
+ return notes.find((n) => n.commitment === commitment);
1067
+ }
1068
+ function filterNotesByNetwork(notes, network) {
1069
+ return notes.filter((n) => n.network === network);
1070
+ }
1071
+ function filterWithdrawableNotes(notes) {
1072
+ return notes.filter(isWithdrawable);
1073
+ }
1074
+ function exportWalletKeys(keys) {
1075
+ return exportKeys(keys);
1076
+ }
1077
+ function importWalletKeys(keysJson) {
1078
+ return importKeys(keysJson);
1079
+ }
1080
+ function getPublicViewKey(keys) {
1081
+ return keys.view.pvk_hex;
1082
+ }
1083
+ function getViewKey(keys) {
1084
+ return keys.view;
1085
+ }
1086
+ var calculateFee2 = calculateFee;
1087
+ function getDistributableAmount2(amountLamports) {
1088
+ return amountLamports - calculateFee2(amountLamports);
1089
+ }
1090
+ function getRecipientAmount(amountLamports) {
1091
+ return getDistributableAmount2(amountLamports);
1092
+ }
1093
+
770
1094
  // src/core/storage.ts
771
1095
  var MemoryStorageAdapter = class {
772
1096
  constructor() {
1097
+ this.notes = /* @__PURE__ */ new Map();
773
1098
  this.keys = null;
774
1099
  }
1100
+ saveNote(note) {
1101
+ this.notes.set(note.commitment, note);
1102
+ }
1103
+ loadAllNotes() {
1104
+ return Array.from(this.notes.values());
1105
+ }
1106
+ updateNote(commitment, updates) {
1107
+ const existing = this.notes.get(commitment);
1108
+ if (existing) {
1109
+ this.notes.set(commitment, { ...existing, ...updates });
1110
+ }
1111
+ }
1112
+ deleteNote(commitment) {
1113
+ this.notes.delete(commitment);
1114
+ }
1115
+ clearAllNotes() {
1116
+ this.notes.clear();
1117
+ }
775
1118
  saveKeys(keys) {
776
1119
  this.keys = keys;
777
1120
  }
@@ -782,10 +1125,10 @@ var MemoryStorageAdapter = class {
782
1125
  this.keys = null;
783
1126
  }
784
1127
  };
785
- var _LocalStorageAdapter = class _LocalStorageAdapter {
786
- constructor(keysKey = "cloak_wallet_keys") {
1128
+ var LocalStorageAdapter = class {
1129
+ constructor(notesKey = "cloak_notes", keysKey = "cloak_wallet_keys") {
1130
+ this.notesKey = notesKey;
787
1131
  this.keysKey = keysKey;
788
- _LocalStorageAdapter.runLegacyPurgeOnce();
789
1132
  }
790
1133
  getStorage() {
791
1134
  if (typeof globalThis !== "undefined" && globalThis.localStorage) {
@@ -793,6 +1136,47 @@ var _LocalStorageAdapter = class _LocalStorageAdapter {
793
1136
  }
794
1137
  return null;
795
1138
  }
1139
+ saveNote(note) {
1140
+ const storage = this.getStorage();
1141
+ if (!storage) throw new Error("localStorage not available");
1142
+ const notes = this.loadAllNotes();
1143
+ notes.push(note);
1144
+ storage.setItem(this.notesKey, JSON.stringify(notes));
1145
+ }
1146
+ loadAllNotes() {
1147
+ const storage = this.getStorage();
1148
+ if (!storage) return [];
1149
+ const stored = storage.getItem(this.notesKey);
1150
+ if (!stored) return [];
1151
+ try {
1152
+ return JSON.parse(stored);
1153
+ } catch {
1154
+ return [];
1155
+ }
1156
+ }
1157
+ updateNote(commitment, updates) {
1158
+ const storage = this.getStorage();
1159
+ if (!storage) return;
1160
+ const notes = this.loadAllNotes();
1161
+ const index = notes.findIndex((n) => n.commitment === commitment);
1162
+ if (index !== -1) {
1163
+ notes[index] = { ...notes[index], ...updates };
1164
+ storage.setItem(this.notesKey, JSON.stringify(notes));
1165
+ }
1166
+ }
1167
+ deleteNote(commitment) {
1168
+ const storage = this.getStorage();
1169
+ if (!storage) return;
1170
+ const notes = this.loadAllNotes();
1171
+ const filtered = notes.filter((n) => n.commitment !== commitment);
1172
+ storage.setItem(this.notesKey, JSON.stringify(filtered));
1173
+ }
1174
+ clearAllNotes() {
1175
+ const storage = this.getStorage();
1176
+ if (storage) {
1177
+ storage.removeItem(this.notesKey);
1178
+ }
1179
+ }
796
1180
  saveKeys(keys) {
797
1181
  const storage = this.getStorage();
798
1182
  if (!storage) throw new Error("localStorage not available");
@@ -815,64 +1199,126 @@ var _LocalStorageAdapter = class _LocalStorageAdapter {
815
1199
  storage.removeItem(this.keysKey);
816
1200
  }
817
1201
  }
818
- /**
819
- * Runs the legacy-purge exactly once per browser origin. Invoked
820
- * automatically by the constructor; exposed as a static for tests and
821
- * for callers who want to force the check before the first adapter
822
- * instantiation.
823
- */
824
- static runLegacyPurgeOnce() {
825
- if (typeof globalThis === "undefined" || !globalThis.localStorage) {
826
- return;
1202
+ };
1203
+
1204
+ // src/utils/validation.ts
1205
+ var import_web3 = require("@solana/web3.js");
1206
+ function isValidSolanaAddress(address) {
1207
+ try {
1208
+ new import_web3.PublicKey(address);
1209
+ return true;
1210
+ } catch {
1211
+ return false;
1212
+ }
1213
+ }
1214
+ function validateNote(note) {
1215
+ if (!note || typeof note !== "object") {
1216
+ throw new Error("Note must be an object");
1217
+ }
1218
+ const requiredFields = ["version", "amount", "commitment", "sk_spend", "r", "timestamp", "network"];
1219
+ for (const field of requiredFields) {
1220
+ if (!(field in note)) {
1221
+ throw new Error(`Missing required field: ${field}`);
827
1222
  }
828
- const storage = globalThis.localStorage;
829
- if (storage.getItem(_LocalStorageAdapter.LEGACY_PURGE_SENTINEL_KEY)) {
830
- return;
1223
+ }
1224
+ if (typeof note.version !== "string") {
1225
+ throw new Error("Version must be a string");
1226
+ }
1227
+ if (typeof note.amount !== "number" || note.amount <= 0) {
1228
+ throw new Error("Amount must be a positive number");
1229
+ }
1230
+ if (!isValidHex(note.commitment, 32)) {
1231
+ throw new Error("Invalid commitment format (expected 64 hex characters)");
1232
+ }
1233
+ if (!isValidHex(note.sk_spend, 32)) {
1234
+ throw new Error("Invalid sk_spend format (expected 64 hex characters)");
1235
+ }
1236
+ if (!isValidHex(note.r, 32)) {
1237
+ throw new Error("Invalid r format (expected 64 hex characters)");
1238
+ }
1239
+ if (typeof note.timestamp !== "number" || note.timestamp <= 0) {
1240
+ throw new Error("Timestamp must be a positive number");
1241
+ }
1242
+ if (!["localnet", "devnet", "testnet", "mainnet"].includes(note.network)) {
1243
+ throw new Error("Network must be localnet, devnet, testnet, or mainnet");
1244
+ }
1245
+ if (note.depositSignature !== void 0 && typeof note.depositSignature !== "string") {
1246
+ throw new Error("Deposit signature must be a string");
1247
+ }
1248
+ if (note.depositSlot !== void 0 && typeof note.depositSlot !== "number") {
1249
+ throw new Error("Deposit slot must be a number");
1250
+ }
1251
+ if (note.leafIndex !== void 0) {
1252
+ if (typeof note.leafIndex !== "number" || note.leafIndex < 0) {
1253
+ throw new Error("Leaf index must be a non-negative number");
831
1254
  }
832
- storage.removeItem(_LocalStorageAdapter.LEGACY_NOTES_KEY);
833
- try {
834
- storage.setItem(_LocalStorageAdapter.LEGACY_PURGE_SENTINEL_KEY, "1");
835
- } catch {
1255
+ }
1256
+ if (note.root !== void 0 && !isValidHex(note.root, 32)) {
1257
+ throw new Error("Invalid root format (expected 64 hex characters)");
1258
+ }
1259
+ if (note.merkleProof !== void 0) {
1260
+ if (!Array.isArray(note.merkleProof.pathElements)) {
1261
+ throw new Error("Merkle proof pathElements must be an array");
1262
+ }
1263
+ if (!Array.isArray(note.merkleProof.pathIndices)) {
1264
+ throw new Error("Merkle proof pathIndices must be an array");
1265
+ }
1266
+ if (note.merkleProof.pathElements.length !== note.merkleProof.pathIndices.length) {
1267
+ throw new Error("Merkle proof pathElements and pathIndices must have same length");
836
1268
  }
837
1269
  }
838
- /**
839
- * Manual cleanup helper for callers that stored notes under a non-default
840
- * key. The auto-purge in the constructor only handles the canonical
841
- * `cloak_notes` slot; pass the custom key here.
842
- *
843
- * Pre-0.1.6 versions stored the user's `CloakNote[]` (including
844
- * `r` randomness and `sk_spend` secret-key hex) as JSON plaintext.
845
- * Those notes are no longer usable (the OLD `withdraw_regular.circom`
846
- * flow is incompatible with the deployed UTXO program) but the plaintext
847
- * would otherwise linger indefinitely.
848
- *
849
- * Idempotent; safe to call when the key doesn't exist or when running
850
- * outside a browser (no-op). Does NOT update the auto-purge sentinel.
851
- *
852
- * @param notesKey - localStorage key to remove (default `"cloak_notes"`,
853
- * matching the pre-0.1.6 default).
854
- */
855
- static purgeLegacyNoteStorage(notesKey = _LocalStorageAdapter.LEGACY_NOTES_KEY) {
856
- if (typeof globalThis === "undefined" || !globalThis.localStorage) {
857
- return;
1270
+ }
1271
+ function parseNote2(jsonString) {
1272
+ let parsed;
1273
+ try {
1274
+ parsed = JSON.parse(jsonString);
1275
+ } catch (error) {
1276
+ throw new Error("Invalid JSON format");
1277
+ }
1278
+ validateNote(parsed);
1279
+ return parsed;
1280
+ }
1281
+ function validateWithdrawableNote(note) {
1282
+ if (!note.depositSignature) {
1283
+ throw new Error("Note must be deposited before withdrawal (missing depositSignature)");
1284
+ }
1285
+ if (note.leafIndex === void 0) {
1286
+ throw new Error("Note must be deposited before withdrawal (missing leafIndex)");
1287
+ }
1288
+ if (!note.root) {
1289
+ throw new Error("Note must have historical root for withdrawal");
1290
+ }
1291
+ if (!note.merkleProof) {
1292
+ throw new Error("Note must have Merkle proof for withdrawal");
1293
+ }
1294
+ if (note.merkleProof.pathElements.length === 0) {
1295
+ throw new Error("Merkle proof is empty");
1296
+ }
1297
+ }
1298
+ function validateTransfers(recipients, totalAmount) {
1299
+ if (recipients.length === 0) {
1300
+ throw new Error("At least one recipient is required");
1301
+ }
1302
+ if (recipients.length > 5) {
1303
+ throw new Error("Maximum 5 recipients allowed");
1304
+ }
1305
+ for (let i = 0; i < recipients.length; i++) {
1306
+ const transfer2 = recipients[i];
1307
+ const isPublicKeyLike = transfer2.recipient && typeof transfer2.recipient.toBase58 === "function" && typeof transfer2.recipient.toBuffer === "function";
1308
+ if (!isPublicKeyLike) {
1309
+ throw new Error(`Recipient ${i} must be a PublicKey (got ${typeof transfer2.recipient})`);
1310
+ }
1311
+ if (typeof transfer2.amount !== "number" || transfer2.amount <= 0) {
1312
+ throw new Error(`Recipient ${i} amount must be a positive number`);
858
1313
  }
859
- globalThis.localStorage.removeItem(notesKey);
860
1314
  }
861
- };
862
- /**
863
- * Sentinel key set after the auto-purge runs. Persists across browser
864
- * sessions so the purge never re-runs on the same origin.
865
- */
866
- _LocalStorageAdapter.LEGACY_PURGE_SENTINEL_KEY = "cloak_purged_v0_1_5";
867
- /**
868
- * Default localStorage key the legacy `<= 0.1.5` SDK used for the
869
- * plaintext `CloakNote[]` blob.
870
- */
871
- _LocalStorageAdapter.LEGACY_NOTES_KEY = "cloak_notes";
872
- var LocalStorageAdapter = _LocalStorageAdapter;
873
-
874
- // src/services/RelayService.ts
875
- var import_sha2 = require("@noble/hashes/sha2");
1315
+ const sum = recipients.reduce((acc, t) => acc + t.amount, 0);
1316
+ if (sum !== totalAmount) {
1317
+ throw new Error(
1318
+ `Recipients sum (${sum}) does not match expected total (${totalAmount})`
1319
+ );
1320
+ }
1321
+ }
876
1322
 
877
1323
  // src/utils/errors.ts
878
1324
  var RootNotFoundError = class extends Error {
@@ -932,7 +1378,8 @@ function classifyRelayError(responseText, httpStatus) {
932
1378
  inner = responseText;
933
1379
  }
934
1380
  const haystack = inner.toLowerCase();
935
- if (haystack.includes("0x1020") || haystack.includes("doublespend")) {
1381
+ if (haystack.includes("0x1020") || // 0x1020 as decimal in a JSON-stringified confirmTransaction err (`"Custom":4128`).
1382
+ haystack.includes('"custom":4128') || haystack.includes("doublespend")) {
936
1383
  return new UtxoAlreadySpentError(
937
1384
  "This shielded balance was already spent. The UTXO's nullifier is registered on-chain \u2014 typically means you spent it via another session or device. Run verifyUtxos() to reconcile local state."
938
1385
  );
@@ -1768,6 +2215,7 @@ function formatErrorForLogging(error) {
1768
2215
  }
1769
2216
 
1770
2217
  // src/services/RelayService.ts
2218
+ var import_sha2 = require("@noble/hashes/sha2");
1771
2219
  var RelayService = class {
1772
2220
  /**
1773
2221
  * Create a new Relay Service client
@@ -2136,24 +2584,88 @@ var RelayService = class {
2136
2584
  }
2137
2585
  };
2138
2586
 
2139
- // src/utils/pda.ts
2587
+ // src/solana/instructions.ts
2140
2588
  var import_web32 = require("@solana/web3.js");
2589
+ function createDepositInstruction(params) {
2590
+ if (params.commitment.length !== 32) {
2591
+ throw new Error(
2592
+ `Invalid commitment length: ${params.commitment.length} (expected 32 bytes)`
2593
+ );
2594
+ }
2595
+ if (params.amount <= 0) {
2596
+ throw new Error("Amount must be positive");
2597
+ }
2598
+ const discriminant = new Uint8Array([1]);
2599
+ const amountBytes = new Uint8Array(8);
2600
+ new DataView(amountBytes.buffer).setBigUint64(
2601
+ 0,
2602
+ BigInt(params.amount),
2603
+ true
2604
+ // little-endian
2605
+ );
2606
+ const data = new Uint8Array(41);
2607
+ data.set(discriminant, 0);
2608
+ data.set(amountBytes, 1);
2609
+ data.set(params.commitment, 9);
2610
+ return new import_web32.TransactionInstruction({
2611
+ programId: params.programId,
2612
+ keys: [
2613
+ // Account 0: Payer (signer, writable) - pays for transaction
2614
+ { pubkey: params.payer, isSigner: true, isWritable: true },
2615
+ // Account 1: Pool (writable) - receives SOL
2616
+ { pubkey: params.pool, isSigner: false, isWritable: true },
2617
+ // Account 2: System Program (readonly) - for transfers
2618
+ { pubkey: import_web32.SystemProgram.programId, isSigner: false, isWritable: false },
2619
+ // Account 3: Merkle Tree (writable) - stores on-chain Merkle tree
2620
+ { pubkey: params.merkleTree, isSigner: false, isWritable: true }
2621
+ ],
2622
+ data: Buffer.from(data)
2623
+ });
2624
+ }
2625
+ function validateDepositParams(params) {
2626
+ if (!(params.programId instanceof import_web32.PublicKey)) {
2627
+ throw new Error("programId must be a PublicKey");
2628
+ }
2629
+ if (!(params.payer instanceof import_web32.PublicKey)) {
2630
+ throw new Error("payer must be a PublicKey");
2631
+ }
2632
+ if (!(params.pool instanceof import_web32.PublicKey)) {
2633
+ throw new Error("pool must be a PublicKey");
2634
+ }
2635
+ if (!(params.merkleTree instanceof import_web32.PublicKey)) {
2636
+ throw new Error("merkleTree must be a PublicKey");
2637
+ }
2638
+ if (typeof params.amount !== "number" || params.amount <= 0) {
2639
+ throw new Error("amount must be a positive number");
2640
+ }
2641
+ if (!(params.commitment instanceof Uint8Array)) {
2642
+ throw new Error("commitment must be a Uint8Array");
2643
+ }
2644
+ if (params.commitment.length !== 32) {
2645
+ throw new Error(
2646
+ `commitment must be 32 bytes (got ${params.commitment.length})`
2647
+ );
2648
+ }
2649
+ }
2650
+
2651
+ // src/utils/pda.ts
2652
+ var import_web34 = require("@solana/web3.js");
2141
2653
  init_utxo();
2142
2654
  function getShieldPoolPDAs(programId, mint = NATIVE_SOL_MINT) {
2143
2655
  const pid = programId || CLOAK_PROGRAM_ID;
2144
- const [pool] = import_web32.PublicKey.findProgramAddressSync(
2656
+ const [pool] = import_web34.PublicKey.findProgramAddressSync(
2145
2657
  [Buffer.from("pool"), mint.toBuffer()],
2146
2658
  pid
2147
2659
  );
2148
- const [merkleTree] = import_web32.PublicKey.findProgramAddressSync(
2660
+ const [merkleTree] = import_web34.PublicKey.findProgramAddressSync(
2149
2661
  [Buffer.from("merkle_tree"), mint.toBuffer()],
2150
2662
  pid
2151
2663
  );
2152
- const [treasury] = import_web32.PublicKey.findProgramAddressSync(
2664
+ const [treasury] = import_web34.PublicKey.findProgramAddressSync(
2153
2665
  [Buffer.from("treasury"), mint.toBuffer()],
2154
2666
  pid
2155
2667
  );
2156
- const [vaultAuthority] = import_web32.PublicKey.findProgramAddressSync(
2668
+ const [vaultAuthority] = import_web34.PublicKey.findProgramAddressSync(
2157
2669
  [Buffer.from("vault_authority"), mint.toBuffer()],
2158
2670
  pid
2159
2671
  );
@@ -2169,7 +2681,7 @@ function getNullifierPDA(poolPubkey, nullifier, programId) {
2169
2681
  if (nullifier.length !== 32) {
2170
2682
  throw new Error(`Nullifier must be 32 bytes, got ${nullifier.length}`);
2171
2683
  }
2172
- return import_web32.PublicKey.findProgramAddressSync(
2684
+ return import_web34.PublicKey.findProgramAddressSync(
2173
2685
  [Buffer.from("nullifier"), poolPubkey.toBuffer(), Buffer.from(nullifier)],
2174
2686
  pid
2175
2687
  );
@@ -2179,29 +2691,273 @@ function getSwapStatePDA(poolPubkey, nullifier, programId) {
2179
2691
  if (nullifier.length !== 32) {
2180
2692
  throw new Error(`Nullifier must be 32 bytes, got ${nullifier.length}`);
2181
2693
  }
2182
- return import_web32.PublicKey.findProgramAddressSync(
2694
+ return import_web34.PublicKey.findProgramAddressSync(
2183
2695
  [Buffer.from("swap_state"), poolPubkey.toBuffer(), Buffer.from(nullifier)],
2184
2696
  pid
2185
2697
  );
2186
2698
  }
2187
2699
 
2188
- // src/core/CloakSDK.ts
2189
- init_utxo();
2190
-
2191
- // src/utils/logger.ts
2192
- var globalDebugEnabled = false;
2193
- function checkEnvDebug() {
2194
- if (typeof process !== "undefined" && process.env) {
2195
- return !!(process.env.CLOAK_DEBUG === "1" || process.env.CLOAK_DEBUG === "true" || process.env.DEBUG?.includes("cloak"));
2196
- }
2197
- return false;
2700
+ // src/utils/proof-generation.ts
2701
+ var snarkjs = __toESM(require("snarkjs"), 1);
2702
+ var IS_REACT_NATIVE = typeof navigator !== "undefined" && navigator.product === "ReactNative";
2703
+ var IS_BROWSER = IS_REACT_NATIVE || typeof window !== "undefined" || typeof globalThis !== "undefined" && typeof globalThis.document !== "undefined";
2704
+ var DEFAULT_CIRCUITS_URL = "https://storage.googleapis.com/cloak-circuits/circuits/0.1.0";
2705
+ var SKIP_CIRCUIT_VERIFY_ENV = typeof process !== "undefined" && process.env && process.env.CLOAK_SKIP_CIRCUIT_INTEGRITY_CHECK === "1";
2706
+ function isUrl(path) {
2707
+ return path.startsWith("http://") || path.startsWith("https://");
2198
2708
  }
2199
- globalDebugEnabled = checkEnvDebug();
2200
- function setDebugMode(enabled) {
2201
- globalDebugEnabled = enabled;
2709
+ function resolveCircuitsUrl(circuitsPath2) {
2710
+ if (circuitsPath2.trim() !== "" && circuitsPath2.trim() !== DEFAULT_CIRCUITS_URL) {
2711
+ return DEFAULT_CIRCUITS_URL;
2712
+ }
2713
+ return DEFAULT_CIRCUITS_URL;
2202
2714
  }
2203
- function isDebugEnabled() {
2204
- return globalDebugEnabled;
2715
+ async function fetchFileAsBuffer(url) {
2716
+ const response = await fetch(url);
2717
+ if (!response.ok) {
2718
+ throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
2719
+ }
2720
+ const arrayBuffer = await response.arrayBuffer();
2721
+ return new Uint8Array(arrayBuffer);
2722
+ }
2723
+ async function sha256Hex(data) {
2724
+ if (IS_BROWSER) {
2725
+ const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
2726
+ const buffer = bytes.buffer.slice(
2727
+ bytes.byteOffset,
2728
+ bytes.byteOffset + bytes.byteLength
2729
+ );
2730
+ const hashBuffer = await crypto.subtle.digest("SHA-256", buffer);
2731
+ return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
2732
+ }
2733
+ const nodeCrypto = await getNodeCrypto();
2734
+ return nodeCrypto.createHash("sha256").update(data).digest("hex");
2735
+ }
2736
+ var _nodeCrypto = null;
2737
+ function nodeRequire(moduleName) {
2738
+ if (IS_BROWSER) {
2739
+ throw new Error(`Node.js ${moduleName} module not available in browser/React Native`);
2740
+ }
2741
+ try {
2742
+ const requireFunc = new Function("moduleName", "return require(moduleName)");
2743
+ return requireFunc(moduleName);
2744
+ } catch {
2745
+ throw new Error(`Cannot load Node.js module ${moduleName} synchronously in ESM.`);
2746
+ }
2747
+ }
2748
+ async function getNodeCrypto() {
2749
+ if (_nodeCrypto) return _nodeCrypto;
2750
+ _nodeCrypto = nodeRequire("crypto");
2751
+ return _nodeCrypto;
2752
+ }
2753
+ async function generateWithdrawRegularProof(inputs, circuitsPath2) {
2754
+ const circuitsUrl = resolveCircuitsUrl(circuitsPath2);
2755
+ let wasmInput;
2756
+ let zkeyInput;
2757
+ if (IS_BROWSER) {
2758
+ wasmInput = `${circuitsUrl}/withdraw_regular_js/withdraw_regular.wasm`;
2759
+ zkeyInput = `${circuitsUrl}/withdraw_regular_final.zkey`;
2760
+ } else {
2761
+ const wasmUrl = `${circuitsUrl}/withdraw_regular_js/withdraw_regular.wasm`;
2762
+ const zkeyUrl = `${circuitsUrl}/withdraw_regular_final.zkey`;
2763
+ const [wasmData, zkeyData] = await Promise.all([
2764
+ fetchFileAsBuffer(wasmUrl),
2765
+ fetchFileAsBuffer(zkeyUrl)
2766
+ ]);
2767
+ wasmInput = wasmData;
2768
+ zkeyInput = zkeyData;
2769
+ }
2770
+ const verification = await verifyCircuitIntegrity(circuitsUrl, "withdraw_regular");
2771
+ if (!verification.valid) {
2772
+ throw new Error(
2773
+ `withdraw_regular circuit integrity verification failed: ${verification.error ?? "hash mismatch"}`
2774
+ );
2775
+ }
2776
+ const circuitInputs = {
2777
+ // Public signals
2778
+ root: inputs.root.toString(),
2779
+ nullifier: inputs.nullifier.toString(),
2780
+ outputs_hash: inputs.outputs_hash.toString(),
2781
+ public_amount: inputs.public_amount.toString(),
2782
+ // Private common inputs
2783
+ amount: inputs.amount.toString(),
2784
+ leaf_index: inputs.leaf_index.toString(),
2785
+ sk: inputs.sk.map((x) => x.toString()),
2786
+ r: inputs.r.map((x) => x.toString()),
2787
+ pathElements: inputs.pathElements.map((x) => x.toString()),
2788
+ pathIndices: inputs.pathIndices,
2789
+ // Outputs
2790
+ num_outputs: inputs.num_outputs,
2791
+ out_addr: inputs.out_addr.map((addr) => addr.map((x) => x.toString())),
2792
+ out_amount: inputs.out_amount.map((x) => x.toString()),
2793
+ out_flags: inputs.out_flags,
2794
+ // Fee calculation
2795
+ var_fee: inputs.var_fee.toString(),
2796
+ rem: inputs.rem.toString()
2797
+ };
2798
+ const { proof, publicSignals } = await snarkjs.groth16.fullProve(circuitInputs, wasmInput, zkeyInput);
2799
+ const proofBytes = proofToBytes(proof);
2800
+ return {
2801
+ proof,
2802
+ publicSignals,
2803
+ proofBytes,
2804
+ publicInputsBytes: new Uint8Array(0)
2805
+ // Not used, kept for compatibility
2806
+ };
2807
+ }
2808
+ async function generateWithdrawSwapProof(inputs, circuitsPath2) {
2809
+ const circuitsUrl = resolveCircuitsUrl(circuitsPath2);
2810
+ let wasmInput;
2811
+ let zkeyInput;
2812
+ if (IS_BROWSER) {
2813
+ wasmInput = `${circuitsUrl}/withdraw_swap_js/withdraw_swap.wasm`;
2814
+ zkeyInput = `${circuitsUrl}/withdraw_swap_final.zkey`;
2815
+ } else {
2816
+ const wasmUrl = `${circuitsUrl}/withdraw_swap_js/withdraw_swap.wasm`;
2817
+ const zkeyUrl = `${circuitsUrl}/withdraw_swap_final.zkey`;
2818
+ const [wasmData, zkeyData] = await Promise.all([
2819
+ fetchFileAsBuffer(wasmUrl),
2820
+ fetchFileAsBuffer(zkeyUrl)
2821
+ ]);
2822
+ wasmInput = wasmData;
2823
+ zkeyInput = zkeyData;
2824
+ }
2825
+ const verification = await verifyCircuitIntegrity(circuitsUrl, "withdraw_swap");
2826
+ if (!verification.valid) {
2827
+ throw new Error(
2828
+ `withdraw_swap circuit integrity verification failed: ${verification.error ?? "hash mismatch"}`
2829
+ );
2830
+ }
2831
+ const sk = splitTo2Limbs(inputs.sk_spend);
2832
+ const r = splitTo2Limbs(inputs.r);
2833
+ const circuitInputs = {
2834
+ // Public signals (must be provided for witness generation)
2835
+ root: inputs.root.toString(),
2836
+ nullifier: inputs.nullifier.toString(),
2837
+ outputs_hash: inputs.outputs_hash.toString(),
2838
+ public_amount: inputs.public_amount.toString(),
2839
+ // Private inputs
2840
+ sk: sk.map((x) => x.toString()),
2841
+ r: r.map((x) => x.toString()),
2842
+ amount: inputs.amount.toString(),
2843
+ leaf_index: inputs.leaf_index.toString(),
2844
+ pathElements: inputs.path_elements.map((x) => x.toString()),
2845
+ pathIndices: inputs.path_indices,
2846
+ input_mint: inputs.input_mint.map((x) => x.toString()),
2847
+ output_mint: inputs.output_mint.map((x) => x.toString()),
2848
+ recipient_ata: inputs.recipient_ata.map((x) => x.toString()),
2849
+ min_output_amount: inputs.min_output_amount.toString(),
2850
+ // Note: fee is computed off-chain and validated on-chain:
2851
+ // - fixed_fee is 5000000 (0.005 SOL)
2852
+ // - var_fee and rem are computed from amount * 3 / 1000 (0.3%)
2853
+ var_fee: inputs.var_fee.toString(),
2854
+ rem: inputs.rem.toString()
2855
+ };
2856
+ const { proof, publicSignals } = await snarkjs.groth16.fullProve(
2857
+ circuitInputs,
2858
+ wasmInput,
2859
+ zkeyInput
2860
+ );
2861
+ const proofBytes = proofToBytes(proof);
2862
+ return {
2863
+ proof,
2864
+ publicSignals,
2865
+ proofBytes,
2866
+ publicInputsBytes: new Uint8Array(0)
2867
+ // Not used, kept for compatibility
2868
+ };
2869
+ }
2870
+ async function areCircuitsAvailable(circuitsPath2) {
2871
+ if (!circuitsPath2 || circuitsPath2 === "") {
2872
+ return false;
2873
+ }
2874
+ return isUrl(resolveCircuitsUrl(circuitsPath2));
2875
+ }
2876
+ async function getDefaultCircuitsPath() {
2877
+ return DEFAULT_CIRCUITS_URL;
2878
+ }
2879
+ var EXPECTED_CIRCUIT_HASHES = {
2880
+ withdraw_regular_wasm: "b80c364c926332111945ef437b07a71720fae774e08206cb11b6171f2a2420a3",
2881
+ withdraw_regular_zkey: "6dbca2612f7cc257b897d93384ae9dd09c1617fd1668f8287efb1b124488d144",
2882
+ withdraw_swap_wasm: "a296badc0047c211a4724c96a4e64a77cd75fbde7cfa4cf23af3640a723e5686",
2883
+ withdraw_swap_zkey: "95251954feaac80396651d12a0df3288630fa75db9410aebecb8aaee17a4f6ea"
2884
+ };
2885
+ async function verifyCircuitIntegrity(circuitsPath2, circuit) {
2886
+ const circuitsUrl = resolveCircuitsUrl(circuitsPath2);
2887
+ if (SKIP_CIRCUIT_VERIFY_ENV) {
2888
+ return {
2889
+ valid: true,
2890
+ circuit,
2891
+ error: "Verification skipped (CLOAK_SKIP_CIRCUIT_INTEGRITY_CHECK=1)"
2892
+ };
2893
+ }
2894
+ const expected = circuit === "withdraw_regular" ? {
2895
+ wasm: EXPECTED_CIRCUIT_HASHES.withdraw_regular_wasm,
2896
+ zkey: EXPECTED_CIRCUIT_HASHES.withdraw_regular_zkey
2897
+ } : {
2898
+ wasm: EXPECTED_CIRCUIT_HASHES.withdraw_swap_wasm,
2899
+ zkey: EXPECTED_CIRCUIT_HASHES.withdraw_swap_zkey
2900
+ };
2901
+ const wasmPath = `${circuitsUrl}/${circuit}_js/${circuit}.wasm`;
2902
+ const zkeyPath = `${circuitsUrl}/${circuit}_final.zkey`;
2903
+ try {
2904
+ const [wasmBytes, zkeyBytes] = await Promise.all([
2905
+ fetchFileAsBuffer(wasmPath),
2906
+ fetchFileAsBuffer(zkeyPath)
2907
+ ]);
2908
+ const computed = {
2909
+ wasm: await sha256Hex(wasmBytes),
2910
+ zkey: await sha256Hex(zkeyBytes)
2911
+ };
2912
+ if (computed.wasm === expected.wasm && computed.zkey === expected.zkey) {
2913
+ return {
2914
+ valid: true,
2915
+ circuit,
2916
+ computed,
2917
+ expected
2918
+ };
2919
+ }
2920
+ return {
2921
+ valid: false,
2922
+ circuit,
2923
+ error: `Circuit artifact hash mismatch for ${circuit}`,
2924
+ computed,
2925
+ expected
2926
+ };
2927
+ } catch (error) {
2928
+ return {
2929
+ valid: false,
2930
+ circuit,
2931
+ error: `Circuit verification failed: ${error instanceof Error ? error.message : String(error)}`
2932
+ };
2933
+ }
2934
+ }
2935
+ async function verifyAllCircuits(circuitsPath2) {
2936
+ const circuitsUrl = resolveCircuitsUrl(circuitsPath2);
2937
+ const results = await Promise.all([
2938
+ verifyCircuitIntegrity(circuitsUrl, "withdraw_regular"),
2939
+ verifyCircuitIntegrity(circuitsUrl, "withdraw_swap")
2940
+ ]);
2941
+ return results;
2942
+ }
2943
+
2944
+ // src/core/CloakSDK.ts
2945
+ init_utxo();
2946
+
2947
+ // src/utils/logger.ts
2948
+ var globalDebugEnabled = false;
2949
+ function checkEnvDebug() {
2950
+ if (typeof process !== "undefined" && process.env) {
2951
+ return !!(process.env.CLOAK_DEBUG === "1" || process.env.CLOAK_DEBUG === "true" || process.env.DEBUG?.includes("cloak"));
2952
+ }
2953
+ return false;
2954
+ }
2955
+ globalDebugEnabled = checkEnvDebug();
2956
+ function setDebugMode(enabled) {
2957
+ globalDebugEnabled = enabled;
2958
+ }
2959
+ function isDebugEnabled() {
2960
+ return globalDebugEnabled;
2205
2961
  }
2206
2962
  var LOG_COLORS = {
2207
2963
  DEBUG: "\x1B[90m",
@@ -2404,22 +3160,50 @@ async function computeProofInternal(leafIndex, _nextIndex, subtrees) {
2404
3160
  }
2405
3161
 
2406
3162
  // src/core/CloakSDK.ts
2407
- var CLOAK_PROGRAM_ID = new import_web33.PublicKey("zh1eLd6rSphLejbFfJEneUwzHRfMKxgzrgkfwA6qRkW");
3163
+ var COMPUTE_BUDGET_PROGRAM_ID = new import_web35.PublicKey("ComputeBudget111111111111111111111111111111");
3164
+ function createSetLoadedAccountsDataSizeLimitInstruction(bytes) {
3165
+ const data = Buffer.alloc(5);
3166
+ data.writeUInt8(4, 0);
3167
+ data.writeUInt32LE(bytes, 1);
3168
+ return new import_web35.TransactionInstruction({
3169
+ keys: [],
3170
+ programId: COMPUTE_BUDGET_PROGRAM_ID,
3171
+ data
3172
+ });
3173
+ }
3174
+ var CLOAK_PROGRAM_ID = new import_web35.PublicKey("zh1eLd6rSphLejbFfJEneUwzHRfMKxgzrgkfwA6qRkW");
2408
3175
  var CLOAK_API_URL = "https://api.cloak.ag";
2409
3176
  var CloakSDK = class {
3177
+ /**
3178
+ * Create a new Cloak SDK client
3179
+ *
3180
+ * @param config - Client configuration
3181
+ *
3182
+ * @example Node.js mode (with keypair)
3183
+ * ```typescript
3184
+ * const sdk = new CloakSDK({
3185
+ * keypairBytes: keypair.secretKey,
3186
+ * network: "devnet"
3187
+ * });
3188
+ * ```
3189
+ *
3190
+ * @example Browser mode (with wallet adapter)
3191
+ * ```typescript
3192
+ * const sdk = new CloakSDK({
3193
+ * wallet: walletAdapter,
3194
+ * network: "devnet"
3195
+ * });
3196
+ * ```
3197
+ */
2410
3198
  constructor(config) {
2411
3199
  if (!config.keypairBytes && !config.wallet) {
2412
- throw new CloakError(
2413
- "Must provide either keypairBytes (Node.js) or wallet (Browser)",
2414
- "validation",
2415
- false
2416
- );
3200
+ throw new Error("Must provide either keypairBytes (Node.js) or wallet (Browser)");
2417
3201
  }
2418
3202
  if (config.debug) {
2419
3203
  setDebugMode(true);
2420
3204
  }
2421
3205
  if (config.keypairBytes) {
2422
- this.keypair = import_web33.Keypair.fromSecretKey(config.keypairBytes);
3206
+ this.keypair = import_web35.Keypair.fromSecretKey(config.keypairBytes);
2423
3207
  }
2424
3208
  this.wallet = config.wallet;
2425
3209
  this.cloakKeys = config.cloakKeys;
@@ -2433,9 +3217,13 @@ var CloakSDK = class {
2433
3217
  }
2434
3218
  }
2435
3219
  const programId = config.programId || CLOAK_PROGRAM_ID;
2436
- const { pool, merkleTree, treasury } = getShieldPoolPDAs(programId, NATIVE_SOL_MINT);
3220
+ const { pool, merkleTree, treasury } = getShieldPoolPDAs(
3221
+ programId,
3222
+ NATIVE_SOL_MINT
3223
+ );
2437
3224
  this.config = {
2438
3225
  network: config.network || "mainnet",
3226
+ keypairBytes: config.keypairBytes,
2439
3227
  cloakKeys: config.cloakKeys,
2440
3228
  programId,
2441
3229
  poolAddress: pool,
@@ -2444,7 +3232,9 @@ var CloakSDK = class {
2444
3232
  debug: config.debug
2445
3233
  };
2446
3234
  }
2447
- /** Public key of the configured signer (keypair or wallet). */
3235
+ /**
3236
+ * Get the public key for deposits (from keypair or wallet)
3237
+ */
2448
3238
  getPublicKey() {
2449
3239
  if (this.keypair) {
2450
3240
  return this.keypair.publicKey;
@@ -2452,23 +3242,877 @@ var CloakSDK = class {
2452
3242
  if (this.wallet?.publicKey) {
2453
3243
  return this.wallet.publicKey;
2454
3244
  }
2455
- throw new CloakError(
2456
- "No public key available - wallet not connected and no keypair provided",
2457
- "wallet",
2458
- false
3245
+ throw new Error("No public key available - wallet not connected or keypair not provided");
3246
+ }
3247
+ /**
3248
+ * Check if the SDK is using a wallet adapter
3249
+ */
3250
+ isWalletMode() {
3251
+ return !!this.wallet && !this.keypair;
3252
+ }
3253
+ /**
3254
+ * Deposit SOL into the Cloak protocol
3255
+ *
3256
+ * Creates a new note (or uses a provided one), submits a deposit transaction,
3257
+ * and registers with the indexer.
3258
+ *
3259
+ * @param connection - Solana connection
3260
+ * @param payer - Payer wallet with sendTransaction method
3261
+ * @param amountOrNote - Amount in lamports OR an existing note to deposit
3262
+ * @param options - Optional configuration
3263
+ * @returns Deposit result with note and transaction info
3264
+ *
3265
+ * @example
3266
+ * ```typescript
3267
+ * // Generate and deposit in one step
3268
+ * const result = await client.deposit(
3269
+ * connection,
3270
+ * wallet,
3271
+ * 1_000_000_000,
3272
+ * {
3273
+ * onProgress: (status) => console.log(status)
3274
+ * }
3275
+ * );
3276
+ *
3277
+ * // Or deposit a pre-generated note
3278
+ * const note = client.generateNote(1_000_000_000);
3279
+ * const result = await client.deposit(connection, wallet, note);
3280
+ * ```
3281
+ */
3282
+ async deposit(connection, amountOrNote, options) {
3283
+ try {
3284
+ let note;
3285
+ let isNewNote = false;
3286
+ if (typeof amountOrNote === "number") {
3287
+ options?.onProgress?.("generating_note", { message: "Generating note with secrets..." });
3288
+ note = await generateNote(amountOrNote, this.config.network);
3289
+ isNewNote = true;
3290
+ } else {
3291
+ note = amountOrNote;
3292
+ if (note.depositSignature) {
3293
+ throw new Error("Note has already been deposited");
3294
+ }
3295
+ }
3296
+ if (isNewNote) {
3297
+ await this.storage.saveNote(note);
3298
+ if (options?.onNoteGenerated) {
3299
+ options?.onProgress?.("awaiting_note_acknowledgment", {
3300
+ message: "Waiting for note to be saved before proceeding with deposit..."
3301
+ });
3302
+ try {
3303
+ await options.onNoteGenerated(note);
3304
+ } catch (error) {
3305
+ throw new Error(
3306
+ `Failed to save note before deposit: ${error instanceof Error ? error.message : String(error)}. For your safety, the deposit has been aborted. Your funds are safe.`
3307
+ );
3308
+ }
3309
+ }
3310
+ options?.onProgress?.("note_saved", {
3311
+ message: "Note saved. Proceeding with on-chain deposit..."
3312
+ });
3313
+ }
3314
+ const payerPubkey = this.getPublicKey();
3315
+ const balance = await connection.getBalance(payerPubkey);
3316
+ const requiredAmount = note.amount + 5e3;
3317
+ if (balance < requiredAmount) {
3318
+ throw new Error(
3319
+ `Insufficient balance. Required: ${requiredAmount} lamports (${note.amount} + fees), Available: ${balance} lamports`
3320
+ );
3321
+ }
3322
+ const commitmentBytes = hexToBytes(note.commitment);
3323
+ const programId = this.config.programId || CLOAK_PROGRAM_ID;
3324
+ const depositIx = createDepositInstruction({
3325
+ programId,
3326
+ payer: payerPubkey,
3327
+ pool: this.config.poolAddress,
3328
+ merkleTree: this.config.merkleTreeAddress,
3329
+ amount: note.amount,
3330
+ commitment: commitmentBytes
3331
+ });
3332
+ const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
3333
+ const priorityFee = options?.priorityFee ?? 1e4;
3334
+ const cuPriceIx = import_web35.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priorityFee });
3335
+ const dataSizeLimit = options?.loadedAccountsDataSizeLimit ?? 256 * 1024;
3336
+ const dataSizeLimitIx = dataSizeLimit > 0 ? createSetLoadedAccountsDataSizeLimitInstruction(dataSizeLimit) : null;
3337
+ let computeUnits;
3338
+ if (options?.optimizeCU && this.keypair) {
3339
+ options?.onProgress?.("simulating", { message: "Simulating transaction for optimal CU..." });
3340
+ const simCuLimitIx = import_web35.ComputeBudgetProgram.setComputeUnitLimit({ units: 2e5 });
3341
+ const simTransaction = new import_web35.Transaction({
3342
+ feePayer: payerPubkey,
3343
+ recentBlockhash: blockhash
3344
+ }).add(simCuLimitIx).add(cuPriceIx);
3345
+ if (dataSizeLimitIx) {
3346
+ simTransaction.add(dataSizeLimitIx);
3347
+ }
3348
+ simTransaction.add(depositIx);
3349
+ try {
3350
+ const simulation = await connection.simulateTransaction(simTransaction, [this.keypair]);
3351
+ if (simulation.value.err) {
3352
+ console.warn("Simulation failed, using default CU:", simulation.value.err);
3353
+ computeUnits = 4e4;
3354
+ } else {
3355
+ const simulatedCU = simulation.value.unitsConsumed ?? 3e4;
3356
+ computeUnits = Math.ceil(simulatedCU * 1.2);
3357
+ console.log(`CU optimization: ${simulatedCU} simulated \u2192 ${computeUnits} limit`);
3358
+ }
3359
+ } catch (simError) {
3360
+ console.warn("Simulation RPC error, using default CU:", simError);
3361
+ computeUnits = 4e4;
3362
+ }
3363
+ } else if (options?.optimizeCU && this.wallet) {
3364
+ console.warn("CU optimization via simulation not available in wallet mode (would require double signing). Using default.");
3365
+ computeUnits = options?.computeUnits ?? 4e4;
3366
+ } else {
3367
+ computeUnits = options?.computeUnits ?? 4e4;
3368
+ }
3369
+ const cuLimitIx = import_web35.ComputeBudgetProgram.setComputeUnitLimit({ units: computeUnits });
3370
+ const transaction = new import_web35.Transaction({
3371
+ feePayer: payerPubkey,
3372
+ recentBlockhash: blockhash
3373
+ }).add(cuLimitIx).add(cuPriceIx);
3374
+ if (dataSizeLimitIx) {
3375
+ transaction.add(dataSizeLimitIx);
3376
+ }
3377
+ transaction.add(depositIx);
3378
+ if (!transaction.feePayer) {
3379
+ throw new Error("Transaction feePayer is not set");
3380
+ }
3381
+ if (!transaction.recentBlockhash) {
3382
+ throw new Error("Transaction recentBlockhash is not set");
3383
+ }
3384
+ let signature;
3385
+ if (this.wallet) {
3386
+ if (!this.wallet.publicKey) {
3387
+ throw new Error("Wallet not connected - publicKey is null");
3388
+ }
3389
+ if (this.wallet.signTransaction) {
3390
+ try {
3391
+ const signedTransaction = await this.wallet.signTransaction(transaction);
3392
+ const rawTransaction = signedTransaction.serialize();
3393
+ signature = await connection.sendRawTransaction(rawTransaction, {
3394
+ skipPreflight: options?.skipPreflight || false,
3395
+ preflightCommitment: "confirmed",
3396
+ maxRetries: 3
3397
+ });
3398
+ } catch (signError) {
3399
+ if (this.wallet.sendTransaction) {
3400
+ signature = await this.wallet.sendTransaction(transaction, connection, {
3401
+ skipPreflight: options?.skipPreflight || false,
3402
+ preflightCommitment: "confirmed",
3403
+ maxRetries: 3
3404
+ });
3405
+ } else {
3406
+ throw signError;
3407
+ }
3408
+ }
3409
+ } else if (this.wallet.sendTransaction) {
3410
+ signature = await this.wallet.sendTransaction(transaction, connection, {
3411
+ skipPreflight: options?.skipPreflight || false,
3412
+ preflightCommitment: "confirmed",
3413
+ maxRetries: 3
3414
+ });
3415
+ } else {
3416
+ throw new Error("Wallet adapter must provide either sendTransaction or signTransaction");
3417
+ }
3418
+ } else if (this.keypair) {
3419
+ signature = await connection.sendTransaction(transaction, [this.keypair], {
3420
+ skipPreflight: options?.skipPreflight || false,
3421
+ preflightCommitment: "confirmed",
3422
+ maxRetries: 3
3423
+ });
3424
+ } else {
3425
+ throw new Error("No signing method available - provide keypair or wallet");
3426
+ }
3427
+ const confirmation = await connection.confirmTransaction({
3428
+ signature,
3429
+ blockhash,
3430
+ lastValidBlockHeight
3431
+ });
3432
+ if (confirmation.value.err) {
3433
+ throw new Error(
3434
+ `Transaction failed: ${JSON.stringify(confirmation.value.err)}`
3435
+ );
3436
+ }
3437
+ const txDetails = await connection.getTransaction(signature, {
3438
+ commitment: "confirmed",
3439
+ maxSupportedTransactionVersion: 0
3440
+ });
3441
+ const depositSlot = txDetails?.slot ?? 0;
3442
+ let leafIndex = null;
3443
+ let root = null;
3444
+ try {
3445
+ if (txDetails?.meta?.logMessages) {
3446
+ for (const log of txDetails.meta.logMessages) {
3447
+ if (log.includes("Program data:")) {
3448
+ try {
3449
+ const parts = log.split("Program data:");
3450
+ if (parts.length > 1) {
3451
+ const base64Data = parts[1].trim();
3452
+ const eventData = Buffer.from(base64Data, "base64");
3453
+ if (eventData.length >= 81 && eventData[0] === 1) {
3454
+ const parsedLeafIndex = Number(eventData.readBigUInt64LE(1));
3455
+ const loggedCommitment = eventData.slice(9, 41);
3456
+ if (Buffer.compare(loggedCommitment, Buffer.from(commitmentBytes)) === 0) {
3457
+ leafIndex = parsedLeafIndex;
3458
+ const loggedRoot = eventData.slice(49, 81);
3459
+ root = Buffer.from(loggedRoot).toString("hex");
3460
+ break;
3461
+ }
3462
+ }
3463
+ }
3464
+ } catch (e) {
3465
+ continue;
3466
+ }
3467
+ }
3468
+ }
3469
+ }
3470
+ } catch (e) {
3471
+ }
3472
+ if (leafIndex === null) {
3473
+ const programId2 = this.config.programId || CLOAK_PROGRAM_ID;
3474
+ const mintForSOL = NATIVE_SOL_MINT;
3475
+ const { merkleTree } = getShieldPoolPDAs(programId2, mintForSOL);
3476
+ const merkleTreeAccount = await connection.getAccountInfo(merkleTree);
3477
+ if (!merkleTreeAccount) {
3478
+ throw new Error("Failed to fetch merkle tree account");
3479
+ }
3480
+ const nextIndex = Number(merkleTreeAccount.data.readBigUInt64LE(32));
3481
+ leafIndex = nextIndex - 1;
3482
+ const rootBytes = merkleTreeAccount.data.slice(1064, 1096);
3483
+ root = Buffer.from(rootBytes).toString("hex");
3484
+ }
3485
+ if (leafIndex === null || root === null) {
3486
+ throw new Error("Failed to get leaf index and root from transaction logs or on-chain state");
3487
+ }
3488
+ let merkleProof;
3489
+ try {
3490
+ const programId2 = this.config.programId || CLOAK_PROGRAM_ID;
3491
+ const mintForSOL = NATIVE_SOL_MINT;
3492
+ const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId2, mintForSOL);
3493
+ const chainProof = await computeProofFromChain(connection, merkleTreePDA, leafIndex);
3494
+ merkleProof = {
3495
+ pathElements: chainProof.pathElements,
3496
+ pathIndices: chainProof.pathIndices
3497
+ };
3498
+ root = chainProof.root;
3499
+ } catch (proofError) {
3500
+ console.warn("[SDK] Could not compute proof from chain, will fetch at withdrawal:", proofError);
3501
+ }
3502
+ const updatedNote = updateNoteWithDeposit(note, {
3503
+ signature,
3504
+ slot: depositSlot,
3505
+ leafIndex,
3506
+ root,
3507
+ merkleProof
3508
+ });
3509
+ await this.storage.updateNote(note.commitment, {
3510
+ depositSignature: signature,
3511
+ depositSlot,
3512
+ leafIndex,
3513
+ root,
3514
+ merkleProof
3515
+ });
3516
+ return {
3517
+ note: updatedNote,
3518
+ signature,
3519
+ leafIndex,
3520
+ root
3521
+ };
3522
+ } catch (error) {
3523
+ throw this.wrapError(error, "Deposit failed");
3524
+ }
3525
+ }
3526
+ /**
3527
+ * Private transfer with up to 5 recipients
3528
+ *
3529
+ * Handles the complete private transfer flow:
3530
+ * 1. If note is not deposited, deposits it first and waits for confirmation
3531
+ * 2. Generates a zero-knowledge proof
3532
+ * 3. Submits the withdrawal via relay service to recipients
3533
+ *
3534
+ * This is the main method for performing private transfers - it handles everything!
3535
+ *
3536
+ * @param connection - Solana connection (required for deposit if not already deposited)
3537
+ * @param payer - Payer wallet (required for deposit if not already deposited)
3538
+ * @param note - Note to spend (can be deposited or not)
3539
+ * @param recipients - Array of 1-5 recipients with amounts
3540
+ * @param options - Optional configuration
3541
+ * @returns Transfer result with signature and outputs
3542
+ *
3543
+ * @example
3544
+ * ```typescript
3545
+ * // Create a note (not deposited yet)
3546
+ * const note = client.generateNote(1_000_000_000);
3547
+ *
3548
+ * // privateTransfer handles the full flow: deposit + withdraw
3549
+ * const result = await client.privateTransfer(
3550
+ * connection,
3551
+ * wallet,
3552
+ * note,
3553
+ * [
3554
+ * { recipient: new PublicKey("..."), amount: 500_000_000 },
3555
+ * { recipient: new PublicKey("..."), amount: 492_500_000 }
3556
+ * ],
3557
+ * {
3558
+ * relayFeeBps: 50, // 0.5%
3559
+ * onProgress: (status) => console.log(status),
3560
+ * onProofProgress: (pct) => console.log(`Proof: ${pct}%`)
3561
+ * }
3562
+ * );
3563
+ * console.log(`Success! TX: ${result.signature}`);
3564
+ * ```
3565
+ */
3566
+ async privateTransfer(connection, note, recipients, options) {
3567
+ if (!isWithdrawable(note)) {
3568
+ const depositResult = await this.deposit(connection, note, {
3569
+ skipPreflight: false
3570
+ });
3571
+ note = depositResult.note;
3572
+ }
3573
+ const protocolFee = note.amount - getDistributableAmount(note.amount);
3574
+ const feeBps = Math.ceil(protocolFee * 1e4 / note.amount);
3575
+ const distributableAmount = getDistributableAmount(note.amount);
3576
+ validateTransfers(recipients, distributableAmount);
3577
+ if (!note.leafIndex && note.leafIndex !== 0) {
3578
+ throw new Error("Note must have a leaf index (note must be deposited)");
3579
+ }
3580
+ if (!isValidHex(note.r, 32)) {
3581
+ throw new Error("Note r must be 64 hex characters (32 bytes)");
3582
+ }
3583
+ if (!isValidHex(note.sk_spend, 32)) {
3584
+ throw new Error("Note sk_spend must be 64 hex characters (32 bytes)");
3585
+ }
3586
+ const nullifier = await computeNullifierAsync(note.sk_spend, note.leafIndex);
3587
+ const nullifierHex = nullifier.toString(16).padStart(64, "0");
3588
+ const outputsHash = await computeOutputsHashAsync(recipients);
3589
+ const outputsHashHex = outputsHash.toString(16).padStart(64, "0");
3590
+ const circuitsPath2 = await getDefaultCircuitsPath();
3591
+ const useDirectProof = await areCircuitsAvailable(circuitsPath2);
3592
+ if (!useDirectProof) {
3593
+ throw new Error(
3594
+ `Circuits not available at ${circuitsPath2}. Make sure circuits are built and accessible. Circuit artifacts are pinned to S3 and must be reachable from this environment.`
3595
+ );
3596
+ }
3597
+ const MAX_PROOF_RETRIES = 3;
3598
+ let lastError = null;
3599
+ for (let attempt = 0; attempt < MAX_PROOF_RETRIES; attempt++) {
3600
+ try {
3601
+ if (attempt > 0) {
3602
+ options?.onProgress?.(`proof_refresh_${attempt + 1}`);
3603
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
3604
+ }
3605
+ let merkleProof;
3606
+ let merkleRoot;
3607
+ const hasStoredProof = note.merkleProof && note.root && note.merkleProof.pathElements && note.merkleProof.pathElements.length > 0;
3608
+ if (attempt === 0 && hasStoredProof) {
3609
+ merkleProof = note.merkleProof;
3610
+ merkleRoot = note.root;
3611
+ } else {
3612
+ const programId = this.config.programId || CLOAK_PROGRAM_ID;
3613
+ const mintForSOL = NATIVE_SOL_MINT;
3614
+ const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId, mintForSOL);
3615
+ const chainProof = await computeProofFromChain(
3616
+ connection,
3617
+ merkleTreePDA,
3618
+ note.leafIndex
3619
+ );
3620
+ merkleProof = {
3621
+ pathElements: chainProof.pathElements,
3622
+ pathIndices: chainProof.pathIndices,
3623
+ root: chainProof.root
3624
+ };
3625
+ merkleRoot = chainProof.root;
3626
+ note.merkleProof = merkleProof;
3627
+ note.root = merkleRoot;
3628
+ }
3629
+ if (!merkleProof || !merkleRoot) {
3630
+ throw new Error("Failed to get Merkle proof from chain");
3631
+ }
3632
+ if (!merkleProof.pathElements || merkleProof.pathElements.length === 0) {
3633
+ throw new Error("Merkle proof is invalid: missing path elements");
3634
+ }
3635
+ if (merkleProof.pathElements.length !== merkleProof.pathIndices.length) {
3636
+ throw new Error("Merkle proof is invalid: path elements and indices length mismatch");
3637
+ }
3638
+ for (let i = 0; i < merkleProof.pathIndices.length; i++) {
3639
+ const idx = merkleProof.pathIndices[i];
3640
+ if (idx !== 0 && idx !== 1) {
3641
+ throw new Error(`Merkle proof path index at position ${i} must be 0 or 1, got ${idx}`);
3642
+ }
3643
+ }
3644
+ if (!isValidHex(merkleRoot, 32)) {
3645
+ throw new Error("Merkle root must be 64 hex characters (32 bytes)");
3646
+ }
3647
+ for (let i = 0; i < merkleProof.pathElements.length; i++) {
3648
+ const element = merkleProof.pathElements[i];
3649
+ if (typeof element !== "string" || !isValidHex(element, 32)) {
3650
+ throw new Error(`Merkle proof path element at position ${i} must be 64 hex characters (32 bytes)`);
3651
+ }
3652
+ }
3653
+ const sk_spend_bigint = BigInt("0x" + note.sk_spend);
3654
+ const r_bigint = BigInt("0x" + note.r);
3655
+ const root_bigint = BigInt("0x" + merkleRoot);
3656
+ const nullifier_bigint = BigInt("0x" + nullifierHex);
3657
+ const outputs_hash_bigint = BigInt("0x" + outputsHashHex);
3658
+ const amount_bigint = BigInt(note.amount);
3659
+ const pathElements = merkleProof.pathElements.map((p) => BigInt("0x" + p));
3660
+ const outAddr = [];
3661
+ for (let i = 0; i < 5; i++) {
3662
+ if (i < recipients.length) {
3663
+ const [lo, hi] = pubkeyToLimbs(recipients[i].recipient.toBytes());
3664
+ outAddr.push([lo, hi]);
3665
+ } else {
3666
+ outAddr.push([0n, 0n]);
3667
+ }
3668
+ }
3669
+ const t = amount_bigint * 3n;
3670
+ const varFee = t / 1000n;
3671
+ const rem = t % 1000n;
3672
+ const outAmount = [];
3673
+ const outFlags = [];
3674
+ for (let i = 0; i < 5; i++) {
3675
+ if (i < recipients.length) {
3676
+ outAmount.push(BigInt(recipients[i].amount));
3677
+ outFlags.push(1);
3678
+ } else {
3679
+ outAmount.push(0n);
3680
+ outFlags.push(0);
3681
+ }
3682
+ }
3683
+ const sk = splitTo2Limbs(sk_spend_bigint);
3684
+ const r = splitTo2Limbs(r_bigint);
3685
+ if (attempt === 0) {
3686
+ const computedCommitment = await computeCommitment(amount_bigint, r_bigint, sk_spend_bigint);
3687
+ const noteCommitment = BigInt("0x" + note.commitment);
3688
+ if (computedCommitment !== noteCommitment) {
3689
+ throw new Error(
3690
+ `Commitment mismatch! Computed: ${computedCommitment.toString(16)}, Note: ${note.commitment}. This means the note's sk_spend, r, or amount doesn't match what was deposited.`
3691
+ );
3692
+ }
3693
+ }
3694
+ const proofInputs = {
3695
+ root: root_bigint,
3696
+ nullifier: nullifier_bigint,
3697
+ outputs_hash: outputs_hash_bigint,
3698
+ public_amount: amount_bigint,
3699
+ amount: amount_bigint,
3700
+ leaf_index: BigInt(note.leafIndex),
3701
+ sk,
3702
+ r,
3703
+ pathElements,
3704
+ pathIndices: merkleProof.pathIndices,
3705
+ num_outputs: recipients.length,
3706
+ out_addr: outAddr,
3707
+ out_amount: outAmount,
3708
+ out_flags: outFlags,
3709
+ var_fee: varFee,
3710
+ rem
3711
+ };
3712
+ options?.onProgress?.("proof_generating");
3713
+ const proofResult = await generateWithdrawRegularProof(proofInputs, circuitsPath2);
3714
+ options?.onProgress?.("proof_complete");
3715
+ const proofHex = Buffer.from(proofResult.proofBytes).toString("hex");
3716
+ const finalPublicInputs = {
3717
+ root: merkleRoot,
3718
+ nf: nullifierHex,
3719
+ outputs_hash: outputsHashHex,
3720
+ amount: note.amount
3721
+ };
3722
+ const metadataBundle = options?.metadataBundle;
3723
+ const signature = await this.relay.submitWithdraw(
3724
+ {
3725
+ proof: proofHex,
3726
+ publicInputs: finalPublicInputs,
3727
+ outputs: recipients.map((r2) => ({
3728
+ recipient: r2.recipient.toBase58(),
3729
+ amount: r2.amount
3730
+ })),
3731
+ feeBps,
3732
+ // Use calculated protocol fee BPS
3733
+ metadataBundle
3734
+ },
3735
+ options?.onProgress
3736
+ );
3737
+ return {
3738
+ signature,
3739
+ outputs: recipients.map((r2) => ({
3740
+ recipient: r2.recipient.toBase58(),
3741
+ amount: r2.amount
3742
+ })),
3743
+ nullifier: nullifierHex,
3744
+ root: merkleRoot
3745
+ };
3746
+ } catch (error) {
3747
+ lastError = error instanceof Error ? error : new Error(String(error));
3748
+ if (error instanceof RootNotFoundError) {
3749
+ console.warn(
3750
+ `[Cloak SDK] Merkle root expired (attempt ${attempt + 1}/${MAX_PROOF_RETRIES}). Regenerating proof with fresh root...`
3751
+ );
3752
+ continue;
3753
+ }
3754
+ throw error;
3755
+ }
3756
+ }
3757
+ throw new Error(
3758
+ `Withdrawal failed after ${MAX_PROOF_RETRIES} proof refresh attempts. The Merkle tree is updating too rapidly. Last error: ${lastError?.message}`
2459
3759
  );
2460
3760
  }
2461
- /** True when the SDK was constructed with a wallet adapter (browser). */
2462
- isWalletMode() {
2463
- return !!this.wallet && !this.keypair;
3761
+ /**
3762
+ * Withdraw to a single recipient
3763
+ *
3764
+ * Convenience method for withdrawing to one address.
3765
+ * Handles the complete flow: deposits if needed, then withdraws.
3766
+ *
3767
+ * @param connection - Solana connection
3768
+ * @param payer - Payer wallet
3769
+ * @param note - Note to spend
3770
+ * @param recipient - Recipient address
3771
+ * @param options - Optional configuration
3772
+ * @returns Transfer result
3773
+ *
3774
+ * @example
3775
+ * ```typescript
3776
+ * const note = client.generateNote(1_000_000_000);
3777
+ * const result = await client.withdraw(
3778
+ * connection,
3779
+ * wallet,
3780
+ * note,
3781
+ * new PublicKey("..."),
3782
+ * { withdrawAll: true }
3783
+ * );
3784
+ * ```
3785
+ */
3786
+ async withdraw(connection, note, recipient, options) {
3787
+ const withdrawAll = options?.withdrawAll ?? true;
3788
+ const amount = withdrawAll ? getDistributableAmount(note.amount) : options?.amount || note.amount;
3789
+ if (!withdrawAll && !options?.amount) {
3790
+ throw new Error("Must specify amount or set withdrawAll: true");
3791
+ }
3792
+ return this.privateTransfer(
3793
+ connection,
3794
+ note,
3795
+ [{ recipient, amount }],
3796
+ options
3797
+ );
3798
+ }
3799
+ /**
3800
+ * Send SOL privately to multiple recipients
3801
+ *
3802
+ * Convenience method that wraps privateTransfer with a simpler API.
3803
+ * Handles the complete flow: deposits if needed, then sends to recipients.
3804
+ *
3805
+ * @param connection - Solana connection
3806
+ * @param note - Note to spend
3807
+ * @param recipients - Array of 1-5 recipients with amounts
3808
+ * @param options - Optional configuration
3809
+ * @returns Transfer result
3810
+ *
3811
+ * @example
3812
+ * ```typescript
3813
+ * const note = client.generateNote(1_000_000_000);
3814
+ * const result = await client.send(
3815
+ * connection,
3816
+ * note,
3817
+ * [
3818
+ * { recipient: new PublicKey("..."), amount: 500_000_000 },
3819
+ * { recipient: new PublicKey("..."), amount: 492_500_000 }
3820
+ * ]
3821
+ * );
3822
+ * ```
3823
+ */
3824
+ async send(connection, note, recipients, options) {
3825
+ return this.privateTransfer(connection, note, recipients, options);
3826
+ }
3827
+ /**
3828
+ * Swap SOL for tokens privately
3829
+ *
3830
+ * Withdraws SOL from a note and swaps it for tokens via the relay service.
3831
+ * Handles the complete flow: deposits if needed, generates proof, and submits swap.
3832
+ *
3833
+ * @param connection - Solana connection
3834
+ * @param note - Note to spend
3835
+ * @param recipient - Recipient address (will receive tokens)
3836
+ * @param options - Swap configuration
3837
+ * @returns Swap result with transaction signature
3838
+ *
3839
+ * @example
3840
+ * ```typescript
3841
+ * const note = client.generateNote(1_000_000_000);
3842
+ * const result = await client.swap(
3843
+ * connection,
3844
+ * note,
3845
+ * new PublicKey("..."), // recipient
3846
+ * {
3847
+ * outputMint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC
3848
+ * slippageBps: 100, // 1%
3849
+ * getQuote: async (amount, mint, slippage) => {
3850
+ * // Fetch quote from your swap API
3851
+ * const quote = await fetchSwapQuote(amount, mint, slippage);
3852
+ * return {
3853
+ * outAmount: quote.outAmount,
3854
+ * minOutputAmount: quote.minOutputAmount
3855
+ * };
3856
+ * }
3857
+ * }
3858
+ * );
3859
+ * ```
3860
+ */
3861
+ async swap(connection, note, recipient, options) {
3862
+ try {
3863
+ if (!isWithdrawable(note)) {
3864
+ const depositResult = await this.deposit(connection, note, {
3865
+ skipPreflight: false
3866
+ });
3867
+ note = depositResult.note;
3868
+ }
3869
+ const variableFee = Math.floor(note.amount * 3 / 1e3);
3870
+ const feeBps = note.amount === 0 ? 0 : Math.min(Math.floor((variableFee * 1e4 + note.amount - 1) / note.amount), 65535);
3871
+ const withdrawAmountLamports = getDistributableAmount(note.amount);
3872
+ if (withdrawAmountLamports <= 0) {
3873
+ throw new Error("Amount too small after fees");
3874
+ }
3875
+ let minOutputAmount;
3876
+ if (options.minOutputAmount !== void 0) {
3877
+ minOutputAmount = options.minOutputAmount;
3878
+ } else if (options.getQuote) {
3879
+ const quote = await options.getQuote(
3880
+ withdrawAmountLamports,
3881
+ options.outputMint,
3882
+ options.slippageBps || 100
3883
+ );
3884
+ minOutputAmount = quote.minOutputAmount;
3885
+ } else {
3886
+ throw new Error(
3887
+ "Must provide either minOutputAmount or getQuote function"
3888
+ );
3889
+ }
3890
+ let recipientAta;
3891
+ if (options.recipientAta) {
3892
+ recipientAta = new import_web35.PublicKey(options.recipientAta);
3893
+ } else {
3894
+ try {
3895
+ const splTokenModule = await import("@solana/spl-token");
3896
+ const getAssociatedTokenAddress = splTokenModule.getAssociatedTokenAddress;
3897
+ if (!getAssociatedTokenAddress) {
3898
+ throw new Error("getAssociatedTokenAddress not found");
3899
+ }
3900
+ const outputMint2 = new import_web35.PublicKey(options.outputMint);
3901
+ recipientAta = await getAssociatedTokenAddress(outputMint2, recipient);
3902
+ } catch (error) {
3903
+ throw new Error(
3904
+ `Failed to get associated token account: ${error instanceof Error ? error.message : String(error)}. Please install @solana/spl-token or provide recipientAta in options.`
3905
+ );
3906
+ }
3907
+ }
3908
+ if (!note.leafIndex && note.leafIndex !== 0) {
3909
+ throw new Error("Note must have a leaf index (note must be deposited)");
3910
+ }
3911
+ const nullifier = await computeNullifierAsync(note.sk_spend, note.leafIndex);
3912
+ const nullifierHex = nullifier.toString(16).padStart(64, "0");
3913
+ const inputMint = new import_web35.PublicKey("11111111111111111111111111111111");
3914
+ const outputMint = new import_web35.PublicKey(options.outputMint);
3915
+ const outputsHash = await computeSwapOutputsHashAsync(
3916
+ inputMint,
3917
+ outputMint,
3918
+ recipientAta,
3919
+ minOutputAmount,
3920
+ note.amount
3921
+ );
3922
+ const outputsHashHex = outputsHash.toString(16).padStart(64, "0");
3923
+ const circuitsPath2 = await getDefaultCircuitsPath();
3924
+ const useDirectProof = await areCircuitsAvailable(circuitsPath2);
3925
+ if (!useDirectProof) {
3926
+ throw new Error(
3927
+ `Circuits not available at ${circuitsPath2}. Make sure circuits are built and accessible. Circuit artifacts are pinned to S3 and must be reachable from this environment.`
3928
+ );
3929
+ }
3930
+ const MAX_PROOF_RETRIES = 3;
3931
+ let lastError = null;
3932
+ for (let attempt = 0; attempt < MAX_PROOF_RETRIES; attempt++) {
3933
+ try {
3934
+ if (attempt > 0) {
3935
+ options?.onProgress?.(`proof_refresh_${attempt + 1}`);
3936
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
3937
+ }
3938
+ const programId = this.config.programId || CLOAK_PROGRAM_ID;
3939
+ const mintForSOL = NATIVE_SOL_MINT;
3940
+ const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId, mintForSOL);
3941
+ const chainProof = await computeProofFromChain(connection, merkleTreePDA, note.leafIndex);
3942
+ const merkleProof = {
3943
+ pathElements: chainProof.pathElements,
3944
+ pathIndices: chainProof.pathIndices
3945
+ };
3946
+ const merkleRoot = chainProof.root;
3947
+ if (!merkleRoot) {
3948
+ throw new Error("Failed to get Merkle root from chain");
3949
+ }
3950
+ if (!merkleProof.pathElements || merkleProof.pathElements.length === 0) {
3951
+ throw new Error("Merkle proof is invalid: missing path elements");
3952
+ }
3953
+ const sk_spend_bigint = BigInt("0x" + note.sk_spend);
3954
+ const r_bigint = BigInt("0x" + note.r);
3955
+ const root_bigint = BigInt("0x" + merkleRoot);
3956
+ const nullifier_bigint = BigInt("0x" + nullifierHex);
3957
+ const outputs_hash_bigint = BigInt("0x" + outputsHashHex);
3958
+ const amount_bigint = BigInt(note.amount);
3959
+ const pathElements = merkleProof.pathElements.map((p) => BigInt("0x" + p));
3960
+ const inputMintLimbs = pubkeyToLimbs(inputMint.toBytes());
3961
+ const outputMintLimbs = pubkeyToLimbs(outputMint.toBytes());
3962
+ const recipientAtaLimbs = pubkeyToLimbs(recipientAta.toBytes());
3963
+ if (attempt === 0) {
3964
+ const computedCommitment = await computeCommitment(amount_bigint, r_bigint, sk_spend_bigint);
3965
+ const noteCommitment = BigInt("0x" + note.commitment);
3966
+ if (computedCommitment !== noteCommitment) {
3967
+ throw new Error(
3968
+ `Commitment mismatch! Computed: ${computedCommitment.toString(16)}, Note: ${note.commitment}. This means the note's sk_spend, r, or amount doesn't match what was deposited.`
3969
+ );
3970
+ }
3971
+ }
3972
+ const t = amount_bigint * 3n;
3973
+ const varFee = t / 1000n;
3974
+ const rem = t % 1000n;
3975
+ const proofInputs = {
3976
+ sk_spend: sk_spend_bigint,
3977
+ r: r_bigint,
3978
+ amount: amount_bigint,
3979
+ leaf_index: BigInt(note.leafIndex),
3980
+ path_elements: pathElements,
3981
+ path_indices: merkleProof.pathIndices,
3982
+ root: root_bigint,
3983
+ nullifier: nullifier_bigint,
3984
+ outputs_hash: outputs_hash_bigint,
3985
+ public_amount: amount_bigint,
3986
+ input_mint: inputMintLimbs,
3987
+ output_mint: outputMintLimbs,
3988
+ recipient_ata: recipientAtaLimbs,
3989
+ min_output_amount: BigInt(minOutputAmount),
3990
+ var_fee: varFee,
3991
+ rem
3992
+ };
3993
+ options?.onProgress?.("proof_generating");
3994
+ const proofResult = await generateWithdrawSwapProof(proofInputs, circuitsPath2);
3995
+ options?.onProgress?.("proof_complete");
3996
+ const proofHex = Buffer.from(proofResult.proofBytes).toString("hex");
3997
+ const finalPublicInputs = {
3998
+ root: merkleRoot,
3999
+ nf: nullifierHex,
4000
+ outputs_hash: outputsHashHex,
4001
+ amount: note.amount
4002
+ };
4003
+ const metadataBundle = options?.metadataBundle;
4004
+ const signature = await this.relay.submitSwap(
4005
+ {
4006
+ proof: proofHex,
4007
+ publicInputs: finalPublicInputs,
4008
+ outputs: [
4009
+ {
4010
+ recipient: recipient.toBase58(),
4011
+ amount: withdrawAmountLamports
4012
+ }
4013
+ ],
4014
+ feeBps,
4015
+ swap: {
4016
+ output_mint: options.outputMint,
4017
+ slippage_bps: options.slippageBps || 100,
4018
+ min_output_amount: minOutputAmount
4019
+ },
4020
+ metadataBundle
4021
+ },
4022
+ options.onProgress
4023
+ );
4024
+ return {
4025
+ signature,
4026
+ outputs: [
4027
+ {
4028
+ recipient: recipient.toBase58(),
4029
+ amount: withdrawAmountLamports
4030
+ }
4031
+ ],
4032
+ nullifier: nullifierHex,
4033
+ root: merkleRoot,
4034
+ outputMint: options.outputMint,
4035
+ minOutputAmount
4036
+ };
4037
+ } catch (error) {
4038
+ lastError = error instanceof Error ? error : new Error(String(error));
4039
+ if (error instanceof RootNotFoundError) {
4040
+ console.warn(
4041
+ `[Cloak SDK] Merkle root expired (attempt ${attempt + 1}/${MAX_PROOF_RETRIES}). Regenerating swap proof with fresh root...`
4042
+ );
4043
+ continue;
4044
+ }
4045
+ throw error;
4046
+ }
4047
+ }
4048
+ throw new Error(
4049
+ `Swap failed after ${MAX_PROOF_RETRIES} proof refresh attempts. The Merkle tree is updating too rapidly. Last error: ${lastError?.message}`
4050
+ );
4051
+ } catch (error) {
4052
+ throw this.wrapError(error, "Swap failed");
4053
+ }
4054
+ }
4055
+ /**
4056
+ * Generate a new note without depositing
4057
+ *
4058
+ * @param amountLamports - Amount for the note
4059
+ * @param useWalletKeys - Whether to use wallet keys (v2.0 recommended)
4060
+ * @returns New note (not yet deposited)
4061
+ */
4062
+ async generateNote(amountLamports, useWalletKeys = false) {
4063
+ if (useWalletKeys && this.cloakKeys) {
4064
+ return await generateNoteFromWallet(amountLamports, this.cloakKeys, this.config.network);
4065
+ } else if (useWalletKeys) {
4066
+ const keys = generateCloakKeys();
4067
+ this.cloakKeys = keys;
4068
+ const result = this.storage.saveKeys(keys);
4069
+ if (result instanceof Promise) {
4070
+ result.catch(() => {
4071
+ });
4072
+ }
4073
+ return await generateNoteFromWallet(amountLamports, keys, this.config.network);
4074
+ }
4075
+ return await generateNote(amountLamports, this.config.network);
4076
+ }
4077
+ /**
4078
+ * Parse a note from JSON string
4079
+ *
4080
+ * @param jsonString - JSON representation
4081
+ * @returns Parsed note
4082
+ */
4083
+ parseNote(jsonString) {
4084
+ return parseNote2(jsonString);
4085
+ }
4086
+ /**
4087
+ * Export a note to JSON string
4088
+ *
4089
+ * @param note - Note to export
4090
+ * @param pretty - Format with indentation
4091
+ * @returns JSON string
4092
+ */
4093
+ exportNote(note, pretty = false) {
4094
+ return pretty ? JSON.stringify(note, null, 2) : JSON.stringify(note);
2464
4095
  }
2465
4096
  /**
2466
- * Compute a Merkle proof for `leafIndex` from on-chain state. No relay /
2467
- * indexer round-trip required.
4097
+ * Check if a note is ready for withdrawal
4098
+ *
4099
+ * @param note - Note to check
4100
+ * @returns True if withdrawable
4101
+ */
4102
+ isWithdrawable(note) {
4103
+ return isWithdrawable(note);
4104
+ }
4105
+ /**
4106
+ * Get Merkle proof for a leaf index directly from on-chain state
4107
+ *
4108
+ * @param connection - Solana connection
4109
+ * @param leafIndex - Leaf index in tree
4110
+ * @returns Merkle proof computed from on-chain data
2468
4111
  */
2469
4112
  async getMerkleProof(connection, leafIndex) {
2470
4113
  const programId = this.config.programId || CLOAK_PROGRAM_ID;
2471
- const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId, NATIVE_SOL_MINT);
4114
+ const mintForSOL = NATIVE_SOL_MINT;
4115
+ const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId, mintForSOL);
2472
4116
  const chainProof = await computeProofFromChain(connection, merkleTreePDA, leafIndex);
2473
4117
  return {
2474
4118
  pathElements: chainProof.pathElements,
@@ -2476,20 +4120,72 @@ var CloakSDK = class {
2476
4120
  root: chainProof.root
2477
4121
  };
2478
4122
  }
2479
- /** Read the current SOL-pool Merkle root from on-chain state. */
4123
+ /**
4124
+ * Get current Merkle root directly from on-chain state
4125
+ *
4126
+ * @param connection - Solana connection
4127
+ * @returns Current root hash from on-chain tree
4128
+ */
2480
4129
  async getCurrentRoot(connection) {
2481
4130
  const programId = this.config.programId || CLOAK_PROGRAM_ID;
2482
- const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId, NATIVE_SOL_MINT);
4131
+ const mintForSOL = NATIVE_SOL_MINT;
4132
+ const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId, mintForSOL);
2483
4133
  const state = await readMerkleTreeState(connection, merkleTreePDA);
2484
4134
  return state.root;
2485
4135
  }
2486
- /** Poll relay for the status of a previously-submitted request. */
4136
+ /**
4137
+ * Get transaction status from relay service
4138
+ *
4139
+ * @param requestId - Request ID from previous submission
4140
+ * @returns Current status
4141
+ */
2487
4142
  async getTransactionStatus(requestId) {
2488
4143
  return this.relay.getStatus(requestId);
2489
4144
  }
2490
- /** Import wallet keys from JSON; persists to the configured storage adapter. */
4145
+ /**
4146
+ * Fetch and decrypt this user's transaction metadata history.
4147
+ * DEPRECATED: This functionality is no longer supported
4148
+ */
4149
+ async getTransactionMetadata(_options) {
4150
+ throw new Error("getTransactionMetadata is deprecated - use compliance export API instead");
4151
+ }
4152
+ /**
4153
+ * Load all notes from storage
4154
+ *
4155
+ * @returns Array of saved notes
4156
+ */
4157
+ async loadNotes() {
4158
+ const notes = this.storage.loadAllNotes();
4159
+ return Array.isArray(notes) ? notes : await notes;
4160
+ }
4161
+ /**
4162
+ * Save a note to storage
4163
+ *
4164
+ * @param note - Note to save
4165
+ */
4166
+ async saveNote(note) {
4167
+ const result = this.storage.saveNote(note);
4168
+ if (result instanceof Promise) {
4169
+ await result;
4170
+ }
4171
+ }
4172
+ /**
4173
+ * Find a note by its commitment
4174
+ *
4175
+ * @param commitment - Commitment hash
4176
+ * @returns Note if found
4177
+ */
4178
+ async findNote(commitment) {
4179
+ const notes = await this.loadNotes();
4180
+ return findNoteByCommitment(notes, commitment);
4181
+ }
4182
+ /**
4183
+ * Import wallet keys from JSON
4184
+ *
4185
+ * @param keysJson - JSON string containing keys
4186
+ */
2491
4187
  async importWalletKeys(keysJson) {
2492
- const keys = importKeys(keysJson);
4188
+ const keys = importWalletKeys(keysJson);
2493
4189
  this.cloakKeys = keys;
2494
4190
  const result = this.storage.saveKeys(keys);
2495
4191
  if (result instanceof Promise) {
@@ -2497,32 +4193,134 @@ var CloakSDK = class {
2497
4193
  }
2498
4194
  }
2499
4195
  /**
2500
- * Export wallet keys to JSON.
2501
- *
2502
- * WARNING: this exports secret keys. Store securely.
4196
+ * Export wallet keys to JSON
4197
+ *
4198
+ * WARNING: This exports secret keys! Store securely.
4199
+ *
4200
+ * @returns JSON string with keys
2503
4201
  */
2504
4202
  exportWalletKeys() {
2505
4203
  if (!this.cloakKeys) {
2506
4204
  throw new CloakError("No wallet keys available", "wallet", false);
2507
4205
  }
2508
- return exportKeys(this.cloakKeys);
4206
+ return exportWalletKeys(this.cloakKeys);
2509
4207
  }
2510
4208
  /**
2511
- * Snapshot of the active SDK configuration.
2512
- *
2513
- * The return type ({@link CloakConfig}) is structurally narrower than the
2514
- * constructor input ({@link CloakSDKOptions}): `keypairBytes`, `wallet`,
2515
- * and `storage` are intentionally absent from the snapshot. The secrets
2516
- * are consumed into private SDK state at construction time and are not
2517
- * re-exposable through this method — the type system enforces it. To
2518
- * sign, pass a `Keypair` / `WalletAdapter` directly to the standalone
2519
- * `transact` / `partialWithdraw` / `fullWithdraw` / `swapUtxo` helpers.
4209
+ * Get the configuration
2520
4210
  */
2521
4211
  getConfig() {
2522
4212
  return { ...this.config };
2523
4213
  }
4214
+ // Note: scanNotes was removed - it required the indexer to store encrypted outputs.
4215
+ // Users should maintain their own note storage. This is more privacy-preserving.
4216
+ /**
4217
+ * Wrap errors with better categorization and user-friendly messages
4218
+ *
4219
+ * @private
4220
+ */
4221
+ wrapError(error, context) {
4222
+ if (error instanceof CloakError) {
4223
+ return error;
4224
+ }
4225
+ const errorMessage = error instanceof Error ? error.message : String(error);
4226
+ if (errorMessage.includes("duplicate key") || errorMessage.includes("already deposited")) {
4227
+ return new CloakError(
4228
+ "This note was already deposited. Generate a new note to continue.",
4229
+ "validation",
4230
+ false,
4231
+ error instanceof Error ? error : void 0
4232
+ );
4233
+ }
4234
+ if (errorMessage.includes("insufficient funds") || errorMessage.includes("insufficient lamports")) {
4235
+ return new CloakError(
4236
+ "Insufficient funds for this transaction. Please check your wallet balance.",
4237
+ "wallet",
4238
+ false,
4239
+ error instanceof Error ? error : void 0
4240
+ );
4241
+ }
4242
+ if (errorMessage.includes("Merkle tree") || errorMessage.includes("merkle")) {
4243
+ return new CloakError(
4244
+ "Failed to read on-chain Merkle tree state. Please try again.",
4245
+ "network",
4246
+ true,
4247
+ error instanceof Error ? error : void 0
4248
+ );
4249
+ }
4250
+ if (errorMessage.includes("timeout") || errorMessage.includes("timed out")) {
4251
+ return new CloakError(
4252
+ "Network timeout. Please check your connection and try again.",
4253
+ "network",
4254
+ true,
4255
+ error instanceof Error ? error : void 0
4256
+ );
4257
+ }
4258
+ if (errorMessage.includes("not connected") || errorMessage.includes("wallet")) {
4259
+ return new CloakError(
4260
+ "Wallet not connected. Please connect your wallet first.",
4261
+ "wallet",
4262
+ false,
4263
+ error instanceof Error ? error : void 0
4264
+ );
4265
+ }
4266
+ if (errorMessage.includes("proof") && (errorMessage.includes("failed") || errorMessage.includes("error"))) {
4267
+ return new CloakError(
4268
+ "Zero-knowledge proof generation failed. This is usually temporary - please try again.",
4269
+ "prover",
4270
+ true,
4271
+ error instanceof Error ? error : void 0
4272
+ );
4273
+ }
4274
+ if (errorMessage.includes("relay") || errorMessage.includes("withdraw")) {
4275
+ return new CloakError(
4276
+ "Relay service error. Please try again later.",
4277
+ "relay",
4278
+ true,
4279
+ error instanceof Error ? error : void 0
4280
+ );
4281
+ }
4282
+ return new CloakError(
4283
+ `${context}: ${errorMessage}`,
4284
+ "network",
4285
+ false,
4286
+ error instanceof Error ? error : void 0
4287
+ );
4288
+ }
2524
4289
  };
2525
4290
 
4291
+ // src/core/note.ts
4292
+ function serializeNote(note, pretty = false) {
4293
+ validateNote(note);
4294
+ return pretty ? JSON.stringify(note, null, 2) : JSON.stringify(note);
4295
+ }
4296
+ function downloadNote(note, filename) {
4297
+ const g = globalThis;
4298
+ const doc = g?.document;
4299
+ const URL_ = g?.URL;
4300
+ const Blob_ = g?.Blob;
4301
+ if (!doc || !URL_ || !Blob_) {
4302
+ throw new Error("downloadNote is only available in browser environments");
4303
+ }
4304
+ const json = serializeNote(note, true);
4305
+ const blob = new Blob_([json], { type: "application/json" });
4306
+ const url = URL_.createObjectURL(blob);
4307
+ const defaultFilename = `cloak-note-${note.commitment.slice(0, 8)}.json`;
4308
+ const link = doc.createElement("a");
4309
+ link.href = url;
4310
+ link.download = filename || defaultFilename;
4311
+ link.click();
4312
+ URL_.revokeObjectURL(url);
4313
+ }
4314
+ async function copyNoteToClipboard(note) {
4315
+ const g = globalThis;
4316
+ const nav = g?.navigator;
4317
+ if (!nav || !nav.clipboard) {
4318
+ throw new Error("Clipboard API not available");
4319
+ }
4320
+ const json = serializeNote(note, true);
4321
+ await nav.clipboard.writeText(json);
4322
+ }
4323
+
2526
4324
  // src/core/compliance-keys.ts
2527
4325
  var import_tweetnacl2 = __toESM(require("tweetnacl"), 1);
2528
4326
  var import_blake33 = require("@noble/hashes/blake3");
@@ -2909,107 +4707,6 @@ async function decryptComplianceMetadataWithMasterKey(encrypted, masterComplianc
2909
4707
  return decryptWithSharedSecret(payload, sharedSecret);
2910
4708
  }
2911
4709
 
2912
- // src/utils/fees.ts
2913
- var LAMPORTS_PER_SOL = 1e9;
2914
- var FIXED_FEE_LAMPORTS = 5e6;
2915
- var VARIABLE_FEE_NUMERATOR = 3;
2916
- var VARIABLE_FEE_DENOMINATOR = 1e3;
2917
- var VARIABLE_FEE_RATE = VARIABLE_FEE_NUMERATOR / VARIABLE_FEE_DENOMINATOR;
2918
- var MIN_DEPOSIT_LAMPORTS = 1e7;
2919
- function calculateFee(amountLamports) {
2920
- const variableFee = Math.floor(amountLamports * VARIABLE_FEE_NUMERATOR / VARIABLE_FEE_DENOMINATOR);
2921
- return FIXED_FEE_LAMPORTS + variableFee;
2922
- }
2923
- function calculateFeeBigint(amountLamports) {
2924
- const fixed = BigInt(FIXED_FEE_LAMPORTS);
2925
- const variable = amountLamports * BigInt(VARIABLE_FEE_NUMERATOR) / BigInt(VARIABLE_FEE_DENOMINATOR);
2926
- return fixed + variable;
2927
- }
2928
- function isWithdrawAmountSufficient(amountLamports) {
2929
- return amountLamports > calculateFeeBigint(amountLamports);
2930
- }
2931
- function getDistributableAmount(amountLamports) {
2932
- return amountLamports - calculateFee(amountLamports);
2933
- }
2934
- function formatAmount(lamports, decimals = 9) {
2935
- return (lamports / LAMPORTS_PER_SOL).toFixed(decimals);
2936
- }
2937
- function parseAmount(sol) {
2938
- const num = parseFloat(sol);
2939
- if (isNaN(num) || num < 0) {
2940
- throw new Error(`Invalid SOL amount: ${sol}`);
2941
- }
2942
- return Math.floor(num * LAMPORTS_PER_SOL);
2943
- }
2944
- function validateOutputsSum(outputs, expectedTotal) {
2945
- const sum = outputs.reduce((acc, out) => acc + out.amount, 0);
2946
- return sum === expectedTotal;
2947
- }
2948
- function calculateRelayFee(amountLamports, feeBps) {
2949
- if (feeBps < 0 || feeBps > 1e4) {
2950
- throw new Error("Fee basis points must be between 0 and 10000");
2951
- }
2952
- return Math.floor(amountLamports * feeBps / 1e4);
2953
- }
2954
-
2955
- // src/utils/validation.ts
2956
- var import_web34 = require("@solana/web3.js");
2957
- function isValidSolanaAddress(address) {
2958
- try {
2959
- new import_web34.PublicKey(address);
2960
- return true;
2961
- } catch {
2962
- return false;
2963
- }
2964
- }
2965
-
2966
- // src/utils/network.ts
2967
- function detectNetworkFromRpcUrl(rpcUrl) {
2968
- const url = rpcUrl || process.env.NEXT_PUBLIC_SOLANA_RPC_URL || "";
2969
- const lowerUrl = url.toLowerCase();
2970
- if (lowerUrl.includes("mainnet") || lowerUrl.includes("api.mainnet-beta") || lowerUrl.includes("mainnet-beta")) {
2971
- return "mainnet";
2972
- }
2973
- if (lowerUrl.includes("testnet") || lowerUrl.includes("api.testnet")) {
2974
- return "testnet";
2975
- }
2976
- if (lowerUrl.includes("devnet") || lowerUrl.includes("api.devnet")) {
2977
- return "devnet";
2978
- }
2979
- if (lowerUrl.includes("localhost") || lowerUrl.includes("127.0.0.1") || lowerUrl.includes("local")) {
2980
- return "localnet";
2981
- }
2982
- return "mainnet";
2983
- }
2984
- function getRpcUrlForNetwork(network) {
2985
- switch (network) {
2986
- case "mainnet":
2987
- return "https://api.mainnet-beta.solana.com";
2988
- case "devnet":
2989
- return "https://api.devnet.solana.com";
2990
- case "localnet":
2991
- return "http://localhost:8899";
2992
- default:
2993
- return "https://api.mainnet-beta.solana.com";
2994
- }
2995
- }
2996
- function isValidRpcUrl(url) {
2997
- try {
2998
- const parsed = new URL(url);
2999
- return parsed.protocol === "http:" || parsed.protocol === "https:";
3000
- } catch {
3001
- return false;
3002
- }
3003
- }
3004
- function getExplorerUrl(signature, network = "devnet") {
3005
- const cluster = network === "mainnet" ? "" : `?cluster=${network}`;
3006
- return `https://explorer.solana.com/tx/${signature}${cluster}`;
3007
- }
3008
- function getAddressExplorerUrl(address, network = "devnet") {
3009
- const cluster = network === "mainnet" ? "" : `?cluster=${network}`;
3010
- return `https://explorer.solana.com/address/${address}${cluster}`;
3011
- }
3012
-
3013
4710
  // src/utils/verify-utxos.ts
3014
4711
  init_utxo();
3015
4712
  async function verifyUtxos(utxos, connection, programId, commitment = "confirmed") {
@@ -3021,7 +4718,7 @@ async function verifyUtxos(utxos, connection, programId, commitment = "confirmed
3021
4718
  continue;
3022
4719
  }
3023
4720
  try {
3024
- const nullifierBig = await computeNullifier(utxo);
4721
+ const nullifierBig = await computeNullifier2(utxo);
3025
4722
  const nullifierHex = nullifierBig.toString(16).padStart(64, "0");
3026
4723
  const nullifierBytes = Buffer.from(nullifierHex, "hex");
3027
4724
  const { pool } = getShieldPoolPDAs(programId, utxo.mintAddress);
@@ -3139,10 +4836,49 @@ async function fetchRiskQuoteIx(connection, wallet, rangeApiKey) {
3139
4836
  };
3140
4837
  }
3141
4838
 
4839
+ // src/helpers/encrypted-output.ts
4840
+ function prepareEncryptedOutput(note, cloakKeys) {
4841
+ const noteData = {
4842
+ amount: note.amount,
4843
+ r: note.r,
4844
+ sk_spend: note.sk_spend,
4845
+ commitment: note.commitment
4846
+ };
4847
+ const encrypted = encryptNoteForRecipient(noteData, cloakKeys.view.pvk);
4848
+ return btoa(JSON.stringify(encrypted));
4849
+ }
4850
+ function prepareEncryptedOutputForRecipient(note, recipientPvkHex) {
4851
+ const noteData = {
4852
+ amount: note.amount,
4853
+ r: note.r,
4854
+ sk_spend: note.sk_spend,
4855
+ commitment: note.commitment
4856
+ };
4857
+ const recipientPvk = hexToBytes(recipientPvkHex);
4858
+ const encrypted = encryptNoteForRecipient(noteData, recipientPvk);
4859
+ return btoa(JSON.stringify(encrypted));
4860
+ }
4861
+ function encodeNoteSimple(note) {
4862
+ const data = {
4863
+ amount: note.amount,
4864
+ r: note.r,
4865
+ sk_spend: note.sk_spend,
4866
+ commitment: note.commitment
4867
+ };
4868
+ const json = JSON.stringify(data);
4869
+ if (typeof Buffer !== "undefined") {
4870
+ return Buffer.from(json).toString("base64");
4871
+ } else if (typeof btoa !== "undefined") {
4872
+ return btoa(json);
4873
+ } else {
4874
+ throw new Error("No base64 encoding method available");
4875
+ }
4876
+ }
4877
+
3142
4878
  // src/helpers/wallet-integration.ts
3143
- var import_web35 = require("@solana/web3.js");
4879
+ var import_web36 = require("@solana/web3.js");
3144
4880
  function validateWalletConnected(wallet) {
3145
- if (wallet instanceof import_web35.Keypair) {
4881
+ if (wallet instanceof import_web36.Keypair) {
3146
4882
  return;
3147
4883
  }
3148
4884
  if (!wallet.publicKey) {
@@ -3154,7 +4890,7 @@ function validateWalletConnected(wallet) {
3154
4890
  }
3155
4891
  }
3156
4892
  function getPublicKey(wallet) {
3157
- if (wallet instanceof import_web35.Keypair) {
4893
+ if (wallet instanceof import_web36.Keypair) {
3158
4894
  return wallet.publicKey;
3159
4895
  }
3160
4896
  if (!wallet.publicKey) {
@@ -3167,7 +4903,7 @@ function getPublicKey(wallet) {
3167
4903
  return wallet.publicKey;
3168
4904
  }
3169
4905
  async function sendTransaction(transaction, wallet, connection, options) {
3170
- if (wallet instanceof import_web35.Keypair) {
4906
+ if (wallet instanceof import_web36.Keypair) {
3171
4907
  return await connection.sendTransaction(transaction, [wallet], options);
3172
4908
  }
3173
4909
  if (wallet.sendTransaction) {
@@ -3184,7 +4920,7 @@ async function sendTransaction(transaction, wallet, connection, options) {
3184
4920
  }
3185
4921
  }
3186
4922
  async function signTransaction(transaction, wallet) {
3187
- if (wallet instanceof import_web35.Keypair) {
4923
+ if (wallet instanceof import_web36.Keypair) {
3188
4924
  transaction.sign(wallet);
3189
4925
  return transaction;
3190
4926
  }
@@ -3648,13 +5384,180 @@ async function preflightCheck(relayUrl, rootHex) {
3648
5384
  };
3649
5385
  }
3650
5386
 
5387
+ // src/utils/pending-operations.ts
5388
+ var PENDING_DEPOSITS_KEY = "cloak_pending_deposits";
5389
+ var PENDING_WITHDRAWALS_KEY = "cloak_pending_withdrawals";
5390
+ function getStorage() {
5391
+ if (typeof globalThis !== "undefined" && globalThis.localStorage) {
5392
+ return globalThis.localStorage;
5393
+ }
5394
+ return null;
5395
+ }
5396
+ function savePendingDeposit(deposit) {
5397
+ const storage = getStorage();
5398
+ if (!storage) {
5399
+ console.warn("localStorage not available - pending deposit not persisted");
5400
+ return;
5401
+ }
5402
+ const deposits = loadPendingDeposits();
5403
+ const index = deposits.findIndex((d) => d.note.commitment === deposit.note.commitment);
5404
+ if (index >= 0) {
5405
+ deposits[index] = deposit;
5406
+ } else {
5407
+ deposits.push(deposit);
5408
+ }
5409
+ storage.setItem(PENDING_DEPOSITS_KEY, JSON.stringify(deposits));
5410
+ }
5411
+ function loadPendingDeposits() {
5412
+ const storage = getStorage();
5413
+ if (!storage) return [];
5414
+ const stored = storage.getItem(PENDING_DEPOSITS_KEY);
5415
+ if (!stored) return [];
5416
+ try {
5417
+ return JSON.parse(stored);
5418
+ } catch {
5419
+ return [];
5420
+ }
5421
+ }
5422
+ function updatePendingDeposit(commitment, updates) {
5423
+ const storage = getStorage();
5424
+ if (!storage) return;
5425
+ const deposits = loadPendingDeposits();
5426
+ const index = deposits.findIndex((d) => d.note.commitment === commitment);
5427
+ if (index >= 0) {
5428
+ deposits[index] = { ...deposits[index], ...updates };
5429
+ storage.setItem(PENDING_DEPOSITS_KEY, JSON.stringify(deposits));
5430
+ }
5431
+ }
5432
+ function removePendingDeposit(commitment) {
5433
+ const storage = getStorage();
5434
+ if (!storage) return;
5435
+ const deposits = loadPendingDeposits();
5436
+ const filtered = deposits.filter((d) => d.note.commitment !== commitment);
5437
+ storage.setItem(PENDING_DEPOSITS_KEY, JSON.stringify(filtered));
5438
+ }
5439
+ function clearPendingDeposits() {
5440
+ const storage = getStorage();
5441
+ if (storage) {
5442
+ storage.removeItem(PENDING_DEPOSITS_KEY);
5443
+ }
5444
+ }
5445
+ function savePendingWithdrawal(withdrawal) {
5446
+ const storage = getStorage();
5447
+ if (!storage) {
5448
+ console.warn("localStorage not available - pending withdrawal not persisted");
5449
+ return;
5450
+ }
5451
+ const withdrawals = loadPendingWithdrawals();
5452
+ const index = withdrawals.findIndex((w) => w.requestId === withdrawal.requestId);
5453
+ if (index >= 0) {
5454
+ withdrawals[index] = withdrawal;
5455
+ } else {
5456
+ withdrawals.push(withdrawal);
5457
+ }
5458
+ storage.setItem(PENDING_WITHDRAWALS_KEY, JSON.stringify(withdrawals));
5459
+ }
5460
+ function loadPendingWithdrawals() {
5461
+ const storage = getStorage();
5462
+ if (!storage) return [];
5463
+ const stored = storage.getItem(PENDING_WITHDRAWALS_KEY);
5464
+ if (!stored) return [];
5465
+ try {
5466
+ return JSON.parse(stored);
5467
+ } catch {
5468
+ return [];
5469
+ }
5470
+ }
5471
+ function updatePendingWithdrawal(requestId, updates) {
5472
+ const storage = getStorage();
5473
+ if (!storage) return;
5474
+ const withdrawals = loadPendingWithdrawals();
5475
+ const index = withdrawals.findIndex((w) => w.requestId === requestId);
5476
+ if (index >= 0) {
5477
+ withdrawals[index] = { ...withdrawals[index], ...updates };
5478
+ storage.setItem(PENDING_WITHDRAWALS_KEY, JSON.stringify(withdrawals));
5479
+ }
5480
+ }
5481
+ function removePendingWithdrawal(requestId) {
5482
+ const storage = getStorage();
5483
+ if (!storage) return;
5484
+ const withdrawals = loadPendingWithdrawals();
5485
+ const filtered = withdrawals.filter((w) => w.requestId !== requestId);
5486
+ storage.setItem(PENDING_WITHDRAWALS_KEY, JSON.stringify(filtered));
5487
+ }
5488
+ function clearPendingWithdrawals() {
5489
+ const storage = getStorage();
5490
+ if (storage) {
5491
+ storage.removeItem(PENDING_WITHDRAWALS_KEY);
5492
+ }
5493
+ }
5494
+ function hasPendingOperations() {
5495
+ const deposits = loadPendingDeposits();
5496
+ const withdrawals = loadPendingWithdrawals();
5497
+ const pendingDeposits = deposits.filter(
5498
+ (d) => d.status === "pending" || d.status === "tx_sent"
5499
+ );
5500
+ const pendingWithdrawals = withdrawals.filter(
5501
+ (w) => w.status === "pending" || w.status === "processing"
5502
+ );
5503
+ return pendingDeposits.length > 0 || pendingWithdrawals.length > 0;
5504
+ }
5505
+ function getPendingOperationsSummary() {
5506
+ const deposits = loadPendingDeposits().filter(
5507
+ (d) => d.status === "pending" || d.status === "tx_sent"
5508
+ );
5509
+ const withdrawals = loadPendingWithdrawals().filter(
5510
+ (w) => w.status === "pending" || w.status === "processing"
5511
+ );
5512
+ return {
5513
+ deposits,
5514
+ withdrawals,
5515
+ totalPending: deposits.length + withdrawals.length
5516
+ };
5517
+ }
5518
+ function cleanupStalePendingOperations(maxAgeMs = 24 * 60 * 60 * 1e3) {
5519
+ const now = Date.now();
5520
+ let removedDeposits = 0;
5521
+ let removedWithdrawals = 0;
5522
+ const deposits = loadPendingDeposits();
5523
+ const activeDeposits = deposits.filter((d) => {
5524
+ const age = now - d.startedAt;
5525
+ const isStale = age > maxAgeMs;
5526
+ const isTerminal = d.status === "confirmed" || d.status === "failed";
5527
+ if (isStale || isTerminal) {
5528
+ removedDeposits++;
5529
+ return false;
5530
+ }
5531
+ return true;
5532
+ });
5533
+ const storage = getStorage();
5534
+ if (storage) {
5535
+ storage.setItem(PENDING_DEPOSITS_KEY, JSON.stringify(activeDeposits));
5536
+ }
5537
+ const withdrawals = loadPendingWithdrawals();
5538
+ const activeWithdrawals = withdrawals.filter((w) => {
5539
+ const age = now - w.startedAt;
5540
+ const isStale = age > maxAgeMs;
5541
+ const isTerminal = w.status === "completed" || w.status === "failed";
5542
+ if (isStale || isTerminal) {
5543
+ removedWithdrawals++;
5544
+ return false;
5545
+ }
5546
+ return true;
5547
+ });
5548
+ if (storage) {
5549
+ storage.setItem(PENDING_WITHDRAWALS_KEY, JSON.stringify(activeWithdrawals));
5550
+ }
5551
+ return { removedDeposits, removedWithdrawals };
5552
+ }
5553
+
3651
5554
  // src/index.ts
3652
5555
  init_utxo();
3653
5556
 
3654
5557
  // src/core/transact.ts
3655
- var import_web36 = require("@solana/web3.js");
5558
+ var import_web37 = require("@solana/web3.js");
3656
5559
  var import_spl_token = require("@solana/spl-token");
3657
- var snarkjs = __toESM(require("snarkjs"), 1);
5560
+ var snarkjs2 = __toESM(require("snarkjs"), 1);
3658
5561
  init_utxo();
3659
5562
 
3660
5563
  // src/core/chain-note.ts
@@ -3805,8 +5708,8 @@ function chainNoteFromBase64(base64) {
3805
5708
  // src/core/transact.ts
3806
5709
  var import_circomlibjs4 = require("circomlibjs");
3807
5710
  var import_tweetnacl4 = __toESM(require("tweetnacl"), 1);
3808
- var IS_BROWSER = typeof window !== "undefined" || typeof globalThis.document !== "undefined";
3809
- var DEFAULT_TRANSACTION_CIRCUITS_URL = "https://cloak-circuits.s3.us-east-1.amazonaws.com/circuits/0.1.0";
5711
+ var IS_BROWSER2 = typeof window !== "undefined" || typeof globalThis.document !== "undefined";
5712
+ var DEFAULT_TRANSACTION_CIRCUITS_URL = "https://storage.googleapis.com/cloak-circuits/circuits/0.1.0";
3810
5713
  var DEFAULT_CLOAK_RELAY_URL = "https://api.cloak.ag";
3811
5714
  function resolveDefaultTransactionCircuitsPath() {
3812
5715
  return DEFAULT_TRANSACTION_CIRCUITS_URL;
@@ -3818,7 +5721,7 @@ async function resolveTransactionCircuitFiles() {
3818
5721
  const baseNorm = base.endsWith("/") ? base : `${base}/`;
3819
5722
  const wasmUrl = `${baseNorm}transaction_js/transaction.wasm`;
3820
5723
  const zkeyUrl = `${baseNorm}transaction_final.zkey`;
3821
- if (IS_BROWSER) {
5724
+ if (IS_BROWSER2) {
3822
5725
  return { wasmPath: wasmUrl, zkeyPath: zkeyUrl };
3823
5726
  }
3824
5727
  if (_remoteTxCircuitsCache?.base === baseNorm) {
@@ -3955,7 +5858,7 @@ async function resolveAddressLookupTableAccounts(connection, relayUrl, altAddres
3955
5858
  const fetched = await Promise.all(
3956
5859
  altAddresses.map(async (addr) => {
3957
5860
  try {
3958
- const pubkey = new import_web36.PublicKey(addr);
5861
+ const pubkey = new import_web37.PublicKey(addr);
3959
5862
  const result = await connection.getAddressLookupTable(pubkey);
3960
5863
  return result.value ?? null;
3961
5864
  } catch {
@@ -4117,7 +6020,7 @@ async function generateTransactionProof(inputs, onProgress) {
4117
6020
  onProgress?.(10);
4118
6021
  const { wasmPath: wasmInput, zkeyPath: zkeyInput } = await resolveTransactionCircuitFiles();
4119
6022
  onProgress?.(30);
4120
- const { proof, publicSignals } = await snarkjs.groth16.fullProve(
6023
+ const { proof, publicSignals } = await snarkjs2.groth16.fullProve(
4121
6024
  inputs,
4122
6025
  wasmInput,
4123
6026
  zkeyInput
@@ -4197,7 +6100,7 @@ function buildPublicInputsBytesFromSignals(publicSignals) {
4197
6100
  }
4198
6101
  var TRANSACT_DISCRIMINATOR = 0;
4199
6102
  function deriveNullifierPDA(programId, poolPDA, nullifier) {
4200
- return import_web36.PublicKey.findProgramAddressSync(
6103
+ return import_web37.PublicKey.findProgramAddressSync(
4201
6104
  [Buffer.from("nullifier"), poolPDA.toBuffer(), Buffer.from(nullifier)],
4202
6105
  programId
4203
6106
  );
@@ -4240,7 +6143,7 @@ function buildTransactInstruction(programId, payer, poolPDA, treasuryPDA, merkle
4240
6143
  // 4. nullifier PDA 0 (writable)
4241
6144
  { pubkey: nullifierPDA1, isSigner: false, isWritable: true },
4242
6145
  // 5. nullifier PDA 1 (writable)
4243
- { pubkey: import_web36.SystemProgram.programId, isSigner: false, isWritable: false }
6146
+ { pubkey: import_web37.SystemProgram.programId, isSigner: false, isWritable: false }
4244
6147
  // 6. system_program
4245
6148
  ];
4246
6149
  if (splAccounts) {
@@ -4249,23 +6152,23 @@ function buildTransactInstruction(programId, payer, poolPDA, treasuryPDA, merkle
4249
6152
  accounts.push({ pubkey: splAccounts.tokenProgram, isSigner: false, isWritable: false });
4250
6153
  if (splAccounts.payerAta) {
4251
6154
  if (enableRiskCheck) {
4252
- accounts.push({ pubkey: import_web36.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
6155
+ accounts.push({ pubkey: import_web37.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
4253
6156
  }
4254
6157
  accounts.push({ pubkey: splAccounts.payerAta, isSigner: false, isWritable: true });
4255
6158
  } else if (enableRiskCheck && !recipient) {
4256
- accounts.push({ pubkey: import_web36.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
6159
+ accounts.push({ pubkey: import_web37.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
4257
6160
  }
4258
6161
  } else {
4259
6162
  if (recipient) {
4260
6163
  accounts.push({ pubkey: recipient, isSigner: false, isWritable: true });
4261
6164
  if (enableRiskCheck) {
4262
- accounts.push({ pubkey: import_web36.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
6165
+ accounts.push({ pubkey: import_web37.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
4263
6166
  }
4264
6167
  } else if (enableRiskCheck) {
4265
- accounts.push({ pubkey: import_web36.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
6168
+ accounts.push({ pubkey: import_web37.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
4266
6169
  }
4267
6170
  }
4268
- return new import_web36.TransactionInstruction({
6171
+ return new import_web37.TransactionInstruction({
4269
6172
  programId,
4270
6173
  keys: accounts,
4271
6174
  data
@@ -4273,10 +6176,10 @@ function buildTransactInstruction(programId, payer, poolPDA, treasuryPDA, merkle
4273
6176
  }
4274
6177
  function getCommonALTAddresses() {
4275
6178
  return [
4276
- import_web36.SystemProgram.programId,
4277
- import_web36.SYSVAR_SLOT_HASHES_PUBKEY,
4278
- import_web36.SYSVAR_INSTRUCTIONS_PUBKEY,
4279
- import_web36.ComputeBudgetProgram.programId
6179
+ import_web37.SystemProgram.programId,
6180
+ import_web37.SYSVAR_SLOT_HASHES_PUBKEY,
6181
+ import_web37.SYSVAR_INSTRUCTIONS_PUBKEY,
6182
+ import_web37.ComputeBudgetProgram.programId
4280
6183
  ];
4281
6184
  }
4282
6185
  function dedupePubkeys(addresses) {
@@ -4326,23 +6229,23 @@ async function createEphemeralALT(connection, depositor, onProgress, additionalA
4326
6229
  for (let altAttempt = 0; altAttempt < MAX_ALT_CREATE_RETRIES; altAttempt++) {
4327
6230
  try {
4328
6231
  const slot = await connection.getSlot("finalized");
4329
- const [createIx, altAddress] = import_web36.AddressLookupTableProgram.createLookupTable({
6232
+ const [createIx, altAddress] = import_web37.AddressLookupTableProgram.createLookupTable({
4330
6233
  authority: depositor.publicKey,
4331
6234
  payer: depositor.publicKey,
4332
6235
  recentSlot: slot
4333
6236
  });
4334
- const extendIx = import_web36.AddressLookupTableProgram.extendLookupTable({
6237
+ const extendIx = import_web37.AddressLookupTableProgram.extendLookupTable({
4335
6238
  payer: depositor.publicKey,
4336
6239
  authority: depositor.publicKey,
4337
6240
  lookupTable: altAddress,
4338
6241
  addresses: addressesForCreate
4339
6242
  });
4340
- const tx = new import_web36.Transaction().add(createIx).add(extendIx);
6243
+ const tx = new import_web37.Transaction().add(createIx).add(extendIx);
4341
6244
  const { blockhash } = await connection.getLatestBlockhash();
4342
6245
  tx.recentBlockhash = blockhash;
4343
6246
  tx.feePayer = depositor.publicKey;
4344
6247
  if (depositor.keypair) {
4345
- await (0, import_web36.sendAndConfirmTransaction)(connection, tx, [depositor.keypair], { commitment: "confirmed" });
6248
+ await (0, import_web37.sendAndConfirmTransaction)(connection, tx, [depositor.keypair], { commitment: "confirmed" });
4346
6249
  } else if (depositor.signTransaction) {
4347
6250
  const signedTx = await depositor.signTransaction(tx);
4348
6251
  const sig = await connection.sendRawTransaction(signedTx.serialize());
@@ -4503,8 +6406,8 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
4503
6406
  if (riskQuoteIx) {
4504
6407
  baseInstructions.unshift(riskQuoteIx);
4505
6408
  }
4506
- const cuLimitIx = import_web36.ComputeBudgetProgram.setComputeUnitLimit({ units: 4e5 });
4507
- const cuPriceIx = import_web36.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1e5 });
6409
+ const cuLimitIx = import_web37.ComputeBudgetProgram.setComputeUnitLimit({ units: 4e5 });
6410
+ const cuPriceIx = import_web37.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1e5 });
4508
6411
  const fullInstructions = [...baseInstructions, cuLimitIx, cuPriceIx, transactIx];
4509
6412
  const compactInstructions = [...baseInstructions, cuLimitIx, transactIx];
4510
6413
  const minimalInstructions = [...baseInstructions, transactIx];
@@ -4515,7 +6418,7 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
4515
6418
  if (useV0) {
4516
6419
  const estimateV0Size = (ixs) => {
4517
6420
  try {
4518
- const messageV0 = new import_web36.TransactionMessage({
6421
+ const messageV0 = new import_web37.TransactionMessage({
4519
6422
  payerKey: depositor.publicKey,
4520
6423
  recentBlockhash: blockhash,
4521
6424
  instructions: ixs
@@ -4566,6 +6469,38 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
4566
6469
  const sendV0 = async (ixs, options) => {
4567
6470
  const maxBlockhashRetries = 2;
4568
6471
  const maxTransportRetries = 3;
6472
+ const alreadyLanded = async (sig) => {
6473
+ if (!sig) return false;
6474
+ for (let i = 0; i < 6; i++) {
6475
+ try {
6476
+ const { value } = await connection.getSignatureStatus(sig, {
6477
+ searchTransactionHistory: true
6478
+ });
6479
+ if (value) {
6480
+ if (value.err) return false;
6481
+ if (value.confirmationStatus === "confirmed" || value.confirmationStatus === "finalized") {
6482
+ return true;
6483
+ }
6484
+ }
6485
+ } catch {
6486
+ }
6487
+ await new Promise((r) => setTimeout(r, 400));
6488
+ }
6489
+ return false;
6490
+ };
6491
+ const looksLikeDoubleSpend = (error) => {
6492
+ const m = error instanceof Error ? error.message : String(error);
6493
+ return m.includes("0x1020") || // confirmTransaction().value.err is JSON-stringified, so 0x1020 appears as
6494
+ // its decimal form ("Custom":4128) rather than the hex "0x1020".
6495
+ m.includes('"Custom":4128') || m.includes("already been processed") || m.toLowerCase().includes("doublespend");
6496
+ };
6497
+ const sentSigs = [];
6498
+ const anyLanded = async () => {
6499
+ for (const s of sentSigs) {
6500
+ if (await alreadyLanded(s)) return s;
6501
+ }
6502
+ return void 0;
6503
+ };
4569
6504
  for (let blockhashRetry = 0; blockhashRetry <= maxBlockhashRetries; blockhashRetry++) {
4570
6505
  if (blockhashRetry > 0) {
4571
6506
  ({ blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash());
@@ -4575,12 +6510,12 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
4575
6510
  }
4576
6511
  let transportAttempt = 0;
4577
6512
  while (true) {
4578
- const messageV0 = new import_web36.TransactionMessage({
6513
+ const messageV0 = new import_web37.TransactionMessage({
4579
6514
  payerKey: depositor.publicKey,
4580
6515
  recentBlockhash: blockhash,
4581
6516
  instructions: ixs
4582
6517
  }).compileToV0Message(addressLookupTableAccounts);
4583
- const versionedTx = new import_web36.VersionedTransaction(messageV0);
6518
+ const versionedTx = new import_web37.VersionedTransaction(messageV0);
4584
6519
  try {
4585
6520
  if (depositor.keypair) {
4586
6521
  onProgress?.("Signing and sending transaction...");
@@ -4588,6 +6523,7 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
4588
6523
  signature = await connection.sendRawTransaction(versionedTx.serialize(), {
4589
6524
  skipPreflight: options?.skipPreflight ?? false
4590
6525
  });
6526
+ sentSigs.push(signature);
4591
6527
  onProgress?.("Confirming transaction...");
4592
6528
  const confirmation = await connection.confirmTransaction({
4593
6529
  signature,
@@ -4604,6 +6540,7 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
4604
6540
  signature = await connection.sendRawTransaction(signedTx.serialize(), {
4605
6541
  skipPreflight: options?.skipPreflight ?? false
4606
6542
  });
6543
+ sentSigs.push(signature);
4607
6544
  onProgress?.("Confirming transaction...");
4608
6545
  const confirmation = await connection.confirmTransaction({
4609
6546
  signature,
@@ -4618,8 +6555,10 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
4618
6555
  }
4619
6556
  return signature;
4620
6557
  } catch (error) {
4621
- if (isBlockhashExpiryError(error) && blockhashRetry < maxBlockhashRetries) {
4622
- break;
6558
+ if (isBlockhashExpiryError(error)) {
6559
+ const landed = await anyLanded();
6560
+ if (landed) return landed;
6561
+ if (blockhashRetry < maxBlockhashRetries) break;
4623
6562
  }
4624
6563
  if (isTransientTransportError(error) && transportAttempt < maxTransportRetries) {
4625
6564
  transportAttempt++;
@@ -4631,6 +6570,10 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
4631
6570
  ({ blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash());
4632
6571
  continue;
4633
6572
  }
6573
+ if (looksLikeDoubleSpend(error)) {
6574
+ const landed = await anyLanded();
6575
+ if (landed) return landed;
6576
+ }
4634
6577
  throw error;
4635
6578
  }
4636
6579
  }
@@ -4713,7 +6656,7 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
4713
6656
  let legacyTransportAttempt = 0;
4714
6657
  while (true) {
4715
6658
  try {
4716
- const tx = new import_web36.Transaction();
6659
+ const tx = new import_web37.Transaction();
4717
6660
  for (const ix of instructions) {
4718
6661
  tx.add(ix);
4719
6662
  }
@@ -4721,7 +6664,7 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
4721
6664
  tx.feePayer = depositor.publicKey;
4722
6665
  if (depositor.keypair) {
4723
6666
  onProgress?.("Signing and sending transaction...");
4724
- signature = await (0, import_web36.sendAndConfirmTransaction)(
6667
+ signature = await (0, import_web37.sendAndConfirmTransaction)(
4725
6668
  connection,
4726
6669
  tx,
4727
6670
  [depositor.keypair],
@@ -4895,7 +6838,7 @@ async function fetchRiskQuoteInstruction(riskQuoteUrl, wallet, options) {
4895
6838
  const signerB58 = signerKey;
4896
6839
  const signatureBuf = Buffer.from(signatureB64, "base64");
4897
6840
  const messageArr = hexToBytes2(messageHex);
4898
- const publicKeyBytes = new import_web36.PublicKey(signerB58).toBytes();
6841
+ const publicKeyBytes = new import_web37.PublicKey(signerB58).toBytes();
4899
6842
  if (signatureBuf.length !== 64 || messageArr.length !== 33 && messageArr.length !== 41 && messageArr.length !== 50 || publicKeyBytes.length !== 32) {
4900
6843
  throw new Error(
4901
6844
  `Invalid risk quote response: invalid lengths (sig=${signatureBuf.length}, msg=${messageArr.length}, pk=${publicKeyBytes.length})`
@@ -4904,7 +6847,7 @@ async function fetchRiskQuoteInstruction(riskQuoteUrl, wallet, options) {
4904
6847
  const signature = new Uint8Array(signatureBuf);
4905
6848
  const message = new Uint8Array(messageArr);
4906
6849
  const publicKey = new Uint8Array(publicKeyBytes);
4907
- return import_web36.Ed25519Program.createInstructionWithPublicKey({
6850
+ return import_web37.Ed25519Program.createInstructionWithPublicKey({
4908
6851
  publicKey,
4909
6852
  message,
4910
6853
  signature
@@ -4917,10 +6860,10 @@ async function fetchRiskQuoteInstruction(riskQuoteUrl, wallet, options) {
4917
6860
  `Invalid risk quote response: expected relay format { signature, message, signer_pubkey } or { instruction }. Received keys: ${keys}`
4918
6861
  );
4919
6862
  }
4920
- return new import_web36.TransactionInstruction({
4921
- programId: new import_web36.PublicKey(raw.programId),
6863
+ return new import_web37.TransactionInstruction({
6864
+ programId: new import_web37.PublicKey(raw.programId),
4922
6865
  keys: raw.keys.map((k) => ({
4923
- pubkey: new import_web36.PublicKey(k.pubkey),
6866
+ pubkey: new import_web37.PublicKey(k.pubkey),
4924
6867
  isSigner: k.isSigner,
4925
6868
  isWritable: k.isWritable
4926
6869
  })),
@@ -5290,7 +7233,7 @@ async function transact(params, options) {
5290
7233
  const outputCommitments = [];
5291
7234
  for (let i = 0; i < paddedOutputs.length; i++) {
5292
7235
  const utxo = paddedOutputs[i];
5293
- const commitment = await computeCommitment(utxo);
7236
+ const commitment = await computeCommitment2(utxo);
5294
7237
  outputCommitments.push(commitment);
5295
7238
  }
5296
7239
  onProgress?.("Computing external data hash...");
@@ -5371,7 +7314,7 @@ async function transact(params, options) {
5371
7314
  }
5372
7315
  inputNullifiers = [];
5373
7316
  for (const utxo of paddedInputs) {
5374
- inputNullifiers.push(await computeNullifier(utxo));
7317
+ inputNullifiers.push(await computeNullifier2(utxo));
5375
7318
  }
5376
7319
  if (nextIndex !== null) {
5377
7320
  onProgress?.(`[Submit ${submissionAttempts + 1}/${maxRootRetries + 1}] Root: ${rootHex.substring(0, 16)}..., nextIndex: ${nextIndex}`);
@@ -5804,12 +7747,12 @@ async function transact(params, options) {
5804
7747
  if (lastError) {
5805
7748
  const classified = classifyRelayError(lastError.message);
5806
7749
  const isMerkleClass = !(classified instanceof UtxoAlreadySpentError) && !(classified instanceof SanctionsQuoteError) && !isRootNotFoundError2(lastError);
5807
- if (!IS_BROWSER && isMerkleClass && relayUrl) {
7750
+ if (!IS_BROWSER2 && isMerkleClass && relayUrl) {
5808
7751
  onProgress?.(
5809
7752
  "All relay-tree attempts failed. Rebuilding merkle tree from chain as last resort..."
5810
7753
  );
5811
7754
  try {
5812
- const [merkleTreePda] = import_web36.PublicKey.findProgramAddressSync(
7755
+ const [merkleTreePda] = import_web37.PublicKey.findProgramAddressSync(
5813
7756
  [Buffer.from("merkle_tree"), mint.toBuffer()],
5814
7757
  programId
5815
7758
  );
@@ -6453,7 +8396,7 @@ async function swapUtxo(params, options) {
6453
8396
  let inputNullifiers = [];
6454
8397
  const outputCommitments = [];
6455
8398
  for (const utxo of paddedOutputs) {
6456
- outputCommitments.push(await computeCommitment(utxo));
8399
+ outputCommitments.push(await computeCommitment2(utxo));
6457
8400
  }
6458
8401
  const chainNoteTimestamp = BigInt(Date.now());
6459
8402
  onProgress?.("Computing external data hash...");
@@ -6533,7 +8476,7 @@ async function swapUtxo(params, options) {
6533
8476
  }
6534
8477
  inputNullifiers = [];
6535
8478
  for (const utxo of paddedInputs) {
6536
- inputNullifiers.push(await computeNullifier(utxo));
8479
+ inputNullifiers.push(await computeNullifier2(utxo));
6537
8480
  }
6538
8481
  if (merkleState) {
6539
8482
  onProgress?.(`[Attempt ${attempt + 1}/${maxRootRetries + 1}] Root: ${rootHex.substring(0, 16)}..., nextIndex: ${merkleState.nextIndex}`);
@@ -6877,7 +8820,7 @@ async function swapWithChange(inputUtxos, swapAmount, outputMint, recipientAta,
6877
8820
 
6878
8821
  // src/core/scanner.ts
6879
8822
  var import_bs582 = __toESM(require("bs58"), 1);
6880
- var import_web37 = require("@solana/web3.js");
8823
+ var import_web38 = require("@solana/web3.js");
6881
8824
  var import_spl_token2 = require("@solana/spl-token");
6882
8825
  var bs582 = import_bs582.default.default || import_bs582.default;
6883
8826
  var TRANSACT_TAG2 = 0;
@@ -6964,7 +8907,7 @@ function parseSwapOutputMint(data) {
6964
8907
  const mintBytes = data.slice(SWAP_OUTPUT_MINT_OFFSET, end);
6965
8908
  if (mintBytes.every((byte) => byte === 0)) return void 0;
6966
8909
  try {
6967
- return new import_web37.PublicKey(mintBytes).toBase58();
8910
+ return new import_web38.PublicKey(mintBytes).toBase58();
6968
8911
  } catch {
6969
8912
  return void 0;
6970
8913
  }
@@ -7054,7 +8997,7 @@ function parseSwapRecipientAta(data) {
7054
8997
  const recipientAtaBytes = data.slice(recipientAtaStart, recipientAtaEnd);
7055
8998
  if (recipientAtaBytes.every((byte) => byte === 0)) return void 0;
7056
8999
  try {
7057
- return new import_web37.PublicKey(recipientAtaBytes).toBase58();
9000
+ return new import_web38.PublicKey(recipientAtaBytes).toBase58();
7058
9001
  } catch {
7059
9002
  return void 0;
7060
9003
  }
@@ -7369,8 +9312,8 @@ async function scanTransactions(opts) {
7369
9312
  if (onChainAta) {
7370
9313
  try {
7371
9314
  const expectedAta = (0, import_spl_token2.getAssociatedTokenAddressSync)(
7372
- new import_web37.PublicKey(asset.mint),
7373
- new import_web37.PublicKey(walletPublicKey)
9315
+ new import_web38.PublicKey(asset.mint),
9316
+ new import_web38.PublicKey(walletPublicKey)
7374
9317
  ).toBase58();
7375
9318
  if (onChainAta === expectedAta) {
7376
9319
  isOurs = true;
@@ -7673,7 +9616,7 @@ function formatComplianceCsv(report) {
7673
9616
  }
7674
9617
 
7675
9618
  // src/core/wallet.ts
7676
- var import_web38 = require("@solana/web3.js");
9619
+ var import_web39 = require("@solana/web3.js");
7677
9620
  init_utxo();
7678
9621
  var UtxoWallet = class _UtxoWallet {
7679
9622
  constructor(viewingKey) {
@@ -7886,7 +9829,7 @@ var UtxoWallet = class _UtxoWallet {
7886
9829
  data.viewingKey ? new Uint8Array(data.viewingKey) : void 0
7887
9830
  );
7888
9831
  for (const w of data.wallets) {
7889
- const mint = new import_web38.PublicKey(w.mint);
9832
+ const mint = new import_web39.PublicKey(w.mint);
7890
9833
  for (const u of w.utxos) {
7891
9834
  wallet.addUtxo({
7892
9835
  amount: BigInt(u.amount),
@@ -7969,14 +9912,16 @@ var SimpleWallet = class {
7969
9912
  };
7970
9913
 
7971
9914
  // src/index.ts
7972
- var VERSION = "0.1.6";
9915
+ var VERSION = "0.1.5";
7973
9916
  var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
7974
9917
  // Annotate the CommonJS export names for ESM import in node:
7975
9918
  0 && (module.exports = {
7976
9919
  CLOAK_PROGRAM_ID,
7977
9920
  CloakError,
7978
9921
  CloakSDK,
9922
+ DEFAULT_CIRCUITS_URL,
7979
9923
  DEFAULT_TRANSACTION_CIRCUITS_URL,
9924
+ EXPECTED_CIRCUIT_HASHES,
7980
9925
  FIXED_FEE_LAMPORTS,
7981
9926
  LAMPORTS_PER_SOL,
7982
9927
  LocalStorageAdapter,
@@ -7999,6 +9944,7 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
7999
9944
  VARIABLE_FEE_NUMERATOR,
8000
9945
  VARIABLE_FEE_RATE,
8001
9946
  VERSION,
9947
+ areCircuitsAvailable,
8002
9948
  bigintToBytes32,
8003
9949
  bigintToHex,
8004
9950
  buildMerkleTree,
@@ -8012,15 +9958,30 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
8012
9958
  chainNoteFromBase64,
8013
9959
  chainNoteToBase64,
8014
9960
  classifyRelayError,
9961
+ cleanupStalePendingOperations,
9962
+ clearPendingDeposits,
9963
+ clearPendingWithdrawals,
8015
9964
  computeChainNoteHash,
9965
+ computeCommitment,
8016
9966
  computeExtDataHash,
8017
9967
  computeMerkleRoot,
9968
+ computeNullifier,
9969
+ computeNullifierAsync,
9970
+ computeNullifierSync,
9971
+ computeOutputsHash,
9972
+ computeOutputsHashAsync,
9973
+ computeOutputsHashSync,
8018
9974
  computeProofForLatestDeposit,
8019
9975
  computeProofFromChain,
8020
9976
  computeSignature,
9977
+ computeSwapOutputsHash,
9978
+ computeSwapOutputsHashAsync,
9979
+ computeSwapOutputsHashSync,
8021
9980
  computeUtxoCommitment,
8022
9981
  computeUtxoNullifier,
9982
+ copyNoteToClipboard,
8023
9983
  createCloakError,
9984
+ createDepositInstruction,
8024
9985
  createLogger,
8025
9986
  createUtxo,
8026
9987
  createZeroUtxo,
@@ -8040,52 +10001,78 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
8040
10001
  deriveViewingKeyFromUtxoPrivateKey,
8041
10002
  deserializeUtxo,
8042
10003
  detectNetworkFromRpcUrl,
10004
+ downloadNote,
10005
+ encodeNoteSimple,
8043
10006
  encryptCompactChainNote,
8044
10007
  encryptNoteForRecipient,
8045
10008
  encryptTransactionMetadata,
8046
10009
  encryptTransactionMetadataBundle,
8047
10010
  expandSpendKey,
8048
10011
  exportKeys,
10012
+ exportNote,
10013
+ exportWalletKeys,
8049
10014
  fetchCommitments,
8050
10015
  fetchRiskQuoteInstruction,
8051
10016
  fetchRiskQuoteIx,
10017
+ filterNotesByNetwork,
10018
+ filterWithdrawableNotes,
10019
+ findNoteByCommitment,
8052
10020
  formatAmount,
8053
10021
  formatComplianceCsv,
8054
10022
  formatErrorForLogging,
8055
10023
  formatSol,
8056
10024
  fullWithdraw,
8057
10025
  generateCloakKeys,
10026
+ generateCommitment,
10027
+ generateCommitmentAsync,
8058
10028
  generateMasterSeed,
10029
+ generateNote,
10030
+ generateNoteFromWallet,
8059
10031
  generateUtxoKeypair,
8060
10032
  generateViewingKeyPair,
10033
+ generateWithdrawRegularProof,
10034
+ generateWithdrawSwapProof,
8061
10035
  getAddressExplorerUrl,
8062
10036
  getCircuitsPath,
10037
+ getDefaultCircuitsPath,
8063
10038
  getDistributableAmount,
8064
10039
  getExplorerUrl,
8065
10040
  getNkFromUtxoPrivateKey,
8066
10041
  getNullifierPDA,
10042
+ getPendingOperationsSummary,
8067
10043
  getPublicKey,
10044
+ getPublicViewKey,
10045
+ getRecipientAmount,
8068
10046
  getRpcUrlForNetwork,
8069
10047
  getShieldPoolPDAs,
8070
10048
  getSwapStatePDA,
10049
+ getViewKey,
10050
+ hasPendingOperations,
8071
10051
  hexToBigint,
8072
10052
  hexToBytes,
8073
10053
  importKeys,
10054
+ importWalletKeys,
8074
10055
  isDebugEnabled,
8075
10056
  isRootNotFoundError,
8076
10057
  isValidHex,
8077
10058
  isValidRpcUrl,
8078
10059
  isValidSolanaAddress,
8079
10060
  isWithdrawAmountSufficient,
10061
+ isWithdrawable,
8080
10062
  keypairToAdapter,
10063
+ loadPendingDeposits,
10064
+ loadPendingWithdrawals,
8081
10065
  parseAmount,
8082
10066
  parseError,
10067
+ parseNote,
8083
10068
  parseRelayErrorResponse,
8084
10069
  parseTransactionError,
8085
10070
  partialWithdraw,
8086
10071
  poseidonHash,
8087
10072
  preflightCheck,
8088
10073
  preflightNullifiers,
10074
+ prepareEncryptedOutput,
10075
+ prepareEncryptedOutputForRecipient,
8089
10076
  proofToBytes,
8090
10077
  pubkeyToFieldElement,
8091
10078
  pubkeyToLimbs,
@@ -8093,11 +10080,16 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
8093
10080
  randomFieldElement,
8094
10081
  readMerkleTreeState,
8095
10082
  registerViewingKey,
10083
+ removePendingDeposit,
10084
+ removePendingWithdrawal,
10085
+ savePendingDeposit,
10086
+ savePendingWithdrawal,
8096
10087
  scanNotesForWallet,
8097
10088
  scanTransactions,
8098
10089
  sdkLogger,
8099
10090
  selectUtxos,
8100
10091
  sendTransaction,
10092
+ serializeNote,
8101
10093
  serializeUtxo,
8102
10094
  setCircuitsPath,
8103
10095
  setDebugMode,
@@ -8111,12 +10103,21 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
8111
10103
  transfer,
8112
10104
  truncate,
8113
10105
  tryDecryptNote,
10106
+ updateNoteWithDeposit,
10107
+ updatePendingDeposit,
10108
+ updatePendingWithdrawal,
8114
10109
  utxoBigintToBytes32,
8115
10110
  utxoEquals,
8116
10111
  utxoHexToBigint,
10112
+ validateDepositParams,
10113
+ validateNote,
8117
10114
  validateOutputsSum,
8118
10115
  validateRoot,
10116
+ validateTransfers,
8119
10117
  validateWalletConnected,
10118
+ validateWithdrawableNote,
10119
+ verifyAllCircuits,
10120
+ verifyCircuitIntegrity,
8120
10121
  verifyUtxos,
8121
10122
  waitForRoot,
8122
10123
  withTiming