@cloak.dev/sdk 0.1.4 → 0.1.6

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: () => computeCommitment2,
40
- computeNullifier: () => computeNullifier2,
39
+ computeCommitment: () => computeCommitment,
40
+ computeNullifier: () => computeNullifier,
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 computeCommitment2(utxo);
115
+ utxo.commitment = await computeCommitment(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 computeCommitment2(utxo);
130
+ utxo.commitment = await computeCommitment(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 computeCommitment2(utxo) {
142
+ async function computeCommitment(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 computeNullifier2(utxo) {
158
+ async function computeNullifier(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 computeCommitment2(utxo);
163
+ const commitment = utxo.commitment ?? await computeCommitment(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_web33.PublicKey(mintBytes);
205
+ const mintAddress = new import_web3.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 computeCommitment2(utxo);
214
+ utxo.commitment = await computeCommitment(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 computeCommitment2(a);
251
- const commitmentB = b.commitment ?? await computeCommitment2(b);
250
+ const commitmentA = a.commitment ?? await computeCommitment(a);
251
+ const commitmentB = b.commitment ?? await computeCommitment(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_web33, import_circomlibjs2, import_blake32, FIELD_MODULUS, UTXO_KEY_DOMAIN, NATIVE_SOL_MINT, poseidonInstance;
273
+ var import_web3, 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_web33 = require("@solana/web3.js");
277
+ import_web3 = 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_web33.PublicKey("So11111111111111111111111111111111111111112");
282
+ NATIVE_SOL_MINT = new import_web3.PublicKey("So11111111111111111111111111111111111111112");
283
283
  poseidonInstance = null;
284
284
  }
285
285
  });
@@ -290,9 +290,7 @@ __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,
294
293
  DEFAULT_TRANSACTION_CIRCUITS_URL: () => DEFAULT_TRANSACTION_CIRCUITS_URL,
295
- EXPECTED_CIRCUIT_HASHES: () => EXPECTED_CIRCUIT_HASHES,
296
294
  FIXED_FEE_LAMPORTS: () => FIXED_FEE_LAMPORTS,
297
295
  LAMPORTS_PER_SOL: () => LAMPORTS_PER_SOL,
298
296
  LocalStorageAdapter: () => LocalStorageAdapter,
@@ -301,18 +299,20 @@ __export(index_exports, {
301
299
  MemoryStorageAdapter: () => MemoryStorageAdapter,
302
300
  MerkleTree: () => MerkleTree,
303
301
  NATIVE_SOL_MINT: () => NATIVE_SOL_MINT,
302
+ RelayInternalError: () => RelayInternalError,
304
303
  RelayService: () => RelayService,
305
304
  RootNotFoundError: () => RootNotFoundError,
306
305
  SCANNER_SUPPORTS_TRANSACT_SWAP: () => SCANNER_SUPPORTS_TRANSACT_SWAP,
307
306
  SIGN_IN_MESSAGE: () => SIGN_IN_MESSAGE,
307
+ SanctionsQuoteError: () => SanctionsQuoteError,
308
308
  ShieldPoolErrors: () => ShieldPoolErrors,
309
309
  SimpleWallet: () => SimpleWallet,
310
+ UtxoAlreadySpentError: () => UtxoAlreadySpentError,
310
311
  UtxoWallet: () => UtxoWallet,
311
312
  VARIABLE_FEE_DENOMINATOR: () => VARIABLE_FEE_DENOMINATOR,
312
313
  VARIABLE_FEE_NUMERATOR: () => VARIABLE_FEE_NUMERATOR,
313
314
  VARIABLE_FEE_RATE: () => VARIABLE_FEE_RATE,
314
315
  VERSION: () => VERSION,
315
- areCircuitsAvailable: () => areCircuitsAvailable,
316
316
  bigintToBytes32: () => bigintToBytes32,
317
317
  bigintToHex: () => bigintToHex,
318
318
  buildMerkleTree: () => buildMerkleTree,
@@ -325,30 +325,16 @@ __export(index_exports, {
325
325
  calculateRelayFee: () => calculateRelayFee,
326
326
  chainNoteFromBase64: () => chainNoteFromBase64,
327
327
  chainNoteToBase64: () => chainNoteToBase64,
328
- cleanupStalePendingOperations: () => cleanupStalePendingOperations,
329
- clearPendingDeposits: () => clearPendingDeposits,
330
- clearPendingWithdrawals: () => clearPendingWithdrawals,
328
+ classifyRelayError: () => classifyRelayError,
331
329
  computeChainNoteHash: () => computeChainNoteHash,
332
- computeCommitment: () => computeCommitment,
333
330
  computeExtDataHash: () => computeExtDataHash,
334
331
  computeMerkleRoot: () => computeMerkleRoot,
335
- computeNullifier: () => computeNullifier,
336
- computeNullifierAsync: () => computeNullifierAsync,
337
- computeNullifierSync: () => computeNullifierSync,
338
- computeOutputsHash: () => computeOutputsHash,
339
- computeOutputsHashAsync: () => computeOutputsHashAsync,
340
- computeOutputsHashSync: () => computeOutputsHashSync,
341
332
  computeProofForLatestDeposit: () => computeProofForLatestDeposit,
342
333
  computeProofFromChain: () => computeProofFromChain,
343
334
  computeSignature: () => computeSignature,
344
- computeSwapOutputsHash: () => computeSwapOutputsHash,
345
- computeSwapOutputsHashAsync: () => computeSwapOutputsHashAsync,
346
- computeSwapOutputsHashSync: () => computeSwapOutputsHashSync,
347
- computeUtxoCommitment: () => computeCommitment2,
348
- computeUtxoNullifier: () => computeNullifier2,
349
- copyNoteToClipboard: () => copyNoteToClipboard,
335
+ computeUtxoCommitment: () => computeCommitment,
336
+ computeUtxoNullifier: () => computeNullifier,
350
337
  createCloakError: () => createCloakError,
351
- createDepositInstruction: () => createDepositInstruction,
352
338
  createLogger: () => createLogger,
353
339
  createUtxo: () => createUtxo,
354
340
  createZeroUtxo: () => createZeroUtxo,
@@ -368,77 +354,52 @@ __export(index_exports, {
368
354
  deriveViewingKeyFromUtxoPrivateKey: () => deriveViewingKeyFromUtxoPrivateKey,
369
355
  deserializeUtxo: () => deserializeUtxo,
370
356
  detectNetworkFromRpcUrl: () => detectNetworkFromRpcUrl,
371
- downloadNote: () => downloadNote,
372
- encodeNoteSimple: () => encodeNoteSimple,
373
357
  encryptCompactChainNote: () => encryptCompactChainNote,
374
358
  encryptNoteForRecipient: () => encryptNoteForRecipient,
375
359
  encryptTransactionMetadata: () => encryptTransactionMetadata,
376
360
  encryptTransactionMetadataBundle: () => encryptTransactionMetadataBundle,
377
361
  expandSpendKey: () => expandSpendKey,
378
362
  exportKeys: () => exportKeys,
379
- exportNote: () => exportNote,
380
- exportWalletKeys: () => exportWalletKeys,
381
363
  fetchCommitments: () => fetchCommitments,
382
364
  fetchRiskQuoteInstruction: () => fetchRiskQuoteInstruction,
383
365
  fetchRiskQuoteIx: () => fetchRiskQuoteIx,
384
- filterNotesByNetwork: () => filterNotesByNetwork,
385
- filterWithdrawableNotes: () => filterWithdrawableNotes,
386
- findNoteByCommitment: () => findNoteByCommitment,
387
366
  formatAmount: () => formatAmount,
388
367
  formatComplianceCsv: () => formatComplianceCsv,
389
368
  formatErrorForLogging: () => formatErrorForLogging,
390
369
  formatSol: () => formatSol,
391
370
  fullWithdraw: () => fullWithdraw,
392
371
  generateCloakKeys: () => generateCloakKeys,
393
- generateCommitment: () => generateCommitment,
394
- generateCommitmentAsync: () => generateCommitmentAsync,
395
372
  generateMasterSeed: () => generateMasterSeed,
396
- generateNote: () => generateNote,
397
- generateNoteFromWallet: () => generateNoteFromWallet,
398
373
  generateUtxoKeypair: () => generateUtxoKeypair,
399
374
  generateViewingKeyPair: () => generateViewingKeyPair,
400
- generateWithdrawRegularProof: () => generateWithdrawRegularProof,
401
- generateWithdrawSwapProof: () => generateWithdrawSwapProof,
402
375
  getAddressExplorerUrl: () => getAddressExplorerUrl,
403
376
  getCircuitsPath: () => getCircuitsPath,
404
- getDefaultCircuitsPath: () => getDefaultCircuitsPath,
405
377
  getDistributableAmount: () => getDistributableAmount,
406
378
  getExplorerUrl: () => getExplorerUrl,
407
379
  getNkFromUtxoPrivateKey: () => getNkFromUtxoPrivateKey,
408
380
  getNullifierPDA: () => getNullifierPDA,
409
- getPendingOperationsSummary: () => getPendingOperationsSummary,
410
381
  getPublicKey: () => getPublicKey,
411
- getPublicViewKey: () => getPublicViewKey,
412
- getRecipientAmount: () => getRecipientAmount,
413
382
  getRpcUrlForNetwork: () => getRpcUrlForNetwork,
414
383
  getShieldPoolPDAs: () => getShieldPoolPDAs,
415
384
  getSwapStatePDA: () => getSwapStatePDA,
416
- getViewKey: () => getViewKey,
417
- hasPendingOperations: () => hasPendingOperations,
418
385
  hexToBigint: () => hexToBigint,
419
386
  hexToBytes: () => hexToBytes,
420
387
  importKeys: () => importKeys,
421
- importWalletKeys: () => importWalletKeys,
422
388
  isDebugEnabled: () => isDebugEnabled,
423
389
  isRootNotFoundError: () => isRootNotFoundError,
424
390
  isValidHex: () => isValidHex,
425
391
  isValidRpcUrl: () => isValidRpcUrl,
426
392
  isValidSolanaAddress: () => isValidSolanaAddress,
427
393
  isWithdrawAmountSufficient: () => isWithdrawAmountSufficient,
428
- isWithdrawable: () => isWithdrawable,
429
394
  keypairToAdapter: () => keypairToAdapter,
430
- loadPendingDeposits: () => loadPendingDeposits,
431
- loadPendingWithdrawals: () => loadPendingWithdrawals,
432
395
  parseAmount: () => parseAmount,
433
396
  parseError: () => parseError,
434
- parseNote: () => parseNote,
435
397
  parseRelayErrorResponse: () => parseRelayErrorResponse,
436
398
  parseTransactionError: () => parseTransactionError,
437
399
  partialWithdraw: () => partialWithdraw,
438
400
  poseidonHash: () => poseidonHash,
439
401
  preflightCheck: () => preflightCheck,
440
- prepareEncryptedOutput: () => prepareEncryptedOutput,
441
- prepareEncryptedOutputForRecipient: () => prepareEncryptedOutputForRecipient,
402
+ preflightNullifiers: () => preflightNullifiers,
442
403
  proofToBytes: () => proofToBytes,
443
404
  pubkeyToFieldElement: () => pubkeyToFieldElement,
444
405
  pubkeyToLimbs: () => pubkeyToLimbs,
@@ -446,16 +407,11 @@ __export(index_exports, {
446
407
  randomFieldElement: () => randomFieldElement,
447
408
  readMerkleTreeState: () => readMerkleTreeState,
448
409
  registerViewingKey: () => registerViewingKey,
449
- removePendingDeposit: () => removePendingDeposit,
450
- removePendingWithdrawal: () => removePendingWithdrawal,
451
- savePendingDeposit: () => savePendingDeposit,
452
- savePendingWithdrawal: () => savePendingWithdrawal,
453
410
  scanNotesForWallet: () => scanNotesForWallet,
454
411
  scanTransactions: () => scanTransactions,
455
412
  sdkLogger: () => sdkLogger,
456
413
  selectUtxos: () => selectUtxos,
457
414
  sendTransaction: () => sendTransaction,
458
- serializeNote: () => serializeNote,
459
415
  serializeUtxo: () => serializeUtxo,
460
416
  setCircuitsPath: () => setCircuitsPath,
461
417
  setDebugMode: () => setDebugMode,
@@ -469,28 +425,20 @@ __export(index_exports, {
469
425
  transfer: () => transfer,
470
426
  truncate: () => truncate,
471
427
  tryDecryptNote: () => tryDecryptNote,
472
- updateNoteWithDeposit: () => updateNoteWithDeposit,
473
- updatePendingDeposit: () => updatePendingDeposit,
474
- updatePendingWithdrawal: () => updatePendingWithdrawal,
475
428
  utxoBigintToBytes32: () => bigintToBytes322,
476
429
  utxoEquals: () => utxoEquals,
477
430
  utxoHexToBigint: () => hexToBigint2,
478
- validateDepositParams: () => validateDepositParams,
479
- validateNote: () => validateNote,
480
431
  validateOutputsSum: () => validateOutputsSum,
481
432
  validateRoot: () => validateRoot,
482
- validateTransfers: () => validateTransfers,
483
433
  validateWalletConnected: () => validateWalletConnected,
484
- validateWithdrawableNote: () => validateWithdrawableNote,
485
- verifyAllCircuits: () => verifyAllCircuits,
486
- verifyCircuitIntegrity: () => verifyCircuitIntegrity,
434
+ verifyUtxos: () => verifyUtxos,
487
435
  waitForRoot: () => waitForRoot,
488
436
  withTiming: () => withTiming
489
437
  });
490
438
  module.exports = __toCommonJS(index_exports);
491
439
 
492
440
  // src/core/CloakSDK.ts
493
- var import_web35 = require("@solana/web3.js");
441
+ var import_web33 = require("@solana/web3.js");
494
442
 
495
443
  // src/core/types.ts
496
444
  var CloakError = class extends Error {
@@ -547,79 +495,6 @@ function hexToBigint(hex) {
547
495
  const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
548
496
  return BigInt("0x" + cleanHex);
549
497
  }
550
- async function computeCommitment(amount, r, sk_spend) {
551
- const [sk0, sk1] = splitTo2Limbs(sk_spend);
552
- const [r0, r1] = splitTo2Limbs(r);
553
- const pk_spend = await poseidonHash([sk0, sk1]);
554
- return await poseidonHash([amount, r0, r1, pk_spend]);
555
- }
556
- async function generateCommitmentAsync(amountLamports, r, skSpend) {
557
- const amount = BigInt(amountLamports);
558
- const rValue = hexToBigint(bytesToHex(r));
559
- const skValue = hexToBigint(bytesToHex(skSpend));
560
- return await computeCommitment(amount, rValue, skValue);
561
- }
562
- function generateCommitment(_amountLamports, _r, _skSpend) {
563
- throw new Error("generateCommitment is deprecated. Use generateCommitmentAsync instead.");
564
- }
565
- async function computeNullifier(sk_spend, leafIndex) {
566
- const [sk0, sk1] = splitTo2Limbs(sk_spend);
567
- return await poseidonHash([sk0, sk1, leafIndex]);
568
- }
569
- async function computeNullifierAsync(skSpend, leafIndex) {
570
- const skValue = typeof skSpend === "string" ? hexToBigint(skSpend) : hexToBigint(bytesToHex(skSpend));
571
- return await computeNullifier(skValue, BigInt(leafIndex));
572
- }
573
- function computeNullifierSync(_skSpend, _leafIndex) {
574
- throw new Error("computeNullifierSync is deprecated. Use computeNullifierAsync instead.");
575
- }
576
- async function computeOutputsHashAsync(outputs) {
577
- let hash = 0n;
578
- for (const output of outputs) {
579
- const [lo, hi] = pubkeyToLimbs(output.recipient);
580
- hash = await poseidonHash([hash, lo, hi, BigInt(output.amount)]);
581
- }
582
- return hash;
583
- }
584
- async function computeOutputsHash(outAddr, outAmount, outFlags) {
585
- let hash = 0n;
586
- for (let i = 0; i < 5; i++) {
587
- if (outFlags[i] === 1) {
588
- hash = await poseidonHash([hash, outAddr[i][0], outAddr[i][1], outAmount[i]]);
589
- }
590
- }
591
- return hash;
592
- }
593
- function computeOutputsHashSync(_outputs) {
594
- throw new Error("computeOutputsHashSync is deprecated. Use computeOutputsHashAsync instead.");
595
- }
596
- async function computeSwapOutputsHash(inputMintLimbs, outputMintLimbs, recipientAtaLimbs, minOutputAmount, publicAmount) {
597
- return await poseidonHash([
598
- inputMintLimbs[0],
599
- inputMintLimbs[1],
600
- outputMintLimbs[0],
601
- outputMintLimbs[1],
602
- recipientAtaLimbs[0],
603
- recipientAtaLimbs[1],
604
- minOutputAmount,
605
- publicAmount
606
- ]);
607
- }
608
- async function computeSwapOutputsHashAsync(inputMint, outputMint, recipientAta, minOutputAmount, amount) {
609
- const inputMintLimbs = pubkeyToLimbs(inputMint);
610
- const outputMintLimbs = pubkeyToLimbs(outputMint);
611
- const recipientAtaLimbs = pubkeyToLimbs(recipientAta);
612
- return await computeSwapOutputsHash(
613
- inputMintLimbs,
614
- outputMintLimbs,
615
- recipientAtaLimbs,
616
- BigInt(minOutputAmount),
617
- BigInt(amount)
618
- );
619
- }
620
- function computeSwapOutputsHashSync(_outputMint, _recipientAta, _minOutputAmount, _amount) {
621
- throw new Error("computeSwapOutputsHashSync is deprecated. Use computeSwapOutputsHashAsync instead.");
622
- }
623
498
  function bigintToBytes32(n) {
624
499
  const hex = n.toString(16).padStart(64, "0");
625
500
  const bytes = new Uint8Array(32);
@@ -892,223 +767,11 @@ function importKeys(exported) {
892
767
  return generateCloakKeys(masterSeed);
893
768
  }
894
769
 
895
- // src/utils/network.ts
896
- function detectNetworkFromRpcUrl(rpcUrl) {
897
- const url = rpcUrl || process.env.NEXT_PUBLIC_SOLANA_RPC_URL || "";
898
- const lowerUrl = url.toLowerCase();
899
- if (lowerUrl.includes("mainnet") || lowerUrl.includes("api.mainnet-beta") || lowerUrl.includes("mainnet-beta")) {
900
- return "mainnet";
901
- }
902
- if (lowerUrl.includes("testnet") || lowerUrl.includes("api.testnet")) {
903
- return "testnet";
904
- }
905
- if (lowerUrl.includes("devnet") || lowerUrl.includes("api.devnet")) {
906
- return "devnet";
907
- }
908
- if (lowerUrl.includes("localhost") || lowerUrl.includes("127.0.0.1") || lowerUrl.includes("local")) {
909
- return "localnet";
910
- }
911
- return "mainnet";
912
- }
913
- function getRpcUrlForNetwork(network) {
914
- switch (network) {
915
- case "mainnet":
916
- return "https://api.mainnet-beta.solana.com";
917
- case "devnet":
918
- return "https://api.devnet.solana.com";
919
- case "localnet":
920
- return "http://localhost:8899";
921
- default:
922
- return "https://api.mainnet-beta.solana.com";
923
- }
924
- }
925
- function isValidRpcUrl(url) {
926
- try {
927
- const parsed = new URL(url);
928
- return parsed.protocol === "http:" || parsed.protocol === "https:";
929
- } catch {
930
- return false;
931
- }
932
- }
933
- function getExplorerUrl(signature, network = "devnet") {
934
- const cluster = network === "mainnet" ? "" : `?cluster=${network}`;
935
- return `https://explorer.solana.com/tx/${signature}${cluster}`;
936
- }
937
- function getAddressExplorerUrl(address, network = "devnet") {
938
- const cluster = network === "mainnet" ? "" : `?cluster=${network}`;
939
- return `https://explorer.solana.com/address/${address}${cluster}`;
940
- }
941
-
942
- // src/utils/fees.ts
943
- var LAMPORTS_PER_SOL = 1e9;
944
- var FIXED_FEE_LAMPORTS = 5e6;
945
- var VARIABLE_FEE_NUMERATOR = 3;
946
- var VARIABLE_FEE_DENOMINATOR = 1e3;
947
- var VARIABLE_FEE_RATE = VARIABLE_FEE_NUMERATOR / VARIABLE_FEE_DENOMINATOR;
948
- var MIN_DEPOSIT_LAMPORTS = 1e7;
949
- function calculateFee(amountLamports) {
950
- const variableFee = Math.floor(amountLamports * VARIABLE_FEE_NUMERATOR / VARIABLE_FEE_DENOMINATOR);
951
- return FIXED_FEE_LAMPORTS + variableFee;
952
- }
953
- function calculateFeeBigint(amountLamports) {
954
- const fixed = BigInt(FIXED_FEE_LAMPORTS);
955
- const variable = amountLamports * BigInt(VARIABLE_FEE_NUMERATOR) / BigInt(VARIABLE_FEE_DENOMINATOR);
956
- return fixed + variable;
957
- }
958
- function isWithdrawAmountSufficient(amountLamports) {
959
- return amountLamports > calculateFeeBigint(amountLamports);
960
- }
961
- function getDistributableAmount(amountLamports) {
962
- return amountLamports - calculateFee(amountLamports);
963
- }
964
- function formatAmount(lamports, decimals = 9) {
965
- return (lamports / LAMPORTS_PER_SOL).toFixed(decimals);
966
- }
967
- function parseAmount(sol) {
968
- const num = parseFloat(sol);
969
- if (isNaN(num) || num < 0) {
970
- throw new Error(`Invalid SOL amount: ${sol}`);
971
- }
972
- return Math.floor(num * LAMPORTS_PER_SOL);
973
- }
974
- function validateOutputsSum(outputs, expectedTotal) {
975
- const sum = outputs.reduce((acc, out) => acc + out.amount, 0);
976
- return sum === expectedTotal;
977
- }
978
- function calculateRelayFee(amountLamports, feeBps) {
979
- if (feeBps < 0 || feeBps > 1e4) {
980
- throw new Error("Fee basis points must be between 0 and 10000");
981
- }
982
- return Math.floor(amountLamports * feeBps / 1e4);
983
- }
984
-
985
- // src/core/note-manager.ts
986
- async function generateNote(amountLamports, network) {
987
- const actualNetwork = network || detectNetworkFromRpcUrl();
988
- const skSpend = randomBytes(32);
989
- const rBytes = randomBytes(32);
990
- const commitmentBigint = await generateCommitmentAsync(amountLamports, rBytes, skSpend);
991
- const commitmentHex = commitmentBigint.toString(16).padStart(64, "0");
992
- const skSpendHex = bytesToHex(skSpend);
993
- const rHex = bytesToHex(rBytes);
994
- return {
995
- version: "1.0",
996
- amount: amountLamports,
997
- commitment: commitmentHex,
998
- sk_spend: skSpendHex,
999
- r: rHex,
1000
- timestamp: Date.now(),
1001
- network: actualNetwork
1002
- };
1003
- }
1004
- async function generateNoteFromWallet(amountLamports, keys, network) {
1005
- const actualNetwork = network || detectNetworkFromRpcUrl();
1006
- const rBytes = randomBytes(32);
1007
- const sk_spend = hexToBytes(keys.spend.sk_spend_hex);
1008
- const commitmentBigint = await generateCommitmentAsync(amountLamports, rBytes, sk_spend);
1009
- const commitmentHex = commitmentBigint.toString(16).padStart(64, "0");
1010
- return {
1011
- version: "2.0",
1012
- amount: amountLamports,
1013
- commitment: commitmentHex,
1014
- sk_spend: keys.spend.sk_spend_hex,
1015
- r: bytesToHex(rBytes),
1016
- timestamp: Date.now(),
1017
- network: actualNetwork
1018
- };
1019
- }
1020
- function parseNote(jsonString) {
1021
- const note = JSON.parse(jsonString);
1022
- if (!note.version || !note.amount || !note.commitment || !note.sk_spend || !note.r) {
1023
- throw new CloakError(
1024
- "Invalid note format: missing required fields",
1025
- "validation",
1026
- false
1027
- );
1028
- }
1029
- if (!/^[0-9a-f]{64}$/i.test(note.commitment)) {
1030
- throw new CloakError("Invalid commitment format", "validation", false);
1031
- }
1032
- if (!/^[0-9a-f]{64}$/i.test(note.sk_spend)) {
1033
- throw new CloakError("Invalid sk_spend format", "validation", false);
1034
- }
1035
- if (!/^[0-9a-f]{64}$/i.test(note.r)) {
1036
- throw new CloakError("Invalid r format", "validation", false);
1037
- }
1038
- return note;
1039
- }
1040
- function exportNote(note, pretty = false) {
1041
- return pretty ? JSON.stringify(note, null, 2) : JSON.stringify(note);
1042
- }
1043
- function isWithdrawable(note) {
1044
- return !!(note.depositSignature && note.leafIndex !== void 0 && note.root);
1045
- }
1046
- function updateNoteWithDeposit(note, depositInfo) {
1047
- const updated = {
1048
- ...note,
1049
- depositSignature: depositInfo.signature,
1050
- depositSlot: depositInfo.slot,
1051
- leafIndex: depositInfo.leafIndex,
1052
- root: depositInfo.root
1053
- };
1054
- if (depositInfo.merkleProof) {
1055
- updated.merkleProof = depositInfo.merkleProof;
1056
- }
1057
- return updated;
1058
- }
1059
- function findNoteByCommitment(notes, commitment) {
1060
- return notes.find((n) => n.commitment === commitment);
1061
- }
1062
- function filterNotesByNetwork(notes, network) {
1063
- return notes.filter((n) => n.network === network);
1064
- }
1065
- function filterWithdrawableNotes(notes) {
1066
- return notes.filter(isWithdrawable);
1067
- }
1068
- function exportWalletKeys(keys) {
1069
- return exportKeys(keys);
1070
- }
1071
- function importWalletKeys(keysJson) {
1072
- return importKeys(keysJson);
1073
- }
1074
- function getPublicViewKey(keys) {
1075
- return keys.view.pvk_hex;
1076
- }
1077
- function getViewKey(keys) {
1078
- return keys.view;
1079
- }
1080
- var calculateFee2 = calculateFee;
1081
- function getDistributableAmount2(amountLamports) {
1082
- return amountLamports - calculateFee2(amountLamports);
1083
- }
1084
- function getRecipientAmount(amountLamports) {
1085
- return getDistributableAmount2(amountLamports);
1086
- }
1087
-
1088
770
  // src/core/storage.ts
1089
771
  var MemoryStorageAdapter = class {
1090
772
  constructor() {
1091
- this.notes = /* @__PURE__ */ new Map();
1092
773
  this.keys = null;
1093
774
  }
1094
- saveNote(note) {
1095
- this.notes.set(note.commitment, note);
1096
- }
1097
- loadAllNotes() {
1098
- return Array.from(this.notes.values());
1099
- }
1100
- updateNote(commitment, updates) {
1101
- const existing = this.notes.get(commitment);
1102
- if (existing) {
1103
- this.notes.set(commitment, { ...existing, ...updates });
1104
- }
1105
- }
1106
- deleteNote(commitment) {
1107
- this.notes.delete(commitment);
1108
- }
1109
- clearAllNotes() {
1110
- this.notes.clear();
1111
- }
1112
775
  saveKeys(keys) {
1113
776
  this.keys = keys;
1114
777
  }
@@ -1119,10 +782,10 @@ var MemoryStorageAdapter = class {
1119
782
  this.keys = null;
1120
783
  }
1121
784
  };
1122
- var LocalStorageAdapter = class {
1123
- constructor(notesKey = "cloak_notes", keysKey = "cloak_wallet_keys") {
1124
- this.notesKey = notesKey;
785
+ var _LocalStorageAdapter = class _LocalStorageAdapter {
786
+ constructor(keysKey = "cloak_wallet_keys") {
1125
787
  this.keysKey = keysKey;
788
+ _LocalStorageAdapter.runLegacyPurgeOnce();
1126
789
  }
1127
790
  getStorage() {
1128
791
  if (typeof globalThis !== "undefined" && globalThis.localStorage) {
@@ -1130,47 +793,6 @@ var LocalStorageAdapter = class {
1130
793
  }
1131
794
  return null;
1132
795
  }
1133
- saveNote(note) {
1134
- const storage = this.getStorage();
1135
- if (!storage) throw new Error("localStorage not available");
1136
- const notes = this.loadAllNotes();
1137
- notes.push(note);
1138
- storage.setItem(this.notesKey, JSON.stringify(notes));
1139
- }
1140
- loadAllNotes() {
1141
- const storage = this.getStorage();
1142
- if (!storage) return [];
1143
- const stored = storage.getItem(this.notesKey);
1144
- if (!stored) return [];
1145
- try {
1146
- return JSON.parse(stored);
1147
- } catch {
1148
- return [];
1149
- }
1150
- }
1151
- updateNote(commitment, updates) {
1152
- const storage = this.getStorage();
1153
- if (!storage) return;
1154
- const notes = this.loadAllNotes();
1155
- const index = notes.findIndex((n) => n.commitment === commitment);
1156
- if (index !== -1) {
1157
- notes[index] = { ...notes[index], ...updates };
1158
- storage.setItem(this.notesKey, JSON.stringify(notes));
1159
- }
1160
- }
1161
- deleteNote(commitment) {
1162
- const storage = this.getStorage();
1163
- if (!storage) return;
1164
- const notes = this.loadAllNotes();
1165
- const filtered = notes.filter((n) => n.commitment !== commitment);
1166
- storage.setItem(this.notesKey, JSON.stringify(filtered));
1167
- }
1168
- clearAllNotes() {
1169
- const storage = this.getStorage();
1170
- if (storage) {
1171
- storage.removeItem(this.notesKey);
1172
- }
1173
- }
1174
796
  saveKeys(keys) {
1175
797
  const storage = this.getStorage();
1176
798
  if (!storage) throw new Error("localStorage not available");
@@ -1193,126 +815,64 @@ var LocalStorageAdapter = class {
1193
815
  storage.removeItem(this.keysKey);
1194
816
  }
1195
817
  }
1196
- };
1197
-
1198
- // src/utils/validation.ts
1199
- var import_web3 = require("@solana/web3.js");
1200
- function isValidSolanaAddress(address) {
1201
- try {
1202
- new import_web3.PublicKey(address);
1203
- return true;
1204
- } catch {
1205
- return false;
1206
- }
1207
- }
1208
- function validateNote(note) {
1209
- if (!note || typeof note !== "object") {
1210
- throw new Error("Note must be an object");
1211
- }
1212
- const requiredFields = ["version", "amount", "commitment", "sk_spend", "r", "timestamp", "network"];
1213
- for (const field of requiredFields) {
1214
- if (!(field in note)) {
1215
- throw new Error(`Missing required field: ${field}`);
1216
- }
1217
- }
1218
- if (typeof note.version !== "string") {
1219
- throw new Error("Version must be a string");
1220
- }
1221
- if (typeof note.amount !== "number" || note.amount <= 0) {
1222
- throw new Error("Amount must be a positive number");
1223
- }
1224
- if (!isValidHex(note.commitment, 32)) {
1225
- throw new Error("Invalid commitment format (expected 64 hex characters)");
1226
- }
1227
- if (!isValidHex(note.sk_spend, 32)) {
1228
- throw new Error("Invalid sk_spend format (expected 64 hex characters)");
1229
- }
1230
- if (!isValidHex(note.r, 32)) {
1231
- throw new Error("Invalid r format (expected 64 hex characters)");
1232
- }
1233
- if (typeof note.timestamp !== "number" || note.timestamp <= 0) {
1234
- throw new Error("Timestamp must be a positive number");
1235
- }
1236
- if (!["localnet", "devnet", "testnet", "mainnet"].includes(note.network)) {
1237
- throw new Error("Network must be localnet, devnet, testnet, or mainnet");
1238
- }
1239
- if (note.depositSignature !== void 0 && typeof note.depositSignature !== "string") {
1240
- throw new Error("Deposit signature must be a string");
1241
- }
1242
- if (note.depositSlot !== void 0 && typeof note.depositSlot !== "number") {
1243
- throw new Error("Deposit slot must be a number");
1244
- }
1245
- if (note.leafIndex !== void 0) {
1246
- if (typeof note.leafIndex !== "number" || note.leafIndex < 0) {
1247
- throw new Error("Leaf index must be a non-negative number");
1248
- }
1249
- }
1250
- if (note.root !== void 0 && !isValidHex(note.root, 32)) {
1251
- throw new Error("Invalid root format (expected 64 hex characters)");
1252
- }
1253
- if (note.merkleProof !== void 0) {
1254
- if (!Array.isArray(note.merkleProof.pathElements)) {
1255
- throw new Error("Merkle proof pathElements must be an array");
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;
1256
827
  }
1257
- if (!Array.isArray(note.merkleProof.pathIndices)) {
1258
- throw new Error("Merkle proof pathIndices must be an array");
828
+ const storage = globalThis.localStorage;
829
+ if (storage.getItem(_LocalStorageAdapter.LEGACY_PURGE_SENTINEL_KEY)) {
830
+ return;
1259
831
  }
1260
- if (note.merkleProof.pathElements.length !== note.merkleProof.pathIndices.length) {
1261
- throw new Error("Merkle proof pathElements and pathIndices must have same length");
832
+ storage.removeItem(_LocalStorageAdapter.LEGACY_NOTES_KEY);
833
+ try {
834
+ storage.setItem(_LocalStorageAdapter.LEGACY_PURGE_SENTINEL_KEY, "1");
835
+ } catch {
1262
836
  }
1263
837
  }
1264
- }
1265
- function parseNote2(jsonString) {
1266
- let parsed;
1267
- try {
1268
- parsed = JSON.parse(jsonString);
1269
- } catch (error) {
1270
- throw new Error("Invalid JSON format");
1271
- }
1272
- validateNote(parsed);
1273
- return parsed;
1274
- }
1275
- function validateWithdrawableNote(note) {
1276
- if (!note.depositSignature) {
1277
- throw new Error("Note must be deposited before withdrawal (missing depositSignature)");
1278
- }
1279
- if (note.leafIndex === void 0) {
1280
- throw new Error("Note must be deposited before withdrawal (missing leafIndex)");
1281
- }
1282
- if (!note.root) {
1283
- throw new Error("Note must have historical root for withdrawal");
1284
- }
1285
- if (!note.merkleProof) {
1286
- throw new Error("Note must have Merkle proof for withdrawal");
1287
- }
1288
- if (note.merkleProof.pathElements.length === 0) {
1289
- throw new Error("Merkle proof is empty");
1290
- }
1291
- }
1292
- function validateTransfers(recipients, totalAmount) {
1293
- if (recipients.length === 0) {
1294
- throw new Error("At least one recipient is required");
1295
- }
1296
- if (recipients.length > 5) {
1297
- throw new Error("Maximum 5 recipients allowed");
1298
- }
1299
- for (let i = 0; i < recipients.length; i++) {
1300
- const transfer2 = recipients[i];
1301
- const isPublicKeyLike = transfer2.recipient && typeof transfer2.recipient.toBase58 === "function" && typeof transfer2.recipient.toBuffer === "function";
1302
- if (!isPublicKeyLike) {
1303
- throw new Error(`Recipient ${i} must be a PublicKey (got ${typeof transfer2.recipient})`);
1304
- }
1305
- if (typeof transfer2.amount !== "number" || transfer2.amount <= 0) {
1306
- throw new Error(`Recipient ${i} amount must be a positive number`);
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;
1307
858
  }
859
+ globalThis.localStorage.removeItem(notesKey);
1308
860
  }
1309
- const sum = recipients.reduce((acc, t) => acc + t.amount, 0);
1310
- if (sum !== totalAmount) {
1311
- throw new Error(
1312
- `Recipients sum (${sum}) does not match expected total (${totalAmount})`
1313
- );
1314
- }
1315
- }
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");
1316
876
 
1317
877
  // src/utils/errors.ts
1318
878
  var RootNotFoundError = class extends Error {
@@ -1339,6 +899,71 @@ function parseRelayErrorResponse(responseText) {
1339
899
  return { isRootNotFound: false };
1340
900
  }
1341
901
  }
902
+ var UtxoAlreadySpentError = class extends Error {
903
+ constructor(message, spentUtxoCommitments = []) {
904
+ super(message);
905
+ this.spentUtxoCommitments = spentUtxoCommitments;
906
+ this.name = "UtxoAlreadySpentError";
907
+ }
908
+ };
909
+ var SanctionsQuoteError = class extends Error {
910
+ constructor(message, onChainCode, subKind) {
911
+ super(message);
912
+ this.onChainCode = onChainCode;
913
+ this.subKind = subKind;
914
+ this.name = "SanctionsQuoteError";
915
+ }
916
+ };
917
+ var RelayInternalError = class extends Error {
918
+ constructor(message, relayMessage, httpStatus, cachedTreeFromChain) {
919
+ super(message);
920
+ this.relayMessage = relayMessage;
921
+ this.httpStatus = httpStatus;
922
+ this.cachedTreeFromChain = cachedTreeFromChain;
923
+ this.name = "RelayInternalError";
924
+ }
925
+ };
926
+ function classifyRelayError(responseText, httpStatus) {
927
+ let inner = "";
928
+ try {
929
+ const parsed = JSON.parse(responseText);
930
+ inner = parsed?.message ?? "";
931
+ } catch {
932
+ inner = responseText;
933
+ }
934
+ const haystack = inner.toLowerCase();
935
+ if (haystack.includes("0x1020") || haystack.includes("doublespend")) {
936
+ return new UtxoAlreadySpentError(
937
+ "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
+ );
939
+ }
940
+ if (haystack.includes("0x10b0") || haystack.includes("rangequoteexpired")) {
941
+ return new SanctionsQuoteError(
942
+ "Range sanctions quote expired before the on-chain program processed the transaction. The relay's quote-expiry window is too short relative to proof-gen + confirmation latency, or on-chain clock drifted. Retry the send; the relay will issue a fresh quote.",
943
+ 4272,
944
+ "expired"
945
+ );
946
+ }
947
+ if (haystack.includes("0x10b2") || haystack.includes("rangequotewalletmismatch")) {
948
+ return new SanctionsQuoteError(
949
+ "Range sanctions quote's wallet doesn't match the on-chain expected wallet. Indicates a relay config drift \u2014 the sender used for quote signing doesn't match the program's pool.range_signer / fee payer. Relay operators must fix; caller retry will not help.",
950
+ 4274,
951
+ "wallet_mismatch"
952
+ );
953
+ }
954
+ if (haystack.includes("0x10b3") || haystack.includes("rangequotemissinged25519")) {
955
+ return new SanctionsQuoteError(
956
+ "Transaction reached the on-chain program without the required Ed25519 sanctions quote instruction. This is a relay bug \u2014 the relay constructed the tx without attaching the signed quote. Caller retry will not help.",
957
+ 4275,
958
+ "missing_ix"
959
+ );
960
+ }
961
+ return new RelayInternalError(
962
+ `Relay returned ${httpStatus ?? "an error"}: ${inner || responseText}`,
963
+ inner || responseText,
964
+ httpStatus
965
+ );
966
+ }
1342
967
  var ShieldPoolErrors = {
1343
968
  // Root management errors
1344
969
  4096: "Invalid Merkle root",
@@ -2143,7 +1768,6 @@ function formatErrorForLogging(error) {
2143
1768
  }
2144
1769
 
2145
1770
  // src/services/RelayService.ts
2146
- var import_sha2 = require("@noble/hashes/sha2");
2147
1771
  var RelayService = class {
2148
1772
  /**
2149
1773
  * Create a new Relay Service client
@@ -2512,88 +2136,24 @@ var RelayService = class {
2512
2136
  }
2513
2137
  };
2514
2138
 
2515
- // src/solana/instructions.ts
2139
+ // src/utils/pda.ts
2516
2140
  var import_web32 = require("@solana/web3.js");
2517
- function createDepositInstruction(params) {
2518
- if (params.commitment.length !== 32) {
2519
- throw new Error(
2520
- `Invalid commitment length: ${params.commitment.length} (expected 32 bytes)`
2521
- );
2522
- }
2523
- if (params.amount <= 0) {
2524
- throw new Error("Amount must be positive");
2525
- }
2526
- const discriminant = new Uint8Array([1]);
2527
- const amountBytes = new Uint8Array(8);
2528
- new DataView(amountBytes.buffer).setBigUint64(
2529
- 0,
2530
- BigInt(params.amount),
2531
- true
2532
- // little-endian
2141
+ init_utxo();
2142
+ function getShieldPoolPDAs(programId, mint = NATIVE_SOL_MINT) {
2143
+ const pid = programId || CLOAK_PROGRAM_ID;
2144
+ const [pool] = import_web32.PublicKey.findProgramAddressSync(
2145
+ [Buffer.from("pool"), mint.toBuffer()],
2146
+ pid
2533
2147
  );
2534
- const data = new Uint8Array(41);
2535
- data.set(discriminant, 0);
2536
- data.set(amountBytes, 1);
2537
- data.set(params.commitment, 9);
2538
- return new import_web32.TransactionInstruction({
2539
- programId: params.programId,
2540
- keys: [
2541
- // Account 0: Payer (signer, writable) - pays for transaction
2542
- { pubkey: params.payer, isSigner: true, isWritable: true },
2543
- // Account 1: Pool (writable) - receives SOL
2544
- { pubkey: params.pool, isSigner: false, isWritable: true },
2545
- // Account 2: System Program (readonly) - for transfers
2546
- { pubkey: import_web32.SystemProgram.programId, isSigner: false, isWritable: false },
2547
- // Account 3: Merkle Tree (writable) - stores on-chain Merkle tree
2548
- { pubkey: params.merkleTree, isSigner: false, isWritable: true }
2549
- ],
2550
- data: Buffer.from(data)
2551
- });
2552
- }
2553
- function validateDepositParams(params) {
2554
- if (!(params.programId instanceof import_web32.PublicKey)) {
2555
- throw new Error("programId must be a PublicKey");
2556
- }
2557
- if (!(params.payer instanceof import_web32.PublicKey)) {
2558
- throw new Error("payer must be a PublicKey");
2559
- }
2560
- if (!(params.pool instanceof import_web32.PublicKey)) {
2561
- throw new Error("pool must be a PublicKey");
2562
- }
2563
- if (!(params.merkleTree instanceof import_web32.PublicKey)) {
2564
- throw new Error("merkleTree must be a PublicKey");
2565
- }
2566
- if (typeof params.amount !== "number" || params.amount <= 0) {
2567
- throw new Error("amount must be a positive number");
2568
- }
2569
- if (!(params.commitment instanceof Uint8Array)) {
2570
- throw new Error("commitment must be a Uint8Array");
2571
- }
2572
- if (params.commitment.length !== 32) {
2573
- throw new Error(
2574
- `commitment must be 32 bytes (got ${params.commitment.length})`
2575
- );
2576
- }
2577
- }
2578
-
2579
- // src/utils/pda.ts
2580
- var import_web34 = require("@solana/web3.js");
2581
- init_utxo();
2582
- function getShieldPoolPDAs(programId, mint = NATIVE_SOL_MINT) {
2583
- const pid = programId || CLOAK_PROGRAM_ID;
2584
- const [pool] = import_web34.PublicKey.findProgramAddressSync(
2585
- [Buffer.from("pool"), mint.toBuffer()],
2586
- pid
2587
- );
2588
- const [merkleTree] = import_web34.PublicKey.findProgramAddressSync(
2148
+ const [merkleTree] = import_web32.PublicKey.findProgramAddressSync(
2589
2149
  [Buffer.from("merkle_tree"), mint.toBuffer()],
2590
2150
  pid
2591
2151
  );
2592
- const [treasury] = import_web34.PublicKey.findProgramAddressSync(
2152
+ const [treasury] = import_web32.PublicKey.findProgramAddressSync(
2593
2153
  [Buffer.from("treasury"), mint.toBuffer()],
2594
2154
  pid
2595
2155
  );
2596
- const [vaultAuthority] = import_web34.PublicKey.findProgramAddressSync(
2156
+ const [vaultAuthority] = import_web32.PublicKey.findProgramAddressSync(
2597
2157
  [Buffer.from("vault_authority"), mint.toBuffer()],
2598
2158
  pid
2599
2159
  );
@@ -2609,7 +2169,7 @@ function getNullifierPDA(poolPubkey, nullifier, programId) {
2609
2169
  if (nullifier.length !== 32) {
2610
2170
  throw new Error(`Nullifier must be 32 bytes, got ${nullifier.length}`);
2611
2171
  }
2612
- return import_web34.PublicKey.findProgramAddressSync(
2172
+ return import_web32.PublicKey.findProgramAddressSync(
2613
2173
  [Buffer.from("nullifier"), poolPubkey.toBuffer(), Buffer.from(nullifier)],
2614
2174
  pid
2615
2175
  );
@@ -2619,256 +2179,12 @@ function getSwapStatePDA(poolPubkey, nullifier, programId) {
2619
2179
  if (nullifier.length !== 32) {
2620
2180
  throw new Error(`Nullifier must be 32 bytes, got ${nullifier.length}`);
2621
2181
  }
2622
- return import_web34.PublicKey.findProgramAddressSync(
2182
+ return import_web32.PublicKey.findProgramAddressSync(
2623
2183
  [Buffer.from("swap_state"), poolPubkey.toBuffer(), Buffer.from(nullifier)],
2624
2184
  pid
2625
2185
  );
2626
2186
  }
2627
2187
 
2628
- // src/utils/proof-generation.ts
2629
- var snarkjs = __toESM(require("snarkjs"), 1);
2630
- var IS_REACT_NATIVE = typeof navigator !== "undefined" && navigator.product === "ReactNative";
2631
- var IS_BROWSER = IS_REACT_NATIVE || typeof window !== "undefined" || typeof globalThis !== "undefined" && typeof globalThis.document !== "undefined";
2632
- var DEFAULT_CIRCUITS_URL = "https://cloak-circuits.s3.us-east-1.amazonaws.com/circuits/0.1.0";
2633
- var SKIP_CIRCUIT_VERIFY_ENV = typeof process !== "undefined" && process.env && process.env.CLOAK_SKIP_CIRCUIT_INTEGRITY_CHECK === "1";
2634
- function isUrl(path) {
2635
- return path.startsWith("http://") || path.startsWith("https://");
2636
- }
2637
- function resolveCircuitsUrl(circuitsPath2) {
2638
- if (circuitsPath2.trim() !== "" && circuitsPath2.trim() !== DEFAULT_CIRCUITS_URL) {
2639
- return DEFAULT_CIRCUITS_URL;
2640
- }
2641
- return DEFAULT_CIRCUITS_URL;
2642
- }
2643
- async function fetchFileAsBuffer(url) {
2644
- const response = await fetch(url);
2645
- if (!response.ok) {
2646
- throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
2647
- }
2648
- const arrayBuffer = await response.arrayBuffer();
2649
- return new Uint8Array(arrayBuffer);
2650
- }
2651
- async function sha256Hex(data) {
2652
- if (IS_BROWSER) {
2653
- const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
2654
- const buffer = bytes.buffer.slice(
2655
- bytes.byteOffset,
2656
- bytes.byteOffset + bytes.byteLength
2657
- );
2658
- const hashBuffer = await crypto.subtle.digest("SHA-256", buffer);
2659
- return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
2660
- }
2661
- const nodeCrypto = await getNodeCrypto();
2662
- return nodeCrypto.createHash("sha256").update(data).digest("hex");
2663
- }
2664
- var _nodeCrypto = null;
2665
- function nodeRequire(moduleName) {
2666
- if (IS_BROWSER) {
2667
- throw new Error(`Node.js ${moduleName} module not available in browser/React Native`);
2668
- }
2669
- try {
2670
- const requireFunc = new Function("moduleName", "return require(moduleName)");
2671
- return requireFunc(moduleName);
2672
- } catch {
2673
- throw new Error(`Cannot load Node.js module ${moduleName} synchronously in ESM.`);
2674
- }
2675
- }
2676
- async function getNodeCrypto() {
2677
- if (_nodeCrypto) return _nodeCrypto;
2678
- _nodeCrypto = nodeRequire("crypto");
2679
- return _nodeCrypto;
2680
- }
2681
- async function generateWithdrawRegularProof(inputs, circuitsPath2) {
2682
- const circuitsUrl = resolveCircuitsUrl(circuitsPath2);
2683
- let wasmInput;
2684
- let zkeyInput;
2685
- if (IS_BROWSER) {
2686
- wasmInput = `${circuitsUrl}/withdraw_regular_js/withdraw_regular.wasm`;
2687
- zkeyInput = `${circuitsUrl}/withdraw_regular_final.zkey`;
2688
- } else {
2689
- const wasmUrl = `${circuitsUrl}/withdraw_regular_js/withdraw_regular.wasm`;
2690
- const zkeyUrl = `${circuitsUrl}/withdraw_regular_final.zkey`;
2691
- const [wasmData, zkeyData] = await Promise.all([
2692
- fetchFileAsBuffer(wasmUrl),
2693
- fetchFileAsBuffer(zkeyUrl)
2694
- ]);
2695
- wasmInput = wasmData;
2696
- zkeyInput = zkeyData;
2697
- }
2698
- const verification = await verifyCircuitIntegrity(circuitsUrl, "withdraw_regular");
2699
- if (!verification.valid) {
2700
- throw new Error(
2701
- `withdraw_regular circuit integrity verification failed: ${verification.error ?? "hash mismatch"}`
2702
- );
2703
- }
2704
- const circuitInputs = {
2705
- // Public signals
2706
- root: inputs.root.toString(),
2707
- nullifier: inputs.nullifier.toString(),
2708
- outputs_hash: inputs.outputs_hash.toString(),
2709
- public_amount: inputs.public_amount.toString(),
2710
- // Private common inputs
2711
- amount: inputs.amount.toString(),
2712
- leaf_index: inputs.leaf_index.toString(),
2713
- sk: inputs.sk.map((x) => x.toString()),
2714
- r: inputs.r.map((x) => x.toString()),
2715
- pathElements: inputs.pathElements.map((x) => x.toString()),
2716
- pathIndices: inputs.pathIndices,
2717
- // Outputs
2718
- num_outputs: inputs.num_outputs,
2719
- out_addr: inputs.out_addr.map((addr) => addr.map((x) => x.toString())),
2720
- out_amount: inputs.out_amount.map((x) => x.toString()),
2721
- out_flags: inputs.out_flags,
2722
- // Fee calculation
2723
- var_fee: inputs.var_fee.toString(),
2724
- rem: inputs.rem.toString()
2725
- };
2726
- const { proof, publicSignals } = await snarkjs.groth16.fullProve(circuitInputs, wasmInput, zkeyInput);
2727
- const proofBytes = proofToBytes(proof);
2728
- return {
2729
- proof,
2730
- publicSignals,
2731
- proofBytes,
2732
- publicInputsBytes: new Uint8Array(0)
2733
- // Not used, kept for compatibility
2734
- };
2735
- }
2736
- async function generateWithdrawSwapProof(inputs, circuitsPath2) {
2737
- const circuitsUrl = resolveCircuitsUrl(circuitsPath2);
2738
- let wasmInput;
2739
- let zkeyInput;
2740
- if (IS_BROWSER) {
2741
- wasmInput = `${circuitsUrl}/withdraw_swap_js/withdraw_swap.wasm`;
2742
- zkeyInput = `${circuitsUrl}/withdraw_swap_final.zkey`;
2743
- } else {
2744
- const wasmUrl = `${circuitsUrl}/withdraw_swap_js/withdraw_swap.wasm`;
2745
- const zkeyUrl = `${circuitsUrl}/withdraw_swap_final.zkey`;
2746
- const [wasmData, zkeyData] = await Promise.all([
2747
- fetchFileAsBuffer(wasmUrl),
2748
- fetchFileAsBuffer(zkeyUrl)
2749
- ]);
2750
- wasmInput = wasmData;
2751
- zkeyInput = zkeyData;
2752
- }
2753
- const verification = await verifyCircuitIntegrity(circuitsUrl, "withdraw_swap");
2754
- if (!verification.valid) {
2755
- throw new Error(
2756
- `withdraw_swap circuit integrity verification failed: ${verification.error ?? "hash mismatch"}`
2757
- );
2758
- }
2759
- const sk = splitTo2Limbs(inputs.sk_spend);
2760
- const r = splitTo2Limbs(inputs.r);
2761
- const circuitInputs = {
2762
- // Public signals (must be provided for witness generation)
2763
- root: inputs.root.toString(),
2764
- nullifier: inputs.nullifier.toString(),
2765
- outputs_hash: inputs.outputs_hash.toString(),
2766
- public_amount: inputs.public_amount.toString(),
2767
- // Private inputs
2768
- sk: sk.map((x) => x.toString()),
2769
- r: r.map((x) => x.toString()),
2770
- amount: inputs.amount.toString(),
2771
- leaf_index: inputs.leaf_index.toString(),
2772
- pathElements: inputs.path_elements.map((x) => x.toString()),
2773
- pathIndices: inputs.path_indices,
2774
- input_mint: inputs.input_mint.map((x) => x.toString()),
2775
- output_mint: inputs.output_mint.map((x) => x.toString()),
2776
- recipient_ata: inputs.recipient_ata.map((x) => x.toString()),
2777
- min_output_amount: inputs.min_output_amount.toString(),
2778
- // Note: fee is computed off-chain and validated on-chain:
2779
- // - fixed_fee is 5000000 (0.005 SOL)
2780
- // - var_fee and rem are computed from amount * 3 / 1000 (0.3%)
2781
- var_fee: inputs.var_fee.toString(),
2782
- rem: inputs.rem.toString()
2783
- };
2784
- const { proof, publicSignals } = await snarkjs.groth16.fullProve(
2785
- circuitInputs,
2786
- wasmInput,
2787
- zkeyInput
2788
- );
2789
- const proofBytes = proofToBytes(proof);
2790
- return {
2791
- proof,
2792
- publicSignals,
2793
- proofBytes,
2794
- publicInputsBytes: new Uint8Array(0)
2795
- // Not used, kept for compatibility
2796
- };
2797
- }
2798
- async function areCircuitsAvailable(circuitsPath2) {
2799
- if (!circuitsPath2 || circuitsPath2 === "") {
2800
- return false;
2801
- }
2802
- return isUrl(resolveCircuitsUrl(circuitsPath2));
2803
- }
2804
- async function getDefaultCircuitsPath() {
2805
- return DEFAULT_CIRCUITS_URL;
2806
- }
2807
- var EXPECTED_CIRCUIT_HASHES = {
2808
- withdraw_regular_wasm: "b80c364c926332111945ef437b07a71720fae774e08206cb11b6171f2a2420a3",
2809
- withdraw_regular_zkey: "6dbca2612f7cc257b897d93384ae9dd09c1617fd1668f8287efb1b124488d144",
2810
- withdraw_swap_wasm: "a296badc0047c211a4724c96a4e64a77cd75fbde7cfa4cf23af3640a723e5686",
2811
- withdraw_swap_zkey: "95251954feaac80396651d12a0df3288630fa75db9410aebecb8aaee17a4f6ea"
2812
- };
2813
- async function verifyCircuitIntegrity(circuitsPath2, circuit) {
2814
- const circuitsUrl = resolveCircuitsUrl(circuitsPath2);
2815
- if (SKIP_CIRCUIT_VERIFY_ENV) {
2816
- return {
2817
- valid: true,
2818
- circuit,
2819
- error: "Verification skipped (CLOAK_SKIP_CIRCUIT_INTEGRITY_CHECK=1)"
2820
- };
2821
- }
2822
- const expected = circuit === "withdraw_regular" ? {
2823
- wasm: EXPECTED_CIRCUIT_HASHES.withdraw_regular_wasm,
2824
- zkey: EXPECTED_CIRCUIT_HASHES.withdraw_regular_zkey
2825
- } : {
2826
- wasm: EXPECTED_CIRCUIT_HASHES.withdraw_swap_wasm,
2827
- zkey: EXPECTED_CIRCUIT_HASHES.withdraw_swap_zkey
2828
- };
2829
- const wasmPath = `${circuitsUrl}/${circuit}_js/${circuit}.wasm`;
2830
- const zkeyPath = `${circuitsUrl}/${circuit}_final.zkey`;
2831
- try {
2832
- const [wasmBytes, zkeyBytes] = await Promise.all([
2833
- fetchFileAsBuffer(wasmPath),
2834
- fetchFileAsBuffer(zkeyPath)
2835
- ]);
2836
- const computed = {
2837
- wasm: await sha256Hex(wasmBytes),
2838
- zkey: await sha256Hex(zkeyBytes)
2839
- };
2840
- if (computed.wasm === expected.wasm && computed.zkey === expected.zkey) {
2841
- return {
2842
- valid: true,
2843
- circuit,
2844
- computed,
2845
- expected
2846
- };
2847
- }
2848
- return {
2849
- valid: false,
2850
- circuit,
2851
- error: `Circuit artifact hash mismatch for ${circuit}`,
2852
- computed,
2853
- expected
2854
- };
2855
- } catch (error) {
2856
- return {
2857
- valid: false,
2858
- circuit,
2859
- error: `Circuit verification failed: ${error instanceof Error ? error.message : String(error)}`
2860
- };
2861
- }
2862
- }
2863
- async function verifyAllCircuits(circuitsPath2) {
2864
- const circuitsUrl = resolveCircuitsUrl(circuitsPath2);
2865
- const results = await Promise.all([
2866
- verifyCircuitIntegrity(circuitsUrl, "withdraw_regular"),
2867
- verifyCircuitIntegrity(circuitsUrl, "withdraw_swap")
2868
- ]);
2869
- return results;
2870
- }
2871
-
2872
2188
  // src/core/CloakSDK.ts
2873
2189
  init_utxo();
2874
2190
 
@@ -3088,50 +2404,22 @@ async function computeProofInternal(leafIndex, _nextIndex, subtrees) {
3088
2404
  }
3089
2405
 
3090
2406
  // src/core/CloakSDK.ts
3091
- var COMPUTE_BUDGET_PROGRAM_ID = new import_web35.PublicKey("ComputeBudget111111111111111111111111111111");
3092
- function createSetLoadedAccountsDataSizeLimitInstruction(bytes) {
3093
- const data = Buffer.alloc(5);
3094
- data.writeUInt8(4, 0);
3095
- data.writeUInt32LE(bytes, 1);
3096
- return new import_web35.TransactionInstruction({
3097
- keys: [],
3098
- programId: COMPUTE_BUDGET_PROGRAM_ID,
3099
- data
3100
- });
3101
- }
3102
- var CLOAK_PROGRAM_ID = new import_web35.PublicKey("zh1eLd6rSphLejbFfJEneUwzHRfMKxgzrgkfwA6qRkW");
2407
+ var CLOAK_PROGRAM_ID = new import_web33.PublicKey("zh1eLd6rSphLejbFfJEneUwzHRfMKxgzrgkfwA6qRkW");
3103
2408
  var CLOAK_API_URL = "https://api.cloak.ag";
3104
2409
  var CloakSDK = class {
3105
- /**
3106
- * Create a new Cloak SDK client
3107
- *
3108
- * @param config - Client configuration
3109
- *
3110
- * @example Node.js mode (with keypair)
3111
- * ```typescript
3112
- * const sdk = new CloakSDK({
3113
- * keypairBytes: keypair.secretKey,
3114
- * network: "devnet"
3115
- * });
3116
- * ```
3117
- *
3118
- * @example Browser mode (with wallet adapter)
3119
- * ```typescript
3120
- * const sdk = new CloakSDK({
3121
- * wallet: walletAdapter,
3122
- * network: "devnet"
3123
- * });
3124
- * ```
3125
- */
3126
2410
  constructor(config) {
3127
2411
  if (!config.keypairBytes && !config.wallet) {
3128
- throw new Error("Must provide either keypairBytes (Node.js) or wallet (Browser)");
2412
+ throw new CloakError(
2413
+ "Must provide either keypairBytes (Node.js) or wallet (Browser)",
2414
+ "validation",
2415
+ false
2416
+ );
3129
2417
  }
3130
2418
  if (config.debug) {
3131
2419
  setDebugMode(true);
3132
2420
  }
3133
2421
  if (config.keypairBytes) {
3134
- this.keypair = import_web35.Keypair.fromSecretKey(config.keypairBytes);
2422
+ this.keypair = import_web33.Keypair.fromSecretKey(config.keypairBytes);
3135
2423
  }
3136
2424
  this.wallet = config.wallet;
3137
2425
  this.cloakKeys = config.cloakKeys;
@@ -3145,13 +2433,9 @@ var CloakSDK = class {
3145
2433
  }
3146
2434
  }
3147
2435
  const programId = config.programId || CLOAK_PROGRAM_ID;
3148
- const { pool, merkleTree, treasury } = getShieldPoolPDAs(
3149
- programId,
3150
- NATIVE_SOL_MINT
3151
- );
2436
+ const { pool, merkleTree, treasury } = getShieldPoolPDAs(programId, NATIVE_SOL_MINT);
3152
2437
  this.config = {
3153
2438
  network: config.network || "mainnet",
3154
- keypairBytes: config.keypairBytes,
3155
2439
  cloakKeys: config.cloakKeys,
3156
2440
  programId,
3157
2441
  poolAddress: pool,
@@ -3160,9 +2444,7 @@ var CloakSDK = class {
3160
2444
  debug: config.debug
3161
2445
  };
3162
2446
  }
3163
- /**
3164
- * Get the public key for deposits (from keypair or wallet)
3165
- */
2447
+ /** Public key of the configured signer (keypair or wallet). */
3166
2448
  getPublicKey() {
3167
2449
  if (this.keypair) {
3168
2450
  return this.keypair.publicKey;
@@ -3170,877 +2452,23 @@ var CloakSDK = class {
3170
2452
  if (this.wallet?.publicKey) {
3171
2453
  return this.wallet.publicKey;
3172
2454
  }
3173
- throw new Error("No public key available - wallet not connected or keypair not provided");
2455
+ throw new CloakError(
2456
+ "No public key available - wallet not connected and no keypair provided",
2457
+ "wallet",
2458
+ false
2459
+ );
3174
2460
  }
3175
- /**
3176
- * Check if the SDK is using a wallet adapter
3177
- */
2461
+ /** True when the SDK was constructed with a wallet adapter (browser). */
3178
2462
  isWalletMode() {
3179
2463
  return !!this.wallet && !this.keypair;
3180
2464
  }
3181
2465
  /**
3182
- * Deposit SOL into the Cloak protocol
3183
- *
3184
- * Creates a new note (or uses a provided one), submits a deposit transaction,
3185
- * and registers with the indexer.
3186
- *
3187
- * @param connection - Solana connection
3188
- * @param payer - Payer wallet with sendTransaction method
3189
- * @param amountOrNote - Amount in lamports OR an existing note to deposit
3190
- * @param options - Optional configuration
3191
- * @returns Deposit result with note and transaction info
3192
- *
3193
- * @example
3194
- * ```typescript
3195
- * // Generate and deposit in one step
3196
- * const result = await client.deposit(
3197
- * connection,
3198
- * wallet,
3199
- * 1_000_000_000,
3200
- * {
3201
- * onProgress: (status) => console.log(status)
3202
- * }
3203
- * );
3204
- *
3205
- * // Or deposit a pre-generated note
3206
- * const note = client.generateNote(1_000_000_000);
3207
- * const result = await client.deposit(connection, wallet, note);
3208
- * ```
3209
- */
3210
- async deposit(connection, amountOrNote, options) {
3211
- try {
3212
- let note;
3213
- let isNewNote = false;
3214
- if (typeof amountOrNote === "number") {
3215
- options?.onProgress?.("generating_note", { message: "Generating note with secrets..." });
3216
- note = await generateNote(amountOrNote, this.config.network);
3217
- isNewNote = true;
3218
- } else {
3219
- note = amountOrNote;
3220
- if (note.depositSignature) {
3221
- throw new Error("Note has already been deposited");
3222
- }
3223
- }
3224
- if (isNewNote) {
3225
- await this.storage.saveNote(note);
3226
- if (options?.onNoteGenerated) {
3227
- options?.onProgress?.("awaiting_note_acknowledgment", {
3228
- message: "Waiting for note to be saved before proceeding with deposit..."
3229
- });
3230
- try {
3231
- await options.onNoteGenerated(note);
3232
- } catch (error) {
3233
- throw new Error(
3234
- `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.`
3235
- );
3236
- }
3237
- }
3238
- options?.onProgress?.("note_saved", {
3239
- message: "Note saved. Proceeding with on-chain deposit..."
3240
- });
3241
- }
3242
- const payerPubkey = this.getPublicKey();
3243
- const balance = await connection.getBalance(payerPubkey);
3244
- const requiredAmount = note.amount + 5e3;
3245
- if (balance < requiredAmount) {
3246
- throw new Error(
3247
- `Insufficient balance. Required: ${requiredAmount} lamports (${note.amount} + fees), Available: ${balance} lamports`
3248
- );
3249
- }
3250
- const commitmentBytes = hexToBytes(note.commitment);
3251
- const programId = this.config.programId || CLOAK_PROGRAM_ID;
3252
- const depositIx = createDepositInstruction({
3253
- programId,
3254
- payer: payerPubkey,
3255
- pool: this.config.poolAddress,
3256
- merkleTree: this.config.merkleTreeAddress,
3257
- amount: note.amount,
3258
- commitment: commitmentBytes
3259
- });
3260
- const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
3261
- const priorityFee = options?.priorityFee ?? 1e4;
3262
- const cuPriceIx = import_web35.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priorityFee });
3263
- const dataSizeLimit = options?.loadedAccountsDataSizeLimit ?? 256 * 1024;
3264
- const dataSizeLimitIx = dataSizeLimit > 0 ? createSetLoadedAccountsDataSizeLimitInstruction(dataSizeLimit) : null;
3265
- let computeUnits;
3266
- if (options?.optimizeCU && this.keypair) {
3267
- options?.onProgress?.("simulating", { message: "Simulating transaction for optimal CU..." });
3268
- const simCuLimitIx = import_web35.ComputeBudgetProgram.setComputeUnitLimit({ units: 2e5 });
3269
- const simTransaction = new import_web35.Transaction({
3270
- feePayer: payerPubkey,
3271
- recentBlockhash: blockhash
3272
- }).add(simCuLimitIx).add(cuPriceIx);
3273
- if (dataSizeLimitIx) {
3274
- simTransaction.add(dataSizeLimitIx);
3275
- }
3276
- simTransaction.add(depositIx);
3277
- try {
3278
- const simulation = await connection.simulateTransaction(simTransaction, [this.keypair]);
3279
- if (simulation.value.err) {
3280
- console.warn("Simulation failed, using default CU:", simulation.value.err);
3281
- computeUnits = 4e4;
3282
- } else {
3283
- const simulatedCU = simulation.value.unitsConsumed ?? 3e4;
3284
- computeUnits = Math.ceil(simulatedCU * 1.2);
3285
- console.log(`CU optimization: ${simulatedCU} simulated \u2192 ${computeUnits} limit`);
3286
- }
3287
- } catch (simError) {
3288
- console.warn("Simulation RPC error, using default CU:", simError);
3289
- computeUnits = 4e4;
3290
- }
3291
- } else if (options?.optimizeCU && this.wallet) {
3292
- console.warn("CU optimization via simulation not available in wallet mode (would require double signing). Using default.");
3293
- computeUnits = options?.computeUnits ?? 4e4;
3294
- } else {
3295
- computeUnits = options?.computeUnits ?? 4e4;
3296
- }
3297
- const cuLimitIx = import_web35.ComputeBudgetProgram.setComputeUnitLimit({ units: computeUnits });
3298
- const transaction = new import_web35.Transaction({
3299
- feePayer: payerPubkey,
3300
- recentBlockhash: blockhash
3301
- }).add(cuLimitIx).add(cuPriceIx);
3302
- if (dataSizeLimitIx) {
3303
- transaction.add(dataSizeLimitIx);
3304
- }
3305
- transaction.add(depositIx);
3306
- if (!transaction.feePayer) {
3307
- throw new Error("Transaction feePayer is not set");
3308
- }
3309
- if (!transaction.recentBlockhash) {
3310
- throw new Error("Transaction recentBlockhash is not set");
3311
- }
3312
- let signature;
3313
- if (this.wallet) {
3314
- if (!this.wallet.publicKey) {
3315
- throw new Error("Wallet not connected - publicKey is null");
3316
- }
3317
- if (this.wallet.signTransaction) {
3318
- try {
3319
- const signedTransaction = await this.wallet.signTransaction(transaction);
3320
- const rawTransaction = signedTransaction.serialize();
3321
- signature = await connection.sendRawTransaction(rawTransaction, {
3322
- skipPreflight: options?.skipPreflight || false,
3323
- preflightCommitment: "confirmed",
3324
- maxRetries: 3
3325
- });
3326
- } catch (signError) {
3327
- if (this.wallet.sendTransaction) {
3328
- signature = await this.wallet.sendTransaction(transaction, connection, {
3329
- skipPreflight: options?.skipPreflight || false,
3330
- preflightCommitment: "confirmed",
3331
- maxRetries: 3
3332
- });
3333
- } else {
3334
- throw signError;
3335
- }
3336
- }
3337
- } else if (this.wallet.sendTransaction) {
3338
- signature = await this.wallet.sendTransaction(transaction, connection, {
3339
- skipPreflight: options?.skipPreflight || false,
3340
- preflightCommitment: "confirmed",
3341
- maxRetries: 3
3342
- });
3343
- } else {
3344
- throw new Error("Wallet adapter must provide either sendTransaction or signTransaction");
3345
- }
3346
- } else if (this.keypair) {
3347
- signature = await connection.sendTransaction(transaction, [this.keypair], {
3348
- skipPreflight: options?.skipPreflight || false,
3349
- preflightCommitment: "confirmed",
3350
- maxRetries: 3
3351
- });
3352
- } else {
3353
- throw new Error("No signing method available - provide keypair or wallet");
3354
- }
3355
- const confirmation = await connection.confirmTransaction({
3356
- signature,
3357
- blockhash,
3358
- lastValidBlockHeight
3359
- });
3360
- if (confirmation.value.err) {
3361
- throw new Error(
3362
- `Transaction failed: ${JSON.stringify(confirmation.value.err)}`
3363
- );
3364
- }
3365
- const txDetails = await connection.getTransaction(signature, {
3366
- commitment: "confirmed",
3367
- maxSupportedTransactionVersion: 0
3368
- });
3369
- const depositSlot = txDetails?.slot ?? 0;
3370
- let leafIndex = null;
3371
- let root = null;
3372
- try {
3373
- if (txDetails?.meta?.logMessages) {
3374
- for (const log of txDetails.meta.logMessages) {
3375
- if (log.includes("Program data:")) {
3376
- try {
3377
- const parts = log.split("Program data:");
3378
- if (parts.length > 1) {
3379
- const base64Data = parts[1].trim();
3380
- const eventData = Buffer.from(base64Data, "base64");
3381
- if (eventData.length >= 81 && eventData[0] === 1) {
3382
- const parsedLeafIndex = Number(eventData.readBigUInt64LE(1));
3383
- const loggedCommitment = eventData.slice(9, 41);
3384
- if (Buffer.compare(loggedCommitment, Buffer.from(commitmentBytes)) === 0) {
3385
- leafIndex = parsedLeafIndex;
3386
- const loggedRoot = eventData.slice(49, 81);
3387
- root = Buffer.from(loggedRoot).toString("hex");
3388
- break;
3389
- }
3390
- }
3391
- }
3392
- } catch (e) {
3393
- continue;
3394
- }
3395
- }
3396
- }
3397
- }
3398
- } catch (e) {
3399
- }
3400
- if (leafIndex === null) {
3401
- const programId2 = this.config.programId || CLOAK_PROGRAM_ID;
3402
- const mintForSOL = NATIVE_SOL_MINT;
3403
- const { merkleTree } = getShieldPoolPDAs(programId2, mintForSOL);
3404
- const merkleTreeAccount = await connection.getAccountInfo(merkleTree);
3405
- if (!merkleTreeAccount) {
3406
- throw new Error("Failed to fetch merkle tree account");
3407
- }
3408
- const nextIndex = Number(merkleTreeAccount.data.readBigUInt64LE(32));
3409
- leafIndex = nextIndex - 1;
3410
- const rootBytes = merkleTreeAccount.data.slice(1064, 1096);
3411
- root = Buffer.from(rootBytes).toString("hex");
3412
- }
3413
- if (leafIndex === null || root === null) {
3414
- throw new Error("Failed to get leaf index and root from transaction logs or on-chain state");
3415
- }
3416
- let merkleProof;
3417
- try {
3418
- const programId2 = this.config.programId || CLOAK_PROGRAM_ID;
3419
- const mintForSOL = NATIVE_SOL_MINT;
3420
- const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId2, mintForSOL);
3421
- const chainProof = await computeProofFromChain(connection, merkleTreePDA, leafIndex);
3422
- merkleProof = {
3423
- pathElements: chainProof.pathElements,
3424
- pathIndices: chainProof.pathIndices
3425
- };
3426
- root = chainProof.root;
3427
- } catch (proofError) {
3428
- console.warn("[SDK] Could not compute proof from chain, will fetch at withdrawal:", proofError);
3429
- }
3430
- const updatedNote = updateNoteWithDeposit(note, {
3431
- signature,
3432
- slot: depositSlot,
3433
- leafIndex,
3434
- root,
3435
- merkleProof
3436
- });
3437
- await this.storage.updateNote(note.commitment, {
3438
- depositSignature: signature,
3439
- depositSlot,
3440
- leafIndex,
3441
- root,
3442
- merkleProof
3443
- });
3444
- return {
3445
- note: updatedNote,
3446
- signature,
3447
- leafIndex,
3448
- root
3449
- };
3450
- } catch (error) {
3451
- throw this.wrapError(error, "Deposit failed");
3452
- }
3453
- }
3454
- /**
3455
- * Private transfer with up to 5 recipients
3456
- *
3457
- * Handles the complete private transfer flow:
3458
- * 1. If note is not deposited, deposits it first and waits for confirmation
3459
- * 2. Generates a zero-knowledge proof
3460
- * 3. Submits the withdrawal via relay service to recipients
3461
- *
3462
- * This is the main method for performing private transfers - it handles everything!
3463
- *
3464
- * @param connection - Solana connection (required for deposit if not already deposited)
3465
- * @param payer - Payer wallet (required for deposit if not already deposited)
3466
- * @param note - Note to spend (can be deposited or not)
3467
- * @param recipients - Array of 1-5 recipients with amounts
3468
- * @param options - Optional configuration
3469
- * @returns Transfer result with signature and outputs
3470
- *
3471
- * @example
3472
- * ```typescript
3473
- * // Create a note (not deposited yet)
3474
- * const note = client.generateNote(1_000_000_000);
3475
- *
3476
- * // privateTransfer handles the full flow: deposit + withdraw
3477
- * const result = await client.privateTransfer(
3478
- * connection,
3479
- * wallet,
3480
- * note,
3481
- * [
3482
- * { recipient: new PublicKey("..."), amount: 500_000_000 },
3483
- * { recipient: new PublicKey("..."), amount: 492_500_000 }
3484
- * ],
3485
- * {
3486
- * relayFeeBps: 50, // 0.5%
3487
- * onProgress: (status) => console.log(status),
3488
- * onProofProgress: (pct) => console.log(`Proof: ${pct}%`)
3489
- * }
3490
- * );
3491
- * console.log(`Success! TX: ${result.signature}`);
3492
- * ```
3493
- */
3494
- async privateTransfer(connection, note, recipients, options) {
3495
- if (!isWithdrawable(note)) {
3496
- const depositResult = await this.deposit(connection, note, {
3497
- skipPreflight: false
3498
- });
3499
- note = depositResult.note;
3500
- }
3501
- const protocolFee = note.amount - getDistributableAmount(note.amount);
3502
- const feeBps = Math.ceil(protocolFee * 1e4 / note.amount);
3503
- const distributableAmount = getDistributableAmount(note.amount);
3504
- validateTransfers(recipients, distributableAmount);
3505
- if (!note.leafIndex && note.leafIndex !== 0) {
3506
- throw new Error("Note must have a leaf index (note must be deposited)");
3507
- }
3508
- if (!isValidHex(note.r, 32)) {
3509
- throw new Error("Note r must be 64 hex characters (32 bytes)");
3510
- }
3511
- if (!isValidHex(note.sk_spend, 32)) {
3512
- throw new Error("Note sk_spend must be 64 hex characters (32 bytes)");
3513
- }
3514
- const nullifier = await computeNullifierAsync(note.sk_spend, note.leafIndex);
3515
- const nullifierHex = nullifier.toString(16).padStart(64, "0");
3516
- const outputsHash = await computeOutputsHashAsync(recipients);
3517
- const outputsHashHex = outputsHash.toString(16).padStart(64, "0");
3518
- const circuitsPath2 = await getDefaultCircuitsPath();
3519
- const useDirectProof = await areCircuitsAvailable(circuitsPath2);
3520
- if (!useDirectProof) {
3521
- throw new Error(
3522
- `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.`
3523
- );
3524
- }
3525
- const MAX_PROOF_RETRIES = 3;
3526
- let lastError = null;
3527
- for (let attempt = 0; attempt < MAX_PROOF_RETRIES; attempt++) {
3528
- try {
3529
- if (attempt > 0) {
3530
- options?.onProgress?.(`proof_refresh_${attempt + 1}`);
3531
- await new Promise((resolve) => setTimeout(resolve, 1e3));
3532
- }
3533
- let merkleProof;
3534
- let merkleRoot;
3535
- const hasStoredProof = note.merkleProof && note.root && note.merkleProof.pathElements && note.merkleProof.pathElements.length > 0;
3536
- if (attempt === 0 && hasStoredProof) {
3537
- merkleProof = note.merkleProof;
3538
- merkleRoot = note.root;
3539
- } else {
3540
- const programId = this.config.programId || CLOAK_PROGRAM_ID;
3541
- const mintForSOL = NATIVE_SOL_MINT;
3542
- const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId, mintForSOL);
3543
- const chainProof = await computeProofFromChain(
3544
- connection,
3545
- merkleTreePDA,
3546
- note.leafIndex
3547
- );
3548
- merkleProof = {
3549
- pathElements: chainProof.pathElements,
3550
- pathIndices: chainProof.pathIndices,
3551
- root: chainProof.root
3552
- };
3553
- merkleRoot = chainProof.root;
3554
- note.merkleProof = merkleProof;
3555
- note.root = merkleRoot;
3556
- }
3557
- if (!merkleProof || !merkleRoot) {
3558
- throw new Error("Failed to get Merkle proof from chain");
3559
- }
3560
- if (!merkleProof.pathElements || merkleProof.pathElements.length === 0) {
3561
- throw new Error("Merkle proof is invalid: missing path elements");
3562
- }
3563
- if (merkleProof.pathElements.length !== merkleProof.pathIndices.length) {
3564
- throw new Error("Merkle proof is invalid: path elements and indices length mismatch");
3565
- }
3566
- for (let i = 0; i < merkleProof.pathIndices.length; i++) {
3567
- const idx = merkleProof.pathIndices[i];
3568
- if (idx !== 0 && idx !== 1) {
3569
- throw new Error(`Merkle proof path index at position ${i} must be 0 or 1, got ${idx}`);
3570
- }
3571
- }
3572
- if (!isValidHex(merkleRoot, 32)) {
3573
- throw new Error("Merkle root must be 64 hex characters (32 bytes)");
3574
- }
3575
- for (let i = 0; i < merkleProof.pathElements.length; i++) {
3576
- const element = merkleProof.pathElements[i];
3577
- if (typeof element !== "string" || !isValidHex(element, 32)) {
3578
- throw new Error(`Merkle proof path element at position ${i} must be 64 hex characters (32 bytes)`);
3579
- }
3580
- }
3581
- const sk_spend_bigint = BigInt("0x" + note.sk_spend);
3582
- const r_bigint = BigInt("0x" + note.r);
3583
- const root_bigint = BigInt("0x" + merkleRoot);
3584
- const nullifier_bigint = BigInt("0x" + nullifierHex);
3585
- const outputs_hash_bigint = BigInt("0x" + outputsHashHex);
3586
- const amount_bigint = BigInt(note.amount);
3587
- const pathElements = merkleProof.pathElements.map((p) => BigInt("0x" + p));
3588
- const outAddr = [];
3589
- for (let i = 0; i < 5; i++) {
3590
- if (i < recipients.length) {
3591
- const [lo, hi] = pubkeyToLimbs(recipients[i].recipient.toBytes());
3592
- outAddr.push([lo, hi]);
3593
- } else {
3594
- outAddr.push([0n, 0n]);
3595
- }
3596
- }
3597
- const t = amount_bigint * 3n;
3598
- const varFee = t / 1000n;
3599
- const rem = t % 1000n;
3600
- const outAmount = [];
3601
- const outFlags = [];
3602
- for (let i = 0; i < 5; i++) {
3603
- if (i < recipients.length) {
3604
- outAmount.push(BigInt(recipients[i].amount));
3605
- outFlags.push(1);
3606
- } else {
3607
- outAmount.push(0n);
3608
- outFlags.push(0);
3609
- }
3610
- }
3611
- const sk = splitTo2Limbs(sk_spend_bigint);
3612
- const r = splitTo2Limbs(r_bigint);
3613
- if (attempt === 0) {
3614
- const computedCommitment = await computeCommitment(amount_bigint, r_bigint, sk_spend_bigint);
3615
- const noteCommitment = BigInt("0x" + note.commitment);
3616
- if (computedCommitment !== noteCommitment) {
3617
- throw new Error(
3618
- `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.`
3619
- );
3620
- }
3621
- }
3622
- const proofInputs = {
3623
- root: root_bigint,
3624
- nullifier: nullifier_bigint,
3625
- outputs_hash: outputs_hash_bigint,
3626
- public_amount: amount_bigint,
3627
- amount: amount_bigint,
3628
- leaf_index: BigInt(note.leafIndex),
3629
- sk,
3630
- r,
3631
- pathElements,
3632
- pathIndices: merkleProof.pathIndices,
3633
- num_outputs: recipients.length,
3634
- out_addr: outAddr,
3635
- out_amount: outAmount,
3636
- out_flags: outFlags,
3637
- var_fee: varFee,
3638
- rem
3639
- };
3640
- options?.onProgress?.("proof_generating");
3641
- const proofResult = await generateWithdrawRegularProof(proofInputs, circuitsPath2);
3642
- options?.onProgress?.("proof_complete");
3643
- const proofHex = Buffer.from(proofResult.proofBytes).toString("hex");
3644
- const finalPublicInputs = {
3645
- root: merkleRoot,
3646
- nf: nullifierHex,
3647
- outputs_hash: outputsHashHex,
3648
- amount: note.amount
3649
- };
3650
- const metadataBundle = options?.metadataBundle;
3651
- const signature = await this.relay.submitWithdraw(
3652
- {
3653
- proof: proofHex,
3654
- publicInputs: finalPublicInputs,
3655
- outputs: recipients.map((r2) => ({
3656
- recipient: r2.recipient.toBase58(),
3657
- amount: r2.amount
3658
- })),
3659
- feeBps,
3660
- // Use calculated protocol fee BPS
3661
- metadataBundle
3662
- },
3663
- options?.onProgress
3664
- );
3665
- return {
3666
- signature,
3667
- outputs: recipients.map((r2) => ({
3668
- recipient: r2.recipient.toBase58(),
3669
- amount: r2.amount
3670
- })),
3671
- nullifier: nullifierHex,
3672
- root: merkleRoot
3673
- };
3674
- } catch (error) {
3675
- lastError = error instanceof Error ? error : new Error(String(error));
3676
- if (error instanceof RootNotFoundError) {
3677
- console.warn(
3678
- `[Cloak SDK] Merkle root expired (attempt ${attempt + 1}/${MAX_PROOF_RETRIES}). Regenerating proof with fresh root...`
3679
- );
3680
- continue;
3681
- }
3682
- throw error;
3683
- }
3684
- }
3685
- throw new Error(
3686
- `Withdrawal failed after ${MAX_PROOF_RETRIES} proof refresh attempts. The Merkle tree is updating too rapidly. Last error: ${lastError?.message}`
3687
- );
3688
- }
3689
- /**
3690
- * Withdraw to a single recipient
3691
- *
3692
- * Convenience method for withdrawing to one address.
3693
- * Handles the complete flow: deposits if needed, then withdraws.
3694
- *
3695
- * @param connection - Solana connection
3696
- * @param payer - Payer wallet
3697
- * @param note - Note to spend
3698
- * @param recipient - Recipient address
3699
- * @param options - Optional configuration
3700
- * @returns Transfer result
3701
- *
3702
- * @example
3703
- * ```typescript
3704
- * const note = client.generateNote(1_000_000_000);
3705
- * const result = await client.withdraw(
3706
- * connection,
3707
- * wallet,
3708
- * note,
3709
- * new PublicKey("..."),
3710
- * { withdrawAll: true }
3711
- * );
3712
- * ```
3713
- */
3714
- async withdraw(connection, note, recipient, options) {
3715
- const withdrawAll = options?.withdrawAll ?? true;
3716
- const amount = withdrawAll ? getDistributableAmount(note.amount) : options?.amount || note.amount;
3717
- if (!withdrawAll && !options?.amount) {
3718
- throw new Error("Must specify amount or set withdrawAll: true");
3719
- }
3720
- return this.privateTransfer(
3721
- connection,
3722
- note,
3723
- [{ recipient, amount }],
3724
- options
3725
- );
3726
- }
3727
- /**
3728
- * Send SOL privately to multiple recipients
3729
- *
3730
- * Convenience method that wraps privateTransfer with a simpler API.
3731
- * Handles the complete flow: deposits if needed, then sends to recipients.
3732
- *
3733
- * @param connection - Solana connection
3734
- * @param note - Note to spend
3735
- * @param recipients - Array of 1-5 recipients with amounts
3736
- * @param options - Optional configuration
3737
- * @returns Transfer result
3738
- *
3739
- * @example
3740
- * ```typescript
3741
- * const note = client.generateNote(1_000_000_000);
3742
- * const result = await client.send(
3743
- * connection,
3744
- * note,
3745
- * [
3746
- * { recipient: new PublicKey("..."), amount: 500_000_000 },
3747
- * { recipient: new PublicKey("..."), amount: 492_500_000 }
3748
- * ]
3749
- * );
3750
- * ```
3751
- */
3752
- async send(connection, note, recipients, options) {
3753
- return this.privateTransfer(connection, note, recipients, options);
3754
- }
3755
- /**
3756
- * Swap SOL for tokens privately
3757
- *
3758
- * Withdraws SOL from a note and swaps it for tokens via the relay service.
3759
- * Handles the complete flow: deposits if needed, generates proof, and submits swap.
3760
- *
3761
- * @param connection - Solana connection
3762
- * @param note - Note to spend
3763
- * @param recipient - Recipient address (will receive tokens)
3764
- * @param options - Swap configuration
3765
- * @returns Swap result with transaction signature
3766
- *
3767
- * @example
3768
- * ```typescript
3769
- * const note = client.generateNote(1_000_000_000);
3770
- * const result = await client.swap(
3771
- * connection,
3772
- * note,
3773
- * new PublicKey("..."), // recipient
3774
- * {
3775
- * outputMint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC
3776
- * slippageBps: 100, // 1%
3777
- * getQuote: async (amount, mint, slippage) => {
3778
- * // Fetch quote from your swap API
3779
- * const quote = await fetchSwapQuote(amount, mint, slippage);
3780
- * return {
3781
- * outAmount: quote.outAmount,
3782
- * minOutputAmount: quote.minOutputAmount
3783
- * };
3784
- * }
3785
- * }
3786
- * );
3787
- * ```
3788
- */
3789
- async swap(connection, note, recipient, options) {
3790
- try {
3791
- if (!isWithdrawable(note)) {
3792
- const depositResult = await this.deposit(connection, note, {
3793
- skipPreflight: false
3794
- });
3795
- note = depositResult.note;
3796
- }
3797
- const variableFee = Math.floor(note.amount * 3 / 1e3);
3798
- const feeBps = note.amount === 0 ? 0 : Math.min(Math.floor((variableFee * 1e4 + note.amount - 1) / note.amount), 65535);
3799
- const withdrawAmountLamports = getDistributableAmount(note.amount);
3800
- if (withdrawAmountLamports <= 0) {
3801
- throw new Error("Amount too small after fees");
3802
- }
3803
- let minOutputAmount;
3804
- if (options.minOutputAmount !== void 0) {
3805
- minOutputAmount = options.minOutputAmount;
3806
- } else if (options.getQuote) {
3807
- const quote = await options.getQuote(
3808
- withdrawAmountLamports,
3809
- options.outputMint,
3810
- options.slippageBps || 100
3811
- );
3812
- minOutputAmount = quote.minOutputAmount;
3813
- } else {
3814
- throw new Error(
3815
- "Must provide either minOutputAmount or getQuote function"
3816
- );
3817
- }
3818
- let recipientAta;
3819
- if (options.recipientAta) {
3820
- recipientAta = new import_web35.PublicKey(options.recipientAta);
3821
- } else {
3822
- try {
3823
- const splTokenModule = await import("@solana/spl-token");
3824
- const getAssociatedTokenAddress = splTokenModule.getAssociatedTokenAddress;
3825
- if (!getAssociatedTokenAddress) {
3826
- throw new Error("getAssociatedTokenAddress not found");
3827
- }
3828
- const outputMint2 = new import_web35.PublicKey(options.outputMint);
3829
- recipientAta = await getAssociatedTokenAddress(outputMint2, recipient);
3830
- } catch (error) {
3831
- throw new Error(
3832
- `Failed to get associated token account: ${error instanceof Error ? error.message : String(error)}. Please install @solana/spl-token or provide recipientAta in options.`
3833
- );
3834
- }
3835
- }
3836
- if (!note.leafIndex && note.leafIndex !== 0) {
3837
- throw new Error("Note must have a leaf index (note must be deposited)");
3838
- }
3839
- const nullifier = await computeNullifierAsync(note.sk_spend, note.leafIndex);
3840
- const nullifierHex = nullifier.toString(16).padStart(64, "0");
3841
- const inputMint = new import_web35.PublicKey("11111111111111111111111111111111");
3842
- const outputMint = new import_web35.PublicKey(options.outputMint);
3843
- const outputsHash = await computeSwapOutputsHashAsync(
3844
- inputMint,
3845
- outputMint,
3846
- recipientAta,
3847
- minOutputAmount,
3848
- note.amount
3849
- );
3850
- const outputsHashHex = outputsHash.toString(16).padStart(64, "0");
3851
- const circuitsPath2 = await getDefaultCircuitsPath();
3852
- const useDirectProof = await areCircuitsAvailable(circuitsPath2);
3853
- if (!useDirectProof) {
3854
- throw new Error(
3855
- `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.`
3856
- );
3857
- }
3858
- const MAX_PROOF_RETRIES = 3;
3859
- let lastError = null;
3860
- for (let attempt = 0; attempt < MAX_PROOF_RETRIES; attempt++) {
3861
- try {
3862
- if (attempt > 0) {
3863
- options?.onProgress?.(`proof_refresh_${attempt + 1}`);
3864
- await new Promise((resolve) => setTimeout(resolve, 1e3));
3865
- }
3866
- const programId = this.config.programId || CLOAK_PROGRAM_ID;
3867
- const mintForSOL = NATIVE_SOL_MINT;
3868
- const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId, mintForSOL);
3869
- const chainProof = await computeProofFromChain(connection, merkleTreePDA, note.leafIndex);
3870
- const merkleProof = {
3871
- pathElements: chainProof.pathElements,
3872
- pathIndices: chainProof.pathIndices
3873
- };
3874
- const merkleRoot = chainProof.root;
3875
- if (!merkleRoot) {
3876
- throw new Error("Failed to get Merkle root from chain");
3877
- }
3878
- if (!merkleProof.pathElements || merkleProof.pathElements.length === 0) {
3879
- throw new Error("Merkle proof is invalid: missing path elements");
3880
- }
3881
- const sk_spend_bigint = BigInt("0x" + note.sk_spend);
3882
- const r_bigint = BigInt("0x" + note.r);
3883
- const root_bigint = BigInt("0x" + merkleRoot);
3884
- const nullifier_bigint = BigInt("0x" + nullifierHex);
3885
- const outputs_hash_bigint = BigInt("0x" + outputsHashHex);
3886
- const amount_bigint = BigInt(note.amount);
3887
- const pathElements = merkleProof.pathElements.map((p) => BigInt("0x" + p));
3888
- const inputMintLimbs = pubkeyToLimbs(inputMint.toBytes());
3889
- const outputMintLimbs = pubkeyToLimbs(outputMint.toBytes());
3890
- const recipientAtaLimbs = pubkeyToLimbs(recipientAta.toBytes());
3891
- if (attempt === 0) {
3892
- const computedCommitment = await computeCommitment(amount_bigint, r_bigint, sk_spend_bigint);
3893
- const noteCommitment = BigInt("0x" + note.commitment);
3894
- if (computedCommitment !== noteCommitment) {
3895
- throw new Error(
3896
- `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.`
3897
- );
3898
- }
3899
- }
3900
- const t = amount_bigint * 3n;
3901
- const varFee = t / 1000n;
3902
- const rem = t % 1000n;
3903
- const proofInputs = {
3904
- sk_spend: sk_spend_bigint,
3905
- r: r_bigint,
3906
- amount: amount_bigint,
3907
- leaf_index: BigInt(note.leafIndex),
3908
- path_elements: pathElements,
3909
- path_indices: merkleProof.pathIndices,
3910
- root: root_bigint,
3911
- nullifier: nullifier_bigint,
3912
- outputs_hash: outputs_hash_bigint,
3913
- public_amount: amount_bigint,
3914
- input_mint: inputMintLimbs,
3915
- output_mint: outputMintLimbs,
3916
- recipient_ata: recipientAtaLimbs,
3917
- min_output_amount: BigInt(minOutputAmount),
3918
- var_fee: varFee,
3919
- rem
3920
- };
3921
- options?.onProgress?.("proof_generating");
3922
- const proofResult = await generateWithdrawSwapProof(proofInputs, circuitsPath2);
3923
- options?.onProgress?.("proof_complete");
3924
- const proofHex = Buffer.from(proofResult.proofBytes).toString("hex");
3925
- const finalPublicInputs = {
3926
- root: merkleRoot,
3927
- nf: nullifierHex,
3928
- outputs_hash: outputsHashHex,
3929
- amount: note.amount
3930
- };
3931
- const metadataBundle = options?.metadataBundle;
3932
- const signature = await this.relay.submitSwap(
3933
- {
3934
- proof: proofHex,
3935
- publicInputs: finalPublicInputs,
3936
- outputs: [
3937
- {
3938
- recipient: recipient.toBase58(),
3939
- amount: withdrawAmountLamports
3940
- }
3941
- ],
3942
- feeBps,
3943
- swap: {
3944
- output_mint: options.outputMint,
3945
- slippage_bps: options.slippageBps || 100,
3946
- min_output_amount: minOutputAmount
3947
- },
3948
- metadataBundle
3949
- },
3950
- options.onProgress
3951
- );
3952
- return {
3953
- signature,
3954
- outputs: [
3955
- {
3956
- recipient: recipient.toBase58(),
3957
- amount: withdrawAmountLamports
3958
- }
3959
- ],
3960
- nullifier: nullifierHex,
3961
- root: merkleRoot,
3962
- outputMint: options.outputMint,
3963
- minOutputAmount
3964
- };
3965
- } catch (error) {
3966
- lastError = error instanceof Error ? error : new Error(String(error));
3967
- if (error instanceof RootNotFoundError) {
3968
- console.warn(
3969
- `[Cloak SDK] Merkle root expired (attempt ${attempt + 1}/${MAX_PROOF_RETRIES}). Regenerating swap proof with fresh root...`
3970
- );
3971
- continue;
3972
- }
3973
- throw error;
3974
- }
3975
- }
3976
- throw new Error(
3977
- `Swap failed after ${MAX_PROOF_RETRIES} proof refresh attempts. The Merkle tree is updating too rapidly. Last error: ${lastError?.message}`
3978
- );
3979
- } catch (error) {
3980
- throw this.wrapError(error, "Swap failed");
3981
- }
3982
- }
3983
- /**
3984
- * Generate a new note without depositing
3985
- *
3986
- * @param amountLamports - Amount for the note
3987
- * @param useWalletKeys - Whether to use wallet keys (v2.0 recommended)
3988
- * @returns New note (not yet deposited)
3989
- */
3990
- async generateNote(amountLamports, useWalletKeys = false) {
3991
- if (useWalletKeys && this.cloakKeys) {
3992
- return await generateNoteFromWallet(amountLamports, this.cloakKeys, this.config.network);
3993
- } else if (useWalletKeys) {
3994
- const keys = generateCloakKeys();
3995
- this.cloakKeys = keys;
3996
- const result = this.storage.saveKeys(keys);
3997
- if (result instanceof Promise) {
3998
- result.catch(() => {
3999
- });
4000
- }
4001
- return await generateNoteFromWallet(amountLamports, keys, this.config.network);
4002
- }
4003
- return await generateNote(amountLamports, this.config.network);
4004
- }
4005
- /**
4006
- * Parse a note from JSON string
4007
- *
4008
- * @param jsonString - JSON representation
4009
- * @returns Parsed note
4010
- */
4011
- parseNote(jsonString) {
4012
- return parseNote2(jsonString);
4013
- }
4014
- /**
4015
- * Export a note to JSON string
4016
- *
4017
- * @param note - Note to export
4018
- * @param pretty - Format with indentation
4019
- * @returns JSON string
4020
- */
4021
- exportNote(note, pretty = false) {
4022
- return pretty ? JSON.stringify(note, null, 2) : JSON.stringify(note);
4023
- }
4024
- /**
4025
- * Check if a note is ready for withdrawal
4026
- *
4027
- * @param note - Note to check
4028
- * @returns True if withdrawable
4029
- */
4030
- isWithdrawable(note) {
4031
- return isWithdrawable(note);
4032
- }
4033
- /**
4034
- * Get Merkle proof for a leaf index directly from on-chain state
4035
- *
4036
- * @param connection - Solana connection
4037
- * @param leafIndex - Leaf index in tree
4038
- * @returns Merkle proof computed from on-chain data
2466
+ * Compute a Merkle proof for `leafIndex` from on-chain state. No relay /
2467
+ * indexer round-trip required.
4039
2468
  */
4040
2469
  async getMerkleProof(connection, leafIndex) {
4041
2470
  const programId = this.config.programId || CLOAK_PROGRAM_ID;
4042
- const mintForSOL = NATIVE_SOL_MINT;
4043
- const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId, mintForSOL);
2471
+ const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId, NATIVE_SOL_MINT);
4044
2472
  const chainProof = await computeProofFromChain(connection, merkleTreePDA, leafIndex);
4045
2473
  return {
4046
2474
  pathElements: chainProof.pathElements,
@@ -4048,72 +2476,20 @@ var CloakSDK = class {
4048
2476
  root: chainProof.root
4049
2477
  };
4050
2478
  }
4051
- /**
4052
- * Get current Merkle root directly from on-chain state
4053
- *
4054
- * @param connection - Solana connection
4055
- * @returns Current root hash from on-chain tree
4056
- */
4057
- async getCurrentRoot(connection) {
4058
- const programId = this.config.programId || CLOAK_PROGRAM_ID;
4059
- const mintForSOL = NATIVE_SOL_MINT;
4060
- const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId, mintForSOL);
4061
- const state = await readMerkleTreeState(connection, merkleTreePDA);
4062
- return state.root;
4063
- }
4064
- /**
4065
- * Get transaction status from relay service
4066
- *
4067
- * @param requestId - Request ID from previous submission
4068
- * @returns Current status
4069
- */
4070
- async getTransactionStatus(requestId) {
4071
- return this.relay.getStatus(requestId);
4072
- }
4073
- /**
4074
- * Fetch and decrypt this user's transaction metadata history.
4075
- * DEPRECATED: This functionality is no longer supported
4076
- */
4077
- async getTransactionMetadata(_options) {
4078
- throw new Error("getTransactionMetadata is deprecated - use compliance export API instead");
4079
- }
4080
- /**
4081
- * Load all notes from storage
4082
- *
4083
- * @returns Array of saved notes
4084
- */
4085
- async loadNotes() {
4086
- const notes = this.storage.loadAllNotes();
4087
- return Array.isArray(notes) ? notes : await notes;
4088
- }
4089
- /**
4090
- * Save a note to storage
4091
- *
4092
- * @param note - Note to save
4093
- */
4094
- async saveNote(note) {
4095
- const result = this.storage.saveNote(note);
4096
- if (result instanceof Promise) {
4097
- await result;
4098
- }
2479
+ /** Read the current SOL-pool Merkle root from on-chain state. */
2480
+ async getCurrentRoot(connection) {
2481
+ const programId = this.config.programId || CLOAK_PROGRAM_ID;
2482
+ const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId, NATIVE_SOL_MINT);
2483
+ const state = await readMerkleTreeState(connection, merkleTreePDA);
2484
+ return state.root;
4099
2485
  }
4100
- /**
4101
- * Find a note by its commitment
4102
- *
4103
- * @param commitment - Commitment hash
4104
- * @returns Note if found
4105
- */
4106
- async findNote(commitment) {
4107
- const notes = await this.loadNotes();
4108
- return findNoteByCommitment(notes, commitment);
2486
+ /** Poll relay for the status of a previously-submitted request. */
2487
+ async getTransactionStatus(requestId) {
2488
+ return this.relay.getStatus(requestId);
4109
2489
  }
4110
- /**
4111
- * Import wallet keys from JSON
4112
- *
4113
- * @param keysJson - JSON string containing keys
4114
- */
2490
+ /** Import wallet keys from JSON; persists to the configured storage adapter. */
4115
2491
  async importWalletKeys(keysJson) {
4116
- const keys = importWalletKeys(keysJson);
2492
+ const keys = importKeys(keysJson);
4117
2493
  this.cloakKeys = keys;
4118
2494
  const result = this.storage.saveKeys(keys);
4119
2495
  if (result instanceof Promise) {
@@ -4121,134 +2497,32 @@ var CloakSDK = class {
4121
2497
  }
4122
2498
  }
4123
2499
  /**
4124
- * Export wallet keys to JSON
4125
- *
4126
- * WARNING: This exports secret keys! Store securely.
4127
- *
4128
- * @returns JSON string with keys
2500
+ * Export wallet keys to JSON.
2501
+ *
2502
+ * WARNING: this exports secret keys. Store securely.
4129
2503
  */
4130
2504
  exportWalletKeys() {
4131
2505
  if (!this.cloakKeys) {
4132
2506
  throw new CloakError("No wallet keys available", "wallet", false);
4133
2507
  }
4134
- return exportWalletKeys(this.cloakKeys);
2508
+ return exportKeys(this.cloakKeys);
4135
2509
  }
4136
2510
  /**
4137
- * Get the configuration
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.
4138
2520
  */
4139
2521
  getConfig() {
4140
2522
  return { ...this.config };
4141
2523
  }
4142
- // Note: scanNotes was removed - it required the indexer to store encrypted outputs.
4143
- // Users should maintain their own note storage. This is more privacy-preserving.
4144
- /**
4145
- * Wrap errors with better categorization and user-friendly messages
4146
- *
4147
- * @private
4148
- */
4149
- wrapError(error, context) {
4150
- if (error instanceof CloakError) {
4151
- return error;
4152
- }
4153
- const errorMessage = error instanceof Error ? error.message : String(error);
4154
- if (errorMessage.includes("duplicate key") || errorMessage.includes("already deposited")) {
4155
- return new CloakError(
4156
- "This note was already deposited. Generate a new note to continue.",
4157
- "validation",
4158
- false,
4159
- error instanceof Error ? error : void 0
4160
- );
4161
- }
4162
- if (errorMessage.includes("insufficient funds") || errorMessage.includes("insufficient lamports")) {
4163
- return new CloakError(
4164
- "Insufficient funds for this transaction. Please check your wallet balance.",
4165
- "wallet",
4166
- false,
4167
- error instanceof Error ? error : void 0
4168
- );
4169
- }
4170
- if (errorMessage.includes("Merkle tree") || errorMessage.includes("merkle")) {
4171
- return new CloakError(
4172
- "Failed to read on-chain Merkle tree state. Please try again.",
4173
- "network",
4174
- true,
4175
- error instanceof Error ? error : void 0
4176
- );
4177
- }
4178
- if (errorMessage.includes("timeout") || errorMessage.includes("timed out")) {
4179
- return new CloakError(
4180
- "Network timeout. Please check your connection and try again.",
4181
- "network",
4182
- true,
4183
- error instanceof Error ? error : void 0
4184
- );
4185
- }
4186
- if (errorMessage.includes("not connected") || errorMessage.includes("wallet")) {
4187
- return new CloakError(
4188
- "Wallet not connected. Please connect your wallet first.",
4189
- "wallet",
4190
- false,
4191
- error instanceof Error ? error : void 0
4192
- );
4193
- }
4194
- if (errorMessage.includes("proof") && (errorMessage.includes("failed") || errorMessage.includes("error"))) {
4195
- return new CloakError(
4196
- "Zero-knowledge proof generation failed. This is usually temporary - please try again.",
4197
- "prover",
4198
- true,
4199
- error instanceof Error ? error : void 0
4200
- );
4201
- }
4202
- if (errorMessage.includes("relay") || errorMessage.includes("withdraw")) {
4203
- return new CloakError(
4204
- "Relay service error. Please try again later.",
4205
- "relay",
4206
- true,
4207
- error instanceof Error ? error : void 0
4208
- );
4209
- }
4210
- return new CloakError(
4211
- `${context}: ${errorMessage}`,
4212
- "network",
4213
- false,
4214
- error instanceof Error ? error : void 0
4215
- );
4216
- }
4217
2524
  };
4218
2525
 
4219
- // src/core/note.ts
4220
- function serializeNote(note, pretty = false) {
4221
- validateNote(note);
4222
- return pretty ? JSON.stringify(note, null, 2) : JSON.stringify(note);
4223
- }
4224
- function downloadNote(note, filename) {
4225
- const g = globalThis;
4226
- const doc = g?.document;
4227
- const URL_ = g?.URL;
4228
- const Blob_ = g?.Blob;
4229
- if (!doc || !URL_ || !Blob_) {
4230
- throw new Error("downloadNote is only available in browser environments");
4231
- }
4232
- const json = serializeNote(note, true);
4233
- const blob = new Blob_([json], { type: "application/json" });
4234
- const url = URL_.createObjectURL(blob);
4235
- const defaultFilename = `cloak-note-${note.commitment.slice(0, 8)}.json`;
4236
- const link = doc.createElement("a");
4237
- link.href = url;
4238
- link.download = filename || defaultFilename;
4239
- link.click();
4240
- URL_.revokeObjectURL(url);
4241
- }
4242
- async function copyNoteToClipboard(note) {
4243
- const g = globalThis;
4244
- const nav = g?.navigator;
4245
- if (!nav || !nav.clipboard) {
4246
- throw new Error("Clipboard API not available");
4247
- }
4248
- const json = serializeNote(note, true);
4249
- await nav.clipboard.writeText(json);
4250
- }
4251
-
4252
2526
  // src/core/compliance-keys.ts
4253
2527
  var import_tweetnacl2 = __toESM(require("tweetnacl"), 1);
4254
2528
  var import_blake33 = require("@noble/hashes/blake3");
@@ -4635,6 +2909,165 @@ async function decryptComplianceMetadataWithMasterKey(encrypted, masterComplianc
4635
2909
  return decryptWithSharedSecret(payload, sharedSecret);
4636
2910
  }
4637
2911
 
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
+ // src/utils/verify-utxos.ts
3014
+ init_utxo();
3015
+ async function verifyUtxos(utxos, connection, programId, commitment = "confirmed") {
3016
+ const checkable = [];
3017
+ const skipped = [];
3018
+ for (const utxo of utxos) {
3019
+ if (utxo.commitment === void 0 || utxo.mintAddress === void 0 || utxo.keypair === void 0 || utxo.keypair.privateKey === void 0) {
3020
+ skipped.push(utxo);
3021
+ continue;
3022
+ }
3023
+ try {
3024
+ const nullifierBig = await computeNullifier(utxo);
3025
+ const nullifierHex = nullifierBig.toString(16).padStart(64, "0");
3026
+ const nullifierBytes = Buffer.from(nullifierHex, "hex");
3027
+ const { pool } = getShieldPoolPDAs(programId, utxo.mintAddress);
3028
+ const [pda] = getNullifierPDA(pool, nullifierBytes, programId);
3029
+ checkable.push({ utxo, pda });
3030
+ } catch {
3031
+ skipped.push(utxo);
3032
+ }
3033
+ }
3034
+ if (checkable.length === 0) {
3035
+ return { spent: [], unspent: [], skipped };
3036
+ }
3037
+ const CHUNK = 100;
3038
+ const infos = [];
3039
+ for (let i = 0; i < checkable.length; i += CHUNK) {
3040
+ const page = checkable.slice(i, i + CHUNK).map((e) => e.pda);
3041
+ const pageInfos = await connection.getMultipleAccountsInfo(page, commitment);
3042
+ infos.push(...pageInfos);
3043
+ }
3044
+ const spent = [];
3045
+ const unspent = [];
3046
+ for (let i = 0; i < checkable.length; i++) {
3047
+ if (infos[i] !== null) {
3048
+ spent.push(checkable[i].utxo);
3049
+ } else {
3050
+ unspent.push(checkable[i].utxo);
3051
+ }
3052
+ }
3053
+ return { spent, unspent, skipped };
3054
+ }
3055
+ async function preflightNullifiers(utxos, connection, programId, commitment = "confirmed") {
3056
+ if (utxos.length === 0) return;
3057
+ const result = await verifyUtxos(utxos, connection, programId, commitment);
3058
+ if (result.spent.length > 0) {
3059
+ const allCommitments = result.spent.map(
3060
+ (u) => u.commitment !== void 0 ? u.commitment.toString(16).padStart(64, "0") : "<unknown>"
3061
+ );
3062
+ const preview = allCommitments.slice(0, 5);
3063
+ const extra = allCommitments.length > preview.length ? ` (+${allCommitments.length - preview.length} more)` : "";
3064
+ throw new UtxoAlreadySpentError(
3065
+ `Pre-flight detected ${allCommitments.length} already-spent UTXO(s). First spent commitment(s): ${preview.join(", ")}${extra}. Call verifyUtxos() and remove these from local state before retrying.`,
3066
+ allCommitments
3067
+ );
3068
+ }
3069
+ }
3070
+
4638
3071
  // src/services/risk-oracle.ts
4639
3072
  function buildRangeRiskScoreJob(OracleJob, walletAddress) {
4640
3073
  return OracleJob.fromObject({
@@ -4706,49 +3139,10 @@ async function fetchRiskQuoteIx(connection, wallet, rangeApiKey) {
4706
3139
  };
4707
3140
  }
4708
3141
 
4709
- // src/helpers/encrypted-output.ts
4710
- function prepareEncryptedOutput(note, cloakKeys) {
4711
- const noteData = {
4712
- amount: note.amount,
4713
- r: note.r,
4714
- sk_spend: note.sk_spend,
4715
- commitment: note.commitment
4716
- };
4717
- const encrypted = encryptNoteForRecipient(noteData, cloakKeys.view.pvk);
4718
- return btoa(JSON.stringify(encrypted));
4719
- }
4720
- function prepareEncryptedOutputForRecipient(note, recipientPvkHex) {
4721
- const noteData = {
4722
- amount: note.amount,
4723
- r: note.r,
4724
- sk_spend: note.sk_spend,
4725
- commitment: note.commitment
4726
- };
4727
- const recipientPvk = hexToBytes(recipientPvkHex);
4728
- const encrypted = encryptNoteForRecipient(noteData, recipientPvk);
4729
- return btoa(JSON.stringify(encrypted));
4730
- }
4731
- function encodeNoteSimple(note) {
4732
- const data = {
4733
- amount: note.amount,
4734
- r: note.r,
4735
- sk_spend: note.sk_spend,
4736
- commitment: note.commitment
4737
- };
4738
- const json = JSON.stringify(data);
4739
- if (typeof Buffer !== "undefined") {
4740
- return Buffer.from(json).toString("base64");
4741
- } else if (typeof btoa !== "undefined") {
4742
- return btoa(json);
4743
- } else {
4744
- throw new Error("No base64 encoding method available");
4745
- }
4746
- }
4747
-
4748
3142
  // src/helpers/wallet-integration.ts
4749
- var import_web36 = require("@solana/web3.js");
3143
+ var import_web35 = require("@solana/web3.js");
4750
3144
  function validateWalletConnected(wallet) {
4751
- if (wallet instanceof import_web36.Keypair) {
3145
+ if (wallet instanceof import_web35.Keypair) {
4752
3146
  return;
4753
3147
  }
4754
3148
  if (!wallet.publicKey) {
@@ -4760,7 +3154,7 @@ function validateWalletConnected(wallet) {
4760
3154
  }
4761
3155
  }
4762
3156
  function getPublicKey(wallet) {
4763
- if (wallet instanceof import_web36.Keypair) {
3157
+ if (wallet instanceof import_web35.Keypair) {
4764
3158
  return wallet.publicKey;
4765
3159
  }
4766
3160
  if (!wallet.publicKey) {
@@ -4773,7 +3167,7 @@ function getPublicKey(wallet) {
4773
3167
  return wallet.publicKey;
4774
3168
  }
4775
3169
  async function sendTransaction(transaction, wallet, connection, options) {
4776
- if (wallet instanceof import_web36.Keypair) {
3170
+ if (wallet instanceof import_web35.Keypair) {
4777
3171
  return await connection.sendTransaction(transaction, [wallet], options);
4778
3172
  }
4779
3173
  if (wallet.sendTransaction) {
@@ -4790,7 +3184,7 @@ async function sendTransaction(transaction, wallet, connection, options) {
4790
3184
  }
4791
3185
  }
4792
3186
  async function signTransaction(transaction, wallet) {
4793
- if (wallet instanceof import_web36.Keypair) {
3187
+ if (wallet instanceof import_web35.Keypair) {
4794
3188
  transaction.sign(wallet);
4795
3189
  return transaction;
4796
3190
  }
@@ -4981,10 +3375,15 @@ var import_bs58 = __toESM(require("bs58"), 1);
4981
3375
  var bs58 = import_bs58.default.default || import_bs58.default;
4982
3376
  var TRANSACT_TAG = 0;
4983
3377
  var TRANSACT_SWAP_TAG = 1;
4984
- var DEPOSIT_TAG = 1;
4985
3378
  var PROOF_LEN = 256;
4986
3379
  var PUBLIC_INPUTS_LEN = 264;
4987
3380
  var COMMITMENTS_OFFSET = 168;
3381
+ function isAllZero(b) {
3382
+ for (let i = 0; i < b.length; i++) {
3383
+ if (b[i] !== 0) return false;
3384
+ }
3385
+ return true;
3386
+ }
4988
3387
  var DEFAULT_TIMEOUT_MS = 3e4;
4989
3388
  var DEFAULT_MAX_RETRIES = 3;
4990
3389
  async function fetchWithRetry(url, options = {}) {
@@ -5122,24 +3521,20 @@ async function buildMerkleTreeFromRelay(relayUrl, options) {
5122
3521
  function extractCommitmentsFromInstruction(data) {
5123
3522
  if (data.length === 0) return [];
5124
3523
  const tag = data[0];
5125
- if (tag === TRANSACT_TAG || tag === TRANSACT_SWAP_TAG) {
5126
- const publicInputsStart = 1 + PROOF_LEN;
5127
- const publicInputsEnd = publicInputsStart + PUBLIC_INPUTS_LEN;
5128
- if (data.length < publicInputsEnd) return [];
5129
- const publicInputs = data.slice(publicInputsStart, publicInputsEnd);
5130
- const commitments = [];
5131
- const c0 = publicInputs.slice(COMMITMENTS_OFFSET, COMMITMENTS_OFFSET + 32);
5132
- const c1 = publicInputs.slice(COMMITMENTS_OFFSET + 32, COMMITMENTS_OFFSET + 64);
5133
- commitments.push(c0);
5134
- commitments.push(c1);
5135
- return commitments;
5136
- }
5137
- if (tag === DEPOSIT_TAG) {
5138
- if (data.length < 1 + 8 + 32) return [];
5139
- const commitment = data.slice(1 + 8, 1 + 8 + 32);
5140
- return [commitment];
5141
- }
5142
- return [];
3524
+ if (tag !== TRANSACT_TAG && tag !== TRANSACT_SWAP_TAG) return [];
3525
+ const publicInputsStart = 1 + PROOF_LEN;
3526
+ const publicInputsEnd = publicInputsStart + PUBLIC_INPUTS_LEN;
3527
+ if (data.length < publicInputsEnd) return [];
3528
+ const publicInputs = data.slice(publicInputsStart, publicInputsEnd);
3529
+ const c0 = publicInputs.slice(COMMITMENTS_OFFSET, COMMITMENTS_OFFSET + 32);
3530
+ const c1 = publicInputs.slice(
3531
+ COMMITMENTS_OFFSET + 32,
3532
+ COMMITMENTS_OFFSET + 64
3533
+ );
3534
+ const out = [];
3535
+ if (!isAllZero(c0)) out.push(c0);
3536
+ if (!isAllZero(c1)) out.push(c1);
3537
+ return out;
5143
3538
  }
5144
3539
  function isBrowser() {
5145
3540
  return typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
@@ -5253,180 +3648,13 @@ async function preflightCheck(relayUrl, rootHex) {
5253
3648
  };
5254
3649
  }
5255
3650
 
5256
- // src/utils/pending-operations.ts
5257
- var PENDING_DEPOSITS_KEY = "cloak_pending_deposits";
5258
- var PENDING_WITHDRAWALS_KEY = "cloak_pending_withdrawals";
5259
- function getStorage() {
5260
- if (typeof globalThis !== "undefined" && globalThis.localStorage) {
5261
- return globalThis.localStorage;
5262
- }
5263
- return null;
5264
- }
5265
- function savePendingDeposit(deposit) {
5266
- const storage = getStorage();
5267
- if (!storage) {
5268
- console.warn("localStorage not available - pending deposit not persisted");
5269
- return;
5270
- }
5271
- const deposits = loadPendingDeposits();
5272
- const index = deposits.findIndex((d) => d.note.commitment === deposit.note.commitment);
5273
- if (index >= 0) {
5274
- deposits[index] = deposit;
5275
- } else {
5276
- deposits.push(deposit);
5277
- }
5278
- storage.setItem(PENDING_DEPOSITS_KEY, JSON.stringify(deposits));
5279
- }
5280
- function loadPendingDeposits() {
5281
- const storage = getStorage();
5282
- if (!storage) return [];
5283
- const stored = storage.getItem(PENDING_DEPOSITS_KEY);
5284
- if (!stored) return [];
5285
- try {
5286
- return JSON.parse(stored);
5287
- } catch {
5288
- return [];
5289
- }
5290
- }
5291
- function updatePendingDeposit(commitment, updates) {
5292
- const storage = getStorage();
5293
- if (!storage) return;
5294
- const deposits = loadPendingDeposits();
5295
- const index = deposits.findIndex((d) => d.note.commitment === commitment);
5296
- if (index >= 0) {
5297
- deposits[index] = { ...deposits[index], ...updates };
5298
- storage.setItem(PENDING_DEPOSITS_KEY, JSON.stringify(deposits));
5299
- }
5300
- }
5301
- function removePendingDeposit(commitment) {
5302
- const storage = getStorage();
5303
- if (!storage) return;
5304
- const deposits = loadPendingDeposits();
5305
- const filtered = deposits.filter((d) => d.note.commitment !== commitment);
5306
- storage.setItem(PENDING_DEPOSITS_KEY, JSON.stringify(filtered));
5307
- }
5308
- function clearPendingDeposits() {
5309
- const storage = getStorage();
5310
- if (storage) {
5311
- storage.removeItem(PENDING_DEPOSITS_KEY);
5312
- }
5313
- }
5314
- function savePendingWithdrawal(withdrawal) {
5315
- const storage = getStorage();
5316
- if (!storage) {
5317
- console.warn("localStorage not available - pending withdrawal not persisted");
5318
- return;
5319
- }
5320
- const withdrawals = loadPendingWithdrawals();
5321
- const index = withdrawals.findIndex((w) => w.requestId === withdrawal.requestId);
5322
- if (index >= 0) {
5323
- withdrawals[index] = withdrawal;
5324
- } else {
5325
- withdrawals.push(withdrawal);
5326
- }
5327
- storage.setItem(PENDING_WITHDRAWALS_KEY, JSON.stringify(withdrawals));
5328
- }
5329
- function loadPendingWithdrawals() {
5330
- const storage = getStorage();
5331
- if (!storage) return [];
5332
- const stored = storage.getItem(PENDING_WITHDRAWALS_KEY);
5333
- if (!stored) return [];
5334
- try {
5335
- return JSON.parse(stored);
5336
- } catch {
5337
- return [];
5338
- }
5339
- }
5340
- function updatePendingWithdrawal(requestId, updates) {
5341
- const storage = getStorage();
5342
- if (!storage) return;
5343
- const withdrawals = loadPendingWithdrawals();
5344
- const index = withdrawals.findIndex((w) => w.requestId === requestId);
5345
- if (index >= 0) {
5346
- withdrawals[index] = { ...withdrawals[index], ...updates };
5347
- storage.setItem(PENDING_WITHDRAWALS_KEY, JSON.stringify(withdrawals));
5348
- }
5349
- }
5350
- function removePendingWithdrawal(requestId) {
5351
- const storage = getStorage();
5352
- if (!storage) return;
5353
- const withdrawals = loadPendingWithdrawals();
5354
- const filtered = withdrawals.filter((w) => w.requestId !== requestId);
5355
- storage.setItem(PENDING_WITHDRAWALS_KEY, JSON.stringify(filtered));
5356
- }
5357
- function clearPendingWithdrawals() {
5358
- const storage = getStorage();
5359
- if (storage) {
5360
- storage.removeItem(PENDING_WITHDRAWALS_KEY);
5361
- }
5362
- }
5363
- function hasPendingOperations() {
5364
- const deposits = loadPendingDeposits();
5365
- const withdrawals = loadPendingWithdrawals();
5366
- const pendingDeposits = deposits.filter(
5367
- (d) => d.status === "pending" || d.status === "tx_sent"
5368
- );
5369
- const pendingWithdrawals = withdrawals.filter(
5370
- (w) => w.status === "pending" || w.status === "processing"
5371
- );
5372
- return pendingDeposits.length > 0 || pendingWithdrawals.length > 0;
5373
- }
5374
- function getPendingOperationsSummary() {
5375
- const deposits = loadPendingDeposits().filter(
5376
- (d) => d.status === "pending" || d.status === "tx_sent"
5377
- );
5378
- const withdrawals = loadPendingWithdrawals().filter(
5379
- (w) => w.status === "pending" || w.status === "processing"
5380
- );
5381
- return {
5382
- deposits,
5383
- withdrawals,
5384
- totalPending: deposits.length + withdrawals.length
5385
- };
5386
- }
5387
- function cleanupStalePendingOperations(maxAgeMs = 24 * 60 * 60 * 1e3) {
5388
- const now = Date.now();
5389
- let removedDeposits = 0;
5390
- let removedWithdrawals = 0;
5391
- const deposits = loadPendingDeposits();
5392
- const activeDeposits = deposits.filter((d) => {
5393
- const age = now - d.startedAt;
5394
- const isStale = age > maxAgeMs;
5395
- const isTerminal = d.status === "confirmed" || d.status === "failed";
5396
- if (isStale || isTerminal) {
5397
- removedDeposits++;
5398
- return false;
5399
- }
5400
- return true;
5401
- });
5402
- const storage = getStorage();
5403
- if (storage) {
5404
- storage.setItem(PENDING_DEPOSITS_KEY, JSON.stringify(activeDeposits));
5405
- }
5406
- const withdrawals = loadPendingWithdrawals();
5407
- const activeWithdrawals = withdrawals.filter((w) => {
5408
- const age = now - w.startedAt;
5409
- const isStale = age > maxAgeMs;
5410
- const isTerminal = w.status === "completed" || w.status === "failed";
5411
- if (isStale || isTerminal) {
5412
- removedWithdrawals++;
5413
- return false;
5414
- }
5415
- return true;
5416
- });
5417
- if (storage) {
5418
- storage.setItem(PENDING_WITHDRAWALS_KEY, JSON.stringify(activeWithdrawals));
5419
- }
5420
- return { removedDeposits, removedWithdrawals };
5421
- }
5422
-
5423
3651
  // src/index.ts
5424
3652
  init_utxo();
5425
3653
 
5426
3654
  // src/core/transact.ts
5427
- var import_web37 = require("@solana/web3.js");
3655
+ var import_web36 = require("@solana/web3.js");
5428
3656
  var import_spl_token = require("@solana/spl-token");
5429
- var snarkjs2 = __toESM(require("snarkjs"), 1);
3657
+ var snarkjs = __toESM(require("snarkjs"), 1);
5430
3658
  init_utxo();
5431
3659
 
5432
3660
  // src/core/chain-note.ts
@@ -5577,7 +3805,7 @@ function chainNoteFromBase64(base64) {
5577
3805
  // src/core/transact.ts
5578
3806
  var import_circomlibjs4 = require("circomlibjs");
5579
3807
  var import_tweetnacl4 = __toESM(require("tweetnacl"), 1);
5580
- var IS_BROWSER2 = typeof window !== "undefined" || typeof globalThis.document !== "undefined";
3808
+ var IS_BROWSER = typeof window !== "undefined" || typeof globalThis.document !== "undefined";
5581
3809
  var DEFAULT_TRANSACTION_CIRCUITS_URL = "https://cloak-circuits.s3.us-east-1.amazonaws.com/circuits/0.1.0";
5582
3810
  var DEFAULT_CLOAK_RELAY_URL = "https://api.cloak.ag";
5583
3811
  function resolveDefaultTransactionCircuitsPath() {
@@ -5590,7 +3818,7 @@ async function resolveTransactionCircuitFiles() {
5590
3818
  const baseNorm = base.endsWith("/") ? base : `${base}/`;
5591
3819
  const wasmUrl = `${baseNorm}transaction_js/transaction.wasm`;
5592
3820
  const zkeyUrl = `${baseNorm}transaction_final.zkey`;
5593
- if (IS_BROWSER2) {
3821
+ if (IS_BROWSER) {
5594
3822
  return { wasmPath: wasmUrl, zkeyPath: zkeyUrl };
5595
3823
  }
5596
3824
  if (_remoteTxCircuitsCache?.base === baseNorm) {
@@ -5727,7 +3955,7 @@ async function resolveAddressLookupTableAccounts(connection, relayUrl, altAddres
5727
3955
  const fetched = await Promise.all(
5728
3956
  altAddresses.map(async (addr) => {
5729
3957
  try {
5730
- const pubkey = new import_web37.PublicKey(addr);
3958
+ const pubkey = new import_web36.PublicKey(addr);
5731
3959
  const result = await connection.getAddressLookupTable(pubkey);
5732
3960
  return result.value ?? null;
5733
3961
  } catch {
@@ -5889,7 +4117,7 @@ async function generateTransactionProof(inputs, onProgress) {
5889
4117
  onProgress?.(10);
5890
4118
  const { wasmPath: wasmInput, zkeyPath: zkeyInput } = await resolveTransactionCircuitFiles();
5891
4119
  onProgress?.(30);
5892
- const { proof, publicSignals } = await snarkjs2.groth16.fullProve(
4120
+ const { proof, publicSignals } = await snarkjs.groth16.fullProve(
5893
4121
  inputs,
5894
4122
  wasmInput,
5895
4123
  zkeyInput
@@ -5969,7 +4197,7 @@ function buildPublicInputsBytesFromSignals(publicSignals) {
5969
4197
  }
5970
4198
  var TRANSACT_DISCRIMINATOR = 0;
5971
4199
  function deriveNullifierPDA(programId, poolPDA, nullifier) {
5972
- return import_web37.PublicKey.findProgramAddressSync(
4200
+ return import_web36.PublicKey.findProgramAddressSync(
5973
4201
  [Buffer.from("nullifier"), poolPDA.toBuffer(), Buffer.from(nullifier)],
5974
4202
  programId
5975
4203
  );
@@ -6012,7 +4240,7 @@ function buildTransactInstruction(programId, payer, poolPDA, treasuryPDA, merkle
6012
4240
  // 4. nullifier PDA 0 (writable)
6013
4241
  { pubkey: nullifierPDA1, isSigner: false, isWritable: true },
6014
4242
  // 5. nullifier PDA 1 (writable)
6015
- { pubkey: import_web37.SystemProgram.programId, isSigner: false, isWritable: false }
4243
+ { pubkey: import_web36.SystemProgram.programId, isSigner: false, isWritable: false }
6016
4244
  // 6. system_program
6017
4245
  ];
6018
4246
  if (splAccounts) {
@@ -6021,23 +4249,23 @@ function buildTransactInstruction(programId, payer, poolPDA, treasuryPDA, merkle
6021
4249
  accounts.push({ pubkey: splAccounts.tokenProgram, isSigner: false, isWritable: false });
6022
4250
  if (splAccounts.payerAta) {
6023
4251
  if (enableRiskCheck) {
6024
- accounts.push({ pubkey: import_web37.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
4252
+ accounts.push({ pubkey: import_web36.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
6025
4253
  }
6026
4254
  accounts.push({ pubkey: splAccounts.payerAta, isSigner: false, isWritable: true });
6027
4255
  } else if (enableRiskCheck && !recipient) {
6028
- accounts.push({ pubkey: import_web37.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
4256
+ accounts.push({ pubkey: import_web36.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
6029
4257
  }
6030
4258
  } else {
6031
4259
  if (recipient) {
6032
4260
  accounts.push({ pubkey: recipient, isSigner: false, isWritable: true });
6033
4261
  if (enableRiskCheck) {
6034
- accounts.push({ pubkey: import_web37.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
4262
+ accounts.push({ pubkey: import_web36.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
6035
4263
  }
6036
4264
  } else if (enableRiskCheck) {
6037
- accounts.push({ pubkey: import_web37.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
4265
+ accounts.push({ pubkey: import_web36.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
6038
4266
  }
6039
4267
  }
6040
- return new import_web37.TransactionInstruction({
4268
+ return new import_web36.TransactionInstruction({
6041
4269
  programId,
6042
4270
  keys: accounts,
6043
4271
  data
@@ -6045,10 +4273,10 @@ function buildTransactInstruction(programId, payer, poolPDA, treasuryPDA, merkle
6045
4273
  }
6046
4274
  function getCommonALTAddresses() {
6047
4275
  return [
6048
- import_web37.SystemProgram.programId,
6049
- import_web37.SYSVAR_SLOT_HASHES_PUBKEY,
6050
- import_web37.SYSVAR_INSTRUCTIONS_PUBKEY,
6051
- import_web37.ComputeBudgetProgram.programId
4276
+ import_web36.SystemProgram.programId,
4277
+ import_web36.SYSVAR_SLOT_HASHES_PUBKEY,
4278
+ import_web36.SYSVAR_INSTRUCTIONS_PUBKEY,
4279
+ import_web36.ComputeBudgetProgram.programId
6052
4280
  ];
6053
4281
  }
6054
4282
  function dedupePubkeys(addresses) {
@@ -6098,23 +4326,23 @@ async function createEphemeralALT(connection, depositor, onProgress, additionalA
6098
4326
  for (let altAttempt = 0; altAttempt < MAX_ALT_CREATE_RETRIES; altAttempt++) {
6099
4327
  try {
6100
4328
  const slot = await connection.getSlot("finalized");
6101
- const [createIx, altAddress] = import_web37.AddressLookupTableProgram.createLookupTable({
4329
+ const [createIx, altAddress] = import_web36.AddressLookupTableProgram.createLookupTable({
6102
4330
  authority: depositor.publicKey,
6103
4331
  payer: depositor.publicKey,
6104
4332
  recentSlot: slot
6105
4333
  });
6106
- const extendIx = import_web37.AddressLookupTableProgram.extendLookupTable({
4334
+ const extendIx = import_web36.AddressLookupTableProgram.extendLookupTable({
6107
4335
  payer: depositor.publicKey,
6108
4336
  authority: depositor.publicKey,
6109
4337
  lookupTable: altAddress,
6110
4338
  addresses: addressesForCreate
6111
4339
  });
6112
- const tx = new import_web37.Transaction().add(createIx).add(extendIx);
4340
+ const tx = new import_web36.Transaction().add(createIx).add(extendIx);
6113
4341
  const { blockhash } = await connection.getLatestBlockhash();
6114
4342
  tx.recentBlockhash = blockhash;
6115
4343
  tx.feePayer = depositor.publicKey;
6116
4344
  if (depositor.keypair) {
6117
- await (0, import_web37.sendAndConfirmTransaction)(connection, tx, [depositor.keypair], { commitment: "confirmed" });
4345
+ await (0, import_web36.sendAndConfirmTransaction)(connection, tx, [depositor.keypair], { commitment: "confirmed" });
6118
4346
  } else if (depositor.signTransaction) {
6119
4347
  const signedTx = await depositor.signTransaction(tx);
6120
4348
  const sig = await connection.sendRawTransaction(signedTx.serialize());
@@ -6275,8 +4503,8 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
6275
4503
  if (riskQuoteIx) {
6276
4504
  baseInstructions.unshift(riskQuoteIx);
6277
4505
  }
6278
- const cuLimitIx = import_web37.ComputeBudgetProgram.setComputeUnitLimit({ units: 4e5 });
6279
- const cuPriceIx = import_web37.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1e5 });
4506
+ const cuLimitIx = import_web36.ComputeBudgetProgram.setComputeUnitLimit({ units: 4e5 });
4507
+ const cuPriceIx = import_web36.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1e5 });
6280
4508
  const fullInstructions = [...baseInstructions, cuLimitIx, cuPriceIx, transactIx];
6281
4509
  const compactInstructions = [...baseInstructions, cuLimitIx, transactIx];
6282
4510
  const minimalInstructions = [...baseInstructions, transactIx];
@@ -6287,7 +4515,7 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
6287
4515
  if (useV0) {
6288
4516
  const estimateV0Size = (ixs) => {
6289
4517
  try {
6290
- const messageV0 = new import_web37.TransactionMessage({
4518
+ const messageV0 = new import_web36.TransactionMessage({
6291
4519
  payerKey: depositor.publicKey,
6292
4520
  recentBlockhash: blockhash,
6293
4521
  instructions: ixs
@@ -6347,12 +4575,12 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
6347
4575
  }
6348
4576
  let transportAttempt = 0;
6349
4577
  while (true) {
6350
- const messageV0 = new import_web37.TransactionMessage({
4578
+ const messageV0 = new import_web36.TransactionMessage({
6351
4579
  payerKey: depositor.publicKey,
6352
4580
  recentBlockhash: blockhash,
6353
4581
  instructions: ixs
6354
4582
  }).compileToV0Message(addressLookupTableAccounts);
6355
- const versionedTx = new import_web37.VersionedTransaction(messageV0);
4583
+ const versionedTx = new import_web36.VersionedTransaction(messageV0);
6356
4584
  try {
6357
4585
  if (depositor.keypair) {
6358
4586
  onProgress?.("Signing and sending transaction...");
@@ -6485,7 +4713,7 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
6485
4713
  let legacyTransportAttempt = 0;
6486
4714
  while (true) {
6487
4715
  try {
6488
- const tx = new import_web37.Transaction();
4716
+ const tx = new import_web36.Transaction();
6489
4717
  for (const ix of instructions) {
6490
4718
  tx.add(ix);
6491
4719
  }
@@ -6493,7 +4721,7 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
6493
4721
  tx.feePayer = depositor.publicKey;
6494
4722
  if (depositor.keypair) {
6495
4723
  onProgress?.("Signing and sending transaction...");
6496
- signature = await (0, import_web37.sendAndConfirmTransaction)(
4724
+ signature = await (0, import_web36.sendAndConfirmTransaction)(
6497
4725
  connection,
6498
4726
  tx,
6499
4727
  [depositor.keypair],
@@ -6667,7 +4895,7 @@ async function fetchRiskQuoteInstruction(riskQuoteUrl, wallet, options) {
6667
4895
  const signerB58 = signerKey;
6668
4896
  const signatureBuf = Buffer.from(signatureB64, "base64");
6669
4897
  const messageArr = hexToBytes2(messageHex);
6670
- const publicKeyBytes = new import_web37.PublicKey(signerB58).toBytes();
4898
+ const publicKeyBytes = new import_web36.PublicKey(signerB58).toBytes();
6671
4899
  if (signatureBuf.length !== 64 || messageArr.length !== 33 && messageArr.length !== 41 && messageArr.length !== 50 || publicKeyBytes.length !== 32) {
6672
4900
  throw new Error(
6673
4901
  `Invalid risk quote response: invalid lengths (sig=${signatureBuf.length}, msg=${messageArr.length}, pk=${publicKeyBytes.length})`
@@ -6676,7 +4904,7 @@ async function fetchRiskQuoteInstruction(riskQuoteUrl, wallet, options) {
6676
4904
  const signature = new Uint8Array(signatureBuf);
6677
4905
  const message = new Uint8Array(messageArr);
6678
4906
  const publicKey = new Uint8Array(publicKeyBytes);
6679
- return import_web37.Ed25519Program.createInstructionWithPublicKey({
4907
+ return import_web36.Ed25519Program.createInstructionWithPublicKey({
6680
4908
  publicKey,
6681
4909
  message,
6682
4910
  signature
@@ -6689,10 +4917,10 @@ async function fetchRiskQuoteInstruction(riskQuoteUrl, wallet, options) {
6689
4917
  `Invalid risk quote response: expected relay format { signature, message, signer_pubkey } or { instruction }. Received keys: ${keys}`
6690
4918
  );
6691
4919
  }
6692
- return new import_web37.TransactionInstruction({
6693
- programId: new import_web37.PublicKey(raw.programId),
4920
+ return new import_web36.TransactionInstruction({
4921
+ programId: new import_web36.PublicKey(raw.programId),
6694
4922
  keys: raw.keys.map((k) => ({
6695
- pubkey: new import_web37.PublicKey(k.pubkey),
4923
+ pubkey: new import_web36.PublicKey(k.pubkey),
6696
4924
  isSigner: k.isSigner,
6697
4925
  isWritable: k.isWritable
6698
4926
  })),
@@ -6746,6 +4974,7 @@ async function transact(params, options) {
6746
4974
  if (outputUtxos.length > 2) {
6747
4975
  throw new Error("Maximum 2 output UTXOs allowed");
6748
4976
  }
4977
+ await preflightNullifiers(inputUtxos, connection, programId);
6749
4978
  const mint = inputUtxos.find((u) => u.amount > 0)?.mintAddress ?? outputUtxos.find((u) => u.amount > 0)?.mintAddress ?? NATIVE_SOL_MINT;
6750
4979
  const paddedInputs = [...inputUtxos];
6751
4980
  while (paddedInputs.length < 2) {
@@ -7061,7 +5290,7 @@ async function transact(params, options) {
7061
5290
  const outputCommitments = [];
7062
5291
  for (let i = 0; i < paddedOutputs.length; i++) {
7063
5292
  const utxo = paddedOutputs[i];
7064
- const commitment = await computeCommitment2(utxo);
5293
+ const commitment = await computeCommitment(utxo);
7065
5294
  outputCommitments.push(commitment);
7066
5295
  }
7067
5296
  onProgress?.("Computing external data hash...");
@@ -7142,7 +5371,7 @@ async function transact(params, options) {
7142
5371
  }
7143
5372
  inputNullifiers = [];
7144
5373
  for (const utxo of paddedInputs) {
7145
- inputNullifiers.push(await computeNullifier2(utxo));
5374
+ inputNullifiers.push(await computeNullifier(utxo));
7146
5375
  }
7147
5376
  if (nextIndex !== null) {
7148
5377
  onProgress?.(`[Submit ${submissionAttempts + 1}/${maxRootRetries + 1}] Root: ${rootHex.substring(0, 16)}..., nextIndex: ${nextIndex}`);
@@ -7573,10 +5802,46 @@ async function transact(params, options) {
7573
5802
  }
7574
5803
  }
7575
5804
  if (lastError) {
5805
+ const classified = classifyRelayError(lastError.message);
5806
+ const isMerkleClass = !(classified instanceof UtxoAlreadySpentError) && !(classified instanceof SanctionsQuoteError) && !isRootNotFoundError2(lastError);
5807
+ if (!IS_BROWSER && isMerkleClass && relayUrl) {
5808
+ onProgress?.(
5809
+ "All relay-tree attempts failed. Rebuilding merkle tree from chain as last resort..."
5810
+ );
5811
+ try {
5812
+ const [merkleTreePda] = import_web36.PublicKey.findProgramAddressSync(
5813
+ [Buffer.from("merkle_tree"), mint.toBuffer()],
5814
+ programId
5815
+ );
5816
+ const chainTree = await buildMerkleTreeFromChain(
5817
+ connection,
5818
+ programId,
5819
+ merkleTreePda,
5820
+ onProgress
5821
+ );
5822
+ const chainRootHex = chainTree.root().toString(16).padStart(64, "0");
5823
+ onProgress?.(
5824
+ `Chain-rebuilt tree root: ${chainRootHex.substring(0, 16)}... \u2014 attached to error for caller retry`
5825
+ );
5826
+ throw new RelayInternalError(
5827
+ `All relay-tree attempts failed. Chain-rebuilt tree available (root ${chainRootHex.substring(0, 16)}...). Retry this call with options.cachedMerkleTree = err.cachedTreeFromChain. Original error: ${lastError.message}`,
5828
+ lastError.message,
5829
+ void 0,
5830
+ chainTree
5831
+ );
5832
+ } catch (e) {
5833
+ if (e instanceof RelayInternalError) throw e;
5834
+ onProgress?.(
5835
+ `Chain-replay fallback failed: ${e instanceof Error ? e.message : String(e)}`
5836
+ );
5837
+ }
5838
+ }
7576
5839
  if (isRootNotFoundError2(lastError)) {
7577
- throw new Error(`Transaction failed after ${maxRootRetries + 1} retries: ${lastError.message}`);
5840
+ throw new Error(
5841
+ `Transaction failed after ${maxRootRetries + 1} retries: ${lastError.message}`
5842
+ );
7578
5843
  }
7579
- throw new Error(`Transaction failed: ${lastError.message}`);
5844
+ throw classified;
7580
5845
  }
7581
5846
  if (relayUrl && outputCommitments.length >= 2) {
7582
5847
  const hasOnChainIndices = commitmentIndices[0] >= 0 && commitmentIndices[1] >= 0;
@@ -7880,6 +6145,7 @@ async function swapUtxo(params, options) {
7880
6145
  if (inputUtxos.length > 2) {
7881
6146
  throw new Error("Maximum 2 input UTXOs allowed");
7882
6147
  }
6148
+ await preflightNullifiers(inputUtxos, connection, programId);
7883
6149
  const outputMintAccount = await connection.getAccountInfo(outputMint, {
7884
6150
  commitment: "confirmed"
7885
6151
  });
@@ -8187,7 +6453,7 @@ async function swapUtxo(params, options) {
8187
6453
  let inputNullifiers = [];
8188
6454
  const outputCommitments = [];
8189
6455
  for (const utxo of paddedOutputs) {
8190
- outputCommitments.push(await computeCommitment2(utxo));
6456
+ outputCommitments.push(await computeCommitment(utxo));
8191
6457
  }
8192
6458
  const chainNoteTimestamp = BigInt(Date.now());
8193
6459
  onProgress?.("Computing external data hash...");
@@ -8267,7 +6533,7 @@ async function swapUtxo(params, options) {
8267
6533
  }
8268
6534
  inputNullifiers = [];
8269
6535
  for (const utxo of paddedInputs) {
8270
- inputNullifiers.push(await computeNullifier2(utxo));
6536
+ inputNullifiers.push(await computeNullifier(utxo));
8271
6537
  }
8272
6538
  if (merkleState) {
8273
6539
  onProgress?.(`[Attempt ${attempt + 1}/${maxRootRetries + 1}] Root: ${rootHex.substring(0, 16)}..., nextIndex: ${merkleState.nextIndex}`);
@@ -8611,7 +6877,7 @@ async function swapWithChange(inputUtxos, swapAmount, outputMint, recipientAta,
8611
6877
 
8612
6878
  // src/core/scanner.ts
8613
6879
  var import_bs582 = __toESM(require("bs58"), 1);
8614
- var import_web38 = require("@solana/web3.js");
6880
+ var import_web37 = require("@solana/web3.js");
8615
6881
  var import_spl_token2 = require("@solana/spl-token");
8616
6882
  var bs582 = import_bs582.default.default || import_bs582.default;
8617
6883
  var TRANSACT_TAG2 = 0;
@@ -8698,7 +6964,7 @@ function parseSwapOutputMint(data) {
8698
6964
  const mintBytes = data.slice(SWAP_OUTPUT_MINT_OFFSET, end);
8699
6965
  if (mintBytes.every((byte) => byte === 0)) return void 0;
8700
6966
  try {
8701
- return new import_web38.PublicKey(mintBytes).toBase58();
6967
+ return new import_web37.PublicKey(mintBytes).toBase58();
8702
6968
  } catch {
8703
6969
  return void 0;
8704
6970
  }
@@ -8788,7 +7054,7 @@ function parseSwapRecipientAta(data) {
8788
7054
  const recipientAtaBytes = data.slice(recipientAtaStart, recipientAtaEnd);
8789
7055
  if (recipientAtaBytes.every((byte) => byte === 0)) return void 0;
8790
7056
  try {
8791
- return new import_web38.PublicKey(recipientAtaBytes).toBase58();
7057
+ return new import_web37.PublicKey(recipientAtaBytes).toBase58();
8792
7058
  } catch {
8793
7059
  return void 0;
8794
7060
  }
@@ -9103,8 +7369,8 @@ async function scanTransactions(opts) {
9103
7369
  if (onChainAta) {
9104
7370
  try {
9105
7371
  const expectedAta = (0, import_spl_token2.getAssociatedTokenAddressSync)(
9106
- new import_web38.PublicKey(asset.mint),
9107
- new import_web38.PublicKey(walletPublicKey)
7372
+ new import_web37.PublicKey(asset.mint),
7373
+ new import_web37.PublicKey(walletPublicKey)
9108
7374
  ).toBase58();
9109
7375
  if (onChainAta === expectedAta) {
9110
7376
  isOurs = true;
@@ -9407,7 +7673,7 @@ function formatComplianceCsv(report) {
9407
7673
  }
9408
7674
 
9409
7675
  // src/core/wallet.ts
9410
- var import_web39 = require("@solana/web3.js");
7676
+ var import_web38 = require("@solana/web3.js");
9411
7677
  init_utxo();
9412
7678
  var UtxoWallet = class _UtxoWallet {
9413
7679
  constructor(viewingKey) {
@@ -9620,7 +7886,7 @@ var UtxoWallet = class _UtxoWallet {
9620
7886
  data.viewingKey ? new Uint8Array(data.viewingKey) : void 0
9621
7887
  );
9622
7888
  for (const w of data.wallets) {
9623
- const mint = new import_web39.PublicKey(w.mint);
7889
+ const mint = new import_web38.PublicKey(w.mint);
9624
7890
  for (const u of w.utxos) {
9625
7891
  wallet.addUtxo({
9626
7892
  amount: BigInt(u.amount),
@@ -9703,16 +7969,14 @@ var SimpleWallet = class {
9703
7969
  };
9704
7970
 
9705
7971
  // src/index.ts
9706
- var VERSION = "1.0.0";
7972
+ var VERSION = "0.1.6";
9707
7973
  var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
9708
7974
  // Annotate the CommonJS export names for ESM import in node:
9709
7975
  0 && (module.exports = {
9710
7976
  CLOAK_PROGRAM_ID,
9711
7977
  CloakError,
9712
7978
  CloakSDK,
9713
- DEFAULT_CIRCUITS_URL,
9714
7979
  DEFAULT_TRANSACTION_CIRCUITS_URL,
9715
- EXPECTED_CIRCUIT_HASHES,
9716
7980
  FIXED_FEE_LAMPORTS,
9717
7981
  LAMPORTS_PER_SOL,
9718
7982
  LocalStorageAdapter,
@@ -9721,18 +7985,20 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
9721
7985
  MemoryStorageAdapter,
9722
7986
  MerkleTree,
9723
7987
  NATIVE_SOL_MINT,
7988
+ RelayInternalError,
9724
7989
  RelayService,
9725
7990
  RootNotFoundError,
9726
7991
  SCANNER_SUPPORTS_TRANSACT_SWAP,
9727
7992
  SIGN_IN_MESSAGE,
7993
+ SanctionsQuoteError,
9728
7994
  ShieldPoolErrors,
9729
7995
  SimpleWallet,
7996
+ UtxoAlreadySpentError,
9730
7997
  UtxoWallet,
9731
7998
  VARIABLE_FEE_DENOMINATOR,
9732
7999
  VARIABLE_FEE_NUMERATOR,
9733
8000
  VARIABLE_FEE_RATE,
9734
8001
  VERSION,
9735
- areCircuitsAvailable,
9736
8002
  bigintToBytes32,
9737
8003
  bigintToHex,
9738
8004
  buildMerkleTree,
@@ -9745,30 +8011,16 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
9745
8011
  calculateRelayFee,
9746
8012
  chainNoteFromBase64,
9747
8013
  chainNoteToBase64,
9748
- cleanupStalePendingOperations,
9749
- clearPendingDeposits,
9750
- clearPendingWithdrawals,
8014
+ classifyRelayError,
9751
8015
  computeChainNoteHash,
9752
- computeCommitment,
9753
8016
  computeExtDataHash,
9754
8017
  computeMerkleRoot,
9755
- computeNullifier,
9756
- computeNullifierAsync,
9757
- computeNullifierSync,
9758
- computeOutputsHash,
9759
- computeOutputsHashAsync,
9760
- computeOutputsHashSync,
9761
8018
  computeProofForLatestDeposit,
9762
8019
  computeProofFromChain,
9763
8020
  computeSignature,
9764
- computeSwapOutputsHash,
9765
- computeSwapOutputsHashAsync,
9766
- computeSwapOutputsHashSync,
9767
8021
  computeUtxoCommitment,
9768
8022
  computeUtxoNullifier,
9769
- copyNoteToClipboard,
9770
8023
  createCloakError,
9771
- createDepositInstruction,
9772
8024
  createLogger,
9773
8025
  createUtxo,
9774
8026
  createZeroUtxo,
@@ -9788,77 +8040,52 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
9788
8040
  deriveViewingKeyFromUtxoPrivateKey,
9789
8041
  deserializeUtxo,
9790
8042
  detectNetworkFromRpcUrl,
9791
- downloadNote,
9792
- encodeNoteSimple,
9793
8043
  encryptCompactChainNote,
9794
8044
  encryptNoteForRecipient,
9795
8045
  encryptTransactionMetadata,
9796
8046
  encryptTransactionMetadataBundle,
9797
8047
  expandSpendKey,
9798
8048
  exportKeys,
9799
- exportNote,
9800
- exportWalletKeys,
9801
8049
  fetchCommitments,
9802
8050
  fetchRiskQuoteInstruction,
9803
8051
  fetchRiskQuoteIx,
9804
- filterNotesByNetwork,
9805
- filterWithdrawableNotes,
9806
- findNoteByCommitment,
9807
8052
  formatAmount,
9808
8053
  formatComplianceCsv,
9809
8054
  formatErrorForLogging,
9810
8055
  formatSol,
9811
8056
  fullWithdraw,
9812
8057
  generateCloakKeys,
9813
- generateCommitment,
9814
- generateCommitmentAsync,
9815
8058
  generateMasterSeed,
9816
- generateNote,
9817
- generateNoteFromWallet,
9818
8059
  generateUtxoKeypair,
9819
8060
  generateViewingKeyPair,
9820
- generateWithdrawRegularProof,
9821
- generateWithdrawSwapProof,
9822
8061
  getAddressExplorerUrl,
9823
8062
  getCircuitsPath,
9824
- getDefaultCircuitsPath,
9825
8063
  getDistributableAmount,
9826
8064
  getExplorerUrl,
9827
8065
  getNkFromUtxoPrivateKey,
9828
8066
  getNullifierPDA,
9829
- getPendingOperationsSummary,
9830
8067
  getPublicKey,
9831
- getPublicViewKey,
9832
- getRecipientAmount,
9833
8068
  getRpcUrlForNetwork,
9834
8069
  getShieldPoolPDAs,
9835
8070
  getSwapStatePDA,
9836
- getViewKey,
9837
- hasPendingOperations,
9838
8071
  hexToBigint,
9839
8072
  hexToBytes,
9840
8073
  importKeys,
9841
- importWalletKeys,
9842
8074
  isDebugEnabled,
9843
8075
  isRootNotFoundError,
9844
8076
  isValidHex,
9845
8077
  isValidRpcUrl,
9846
8078
  isValidSolanaAddress,
9847
8079
  isWithdrawAmountSufficient,
9848
- isWithdrawable,
9849
8080
  keypairToAdapter,
9850
- loadPendingDeposits,
9851
- loadPendingWithdrawals,
9852
8081
  parseAmount,
9853
8082
  parseError,
9854
- parseNote,
9855
8083
  parseRelayErrorResponse,
9856
8084
  parseTransactionError,
9857
8085
  partialWithdraw,
9858
8086
  poseidonHash,
9859
8087
  preflightCheck,
9860
- prepareEncryptedOutput,
9861
- prepareEncryptedOutputForRecipient,
8088
+ preflightNullifiers,
9862
8089
  proofToBytes,
9863
8090
  pubkeyToFieldElement,
9864
8091
  pubkeyToLimbs,
@@ -9866,16 +8093,11 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
9866
8093
  randomFieldElement,
9867
8094
  readMerkleTreeState,
9868
8095
  registerViewingKey,
9869
- removePendingDeposit,
9870
- removePendingWithdrawal,
9871
- savePendingDeposit,
9872
- savePendingWithdrawal,
9873
8096
  scanNotesForWallet,
9874
8097
  scanTransactions,
9875
8098
  sdkLogger,
9876
8099
  selectUtxos,
9877
8100
  sendTransaction,
9878
- serializeNote,
9879
8101
  serializeUtxo,
9880
8102
  setCircuitsPath,
9881
8103
  setDebugMode,
@@ -9889,21 +8111,13 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
9889
8111
  transfer,
9890
8112
  truncate,
9891
8113
  tryDecryptNote,
9892
- updateNoteWithDeposit,
9893
- updatePendingDeposit,
9894
- updatePendingWithdrawal,
9895
8114
  utxoBigintToBytes32,
9896
8115
  utxoEquals,
9897
8116
  utxoHexToBigint,
9898
- validateDepositParams,
9899
- validateNote,
9900
8117
  validateOutputsSum,
9901
8118
  validateRoot,
9902
- validateTransfers,
9903
8119
  validateWalletConnected,
9904
- validateWithdrawableNote,
9905
- verifyAllCircuits,
9906
- verifyCircuitIntegrity,
8120
+ verifyUtxos,
9907
8121
  waitForRoot,
9908
8122
  withTiming
9909
8123
  });