@cloak.ag/sdk 1.0.4 → 1.0.5

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
@@ -34,12 +34,14 @@ __export(index_exports, {
34
34
  CloakError: () => CloakError,
35
35
  CloakSDK: () => CloakSDK,
36
36
  DepositRecoveryService: () => DepositRecoveryService,
37
+ EXPECTED_CIRCUIT_HASHES: () => EXPECTED_CIRCUIT_HASHES,
37
38
  FIXED_FEE_LAMPORTS: () => FIXED_FEE_LAMPORTS,
38
39
  IndexerService: () => IndexerService,
39
40
  LAMPORTS_PER_SOL: () => LAMPORTS_PER_SOL,
40
41
  LocalStorageAdapter: () => LocalStorageAdapter,
41
42
  MemoryStorageAdapter: () => MemoryStorageAdapter,
42
43
  RelayService: () => RelayService,
44
+ ShieldPoolErrors: () => ShieldPoolErrors,
43
45
  VARIABLE_FEE_RATE: () => VARIABLE_FEE_RATE,
44
46
  VERSION: () => VERSION,
45
47
  bigintToBytes32: () => bigintToBytes32,
@@ -47,6 +49,9 @@ __export(index_exports, {
47
49
  bytesToHex: () => bytesToHex,
48
50
  calculateFee: () => calculateFee2,
49
51
  calculateRelayFee: () => calculateRelayFee,
52
+ cleanupStalePendingOperations: () => cleanupStalePendingOperations,
53
+ clearPendingDeposits: () => clearPendingDeposits,
54
+ clearPendingWithdrawals: () => clearPendingWithdrawals,
50
55
  computeCommitment: () => computeCommitment,
51
56
  computeMerkleRoot: () => computeMerkleRoot,
52
57
  computeNullifier: () => computeNullifier,
@@ -86,12 +91,14 @@ __export(index_exports, {
86
91
  getAddressExplorerUrl: () => getAddressExplorerUrl,
87
92
  getDistributableAmount: () => getDistributableAmount2,
88
93
  getExplorerUrl: () => getExplorerUrl,
94
+ getPendingOperationsSummary: () => getPendingOperationsSummary,
89
95
  getPublicKey: () => getPublicKey,
90
96
  getPublicViewKey: () => getPublicViewKey,
91
97
  getRecipientAmount: () => getRecipientAmount,
92
98
  getRpcUrlForNetwork: () => getRpcUrlForNetwork,
93
99
  getShieldPoolPDAs: () => getShieldPoolPDAs,
94
100
  getViewKey: () => getViewKey,
101
+ hasPendingOperations: () => hasPendingOperations,
95
102
  hexToBigint: () => hexToBigint,
96
103
  hexToBytes: () => hexToBytes,
97
104
  importKeys: () => importKeys,
@@ -101,7 +108,10 @@ __export(index_exports, {
101
108
  isValidSolanaAddress: () => isValidSolanaAddress,
102
109
  isWithdrawable: () => isWithdrawable,
103
110
  keypairToAdapter: () => keypairToAdapter,
111
+ loadPendingDeposits: () => loadPendingDeposits,
112
+ loadPendingWithdrawals: () => loadPendingWithdrawals,
104
113
  parseAmount: () => parseAmount,
114
+ parseError: () => parseError,
105
115
  parseNote: () => parseNote,
106
116
  parseTransactionError: () => parseTransactionError,
107
117
  poseidonHash: () => poseidonHash,
@@ -110,6 +120,10 @@ __export(index_exports, {
110
120
  proofToBytes: () => proofToBytes,
111
121
  pubkeyToLimbs: () => pubkeyToLimbs,
112
122
  randomBytes: () => randomBytes,
123
+ removePendingDeposit: () => removePendingDeposit,
124
+ removePendingWithdrawal: () => removePendingWithdrawal,
125
+ savePendingDeposit: () => savePendingDeposit,
126
+ savePendingWithdrawal: () => savePendingWithdrawal,
113
127
  scanNotesForWallet: () => scanNotesForWallet,
114
128
  sendTransaction: () => sendTransaction,
115
129
  serializeNote: () => serializeNote,
@@ -117,12 +131,16 @@ __export(index_exports, {
117
131
  splitTo2Limbs: () => splitTo2Limbs,
118
132
  tryDecryptNote: () => tryDecryptNote,
119
133
  updateNoteWithDeposit: () => updateNoteWithDeposit,
134
+ updatePendingDeposit: () => updatePendingDeposit,
135
+ updatePendingWithdrawal: () => updatePendingWithdrawal,
120
136
  validateDepositParams: () => validateDepositParams,
121
137
  validateNote: () => validateNote,
122
138
  validateOutputsSum: () => validateOutputsSum,
123
139
  validateTransfers: () => validateTransfers,
124
140
  validateWalletConnected: () => validateWalletConnected,
125
- validateWithdrawableNote: () => validateWithdrawableNote
141
+ validateWithdrawableNote: () => validateWithdrawableNote,
142
+ verifyAllCircuits: () => verifyAllCircuits,
143
+ verifyCircuitIntegrity: () => verifyCircuitIntegrity
126
144
  });
127
145
  module.exports = __toCommonJS(index_exports);
128
146
 
@@ -289,14 +307,14 @@ function randomBytes(length) {
289
307
  } catch {
290
308
  }
291
309
  try {
292
- const nodeCrypto = require("crypto");
293
- if (nodeCrypto?.randomBytes) {
294
- const buffer = nodeCrypto.randomBytes(length);
310
+ const nodeCrypto2 = require("crypto");
311
+ if (nodeCrypto2?.randomBytes) {
312
+ const buffer = nodeCrypto2.randomBytes(length);
295
313
  bytes.set(buffer);
296
314
  return bytes;
297
315
  }
298
- if (nodeCrypto?.webcrypto?.getRandomValues) {
299
- nodeCrypto.webcrypto.getRandomValues(bytes);
316
+ if (nodeCrypto2?.webcrypto?.getRandomValues) {
317
+ nodeCrypto2.webcrypto.getRandomValues(bytes);
300
318
  return bytes;
301
319
  }
302
320
  } catch {
@@ -418,8 +436,8 @@ function generateMasterSeed() {
418
436
  cryptoObj.getRandomValues(seed);
419
437
  } else {
420
438
  try {
421
- const nodeCrypto = require("crypto");
422
- const buffer = nodeCrypto.randomBytes(32);
439
+ const nodeCrypto2 = require("crypto");
440
+ const buffer = nodeCrypto2.randomBytes(32);
423
441
  seed.set(buffer);
424
442
  } catch {
425
443
  throw new Error("No secure random number generator available");
@@ -1160,6 +1178,8 @@ var RelayService = class {
1160
1178
  *
1161
1179
  * @param params - Withdrawal parameters
1162
1180
  * @param onStatusUpdate - Optional callback for status updates
1181
+ * @param onRequestId - CRITICAL: Callback when request_id is received.
1182
+ * Persist this ID to recover if browser crashes during polling.
1163
1183
  * @returns Transaction signature when completed
1164
1184
  *
1165
1185
  * @example
@@ -1169,11 +1189,12 @@ var RelayService = class {
1169
1189
  * publicInputs: { root, nf, outputs_hash, amount },
1170
1190
  * outputs: [{ recipient: addr, amount: lamports }],
1171
1191
  * feeBps: 50
1172
- * }, (status) => console.log(`Status: ${status}`));
1192
+ * }, (status) => console.log(`Status: ${status}`),
1193
+ * (requestId) => localStorage.setItem('pending_withdraw', requestId));
1173
1194
  * console.log(`Transaction: ${signature}`);
1174
1195
  * ```
1175
1196
  */
1176
- async submitWithdraw(params, onStatusUpdate) {
1197
+ async submitWithdraw(params, onStatusUpdate, onRequestId) {
1177
1198
  const proofBytes = hexToBytes(params.proof);
1178
1199
  const proofBase64 = this.bytesToBase64(proofBytes);
1179
1200
  const requestBody = {
@@ -1214,8 +1235,55 @@ var RelayService = class {
1214
1235
  if (!requestId) {
1215
1236
  throw new Error("Relay response missing request_id");
1216
1237
  }
1238
+ if (onRequestId) {
1239
+ onRequestId(requestId);
1240
+ }
1217
1241
  return this.pollForCompletion(requestId, onStatusUpdate);
1218
1242
  }
1243
+ /**
1244
+ * Resume polling for a withdrawal that was previously started
1245
+ *
1246
+ * Use this after page reload to check status of a pending withdrawal.
1247
+ * The requestId should have been persisted via the onRequestId callback.
1248
+ *
1249
+ * @param requestId - Request ID from a previous submitWithdraw call
1250
+ * @param onStatusUpdate - Optional callback for status updates
1251
+ * @returns Transaction signature if completed, null if still pending/failed
1252
+ *
1253
+ * @example
1254
+ * ```typescript
1255
+ * // On page load, check for pending withdrawal
1256
+ * const pendingId = localStorage.getItem('pending_withdraw');
1257
+ * if (pendingId) {
1258
+ * const result = await relay.resumeWithdraw(pendingId);
1259
+ * if (result.status === 'completed') {
1260
+ * console.log('Withdrawal completed:', result.signature);
1261
+ * localStorage.removeItem('pending_withdraw');
1262
+ * }
1263
+ * }
1264
+ * ```
1265
+ */
1266
+ async resumeWithdraw(requestId, onStatusUpdate) {
1267
+ try {
1268
+ const status = await this.getStatus(requestId);
1269
+ if (onStatusUpdate) {
1270
+ onStatusUpdate(status.status);
1271
+ }
1272
+ if (status.status === "completed") {
1273
+ return { status: "completed", signature: status.txId };
1274
+ } else if (status.status === "failed") {
1275
+ return { status: "failed", error: status.error };
1276
+ } else {
1277
+ const signature = await this.pollForCompletion(requestId, onStatusUpdate);
1278
+ return { status: "completed", signature };
1279
+ }
1280
+ } catch (error) {
1281
+ return {
1282
+ status: "failed",
1283
+ error: error instanceof Error ? error.message : String(error)
1284
+ };
1285
+ }
1286
+ }
1219
1287
  /**
1220
1288
  * Poll for withdrawal completion
1221
1289
  *
@@ -1288,7 +1356,7 @@ var RelayService = class {
1288
1356
  * console.log(`Transaction: ${signature}`);
1289
1357
  * ```
1290
1358
  */
1291
- async submitSwap(params, onStatusUpdate) {
1359
+ async submitSwap(params, onStatusUpdate, onRequestId) {
1292
1360
  const proofBytes = hexToBytes(params.proof);
1293
1361
  const proofBase64 = this.bytesToBase64(proofBytes);
1294
1362
  const requestBody = {
@@ -1330,6 +1398,9 @@ var RelayService = class {
1330
1398
  if (!requestId) {
1331
1399
  throw new Error("Relay response missing request_id");
1332
1400
  }
1401
+ if (onRequestId) {
1402
+ onRequestId(requestId);
1403
+ }
1333
1404
  return this.pollForCompletion(requestId, onStatusUpdate);
1334
1405
  }
1335
1406
  /**
@@ -1566,29 +1637,94 @@ var DepositRecoveryService = class {
1566
1637
  }
1567
1638
  /**
1568
1639
  * Check if a deposit already exists in the indexer
1640
+ * Uses the enhanced deposit lookup endpoint with include_proof=true
1569
1641
  *
1570
1642
  * @private
1571
1643
  */
1572
- async checkExistingDeposit(_commitment) {
1644
+ async checkExistingDeposit(commitment) {
1573
1645
  try {
1574
- const { next_index } = await this.indexer.getMerkleRoot();
1575
- const batchSize = 100;
1576
- for (let i = 0; i < next_index; i += batchSize) {
1577
- const end = Math.min(i + batchSize - 1, next_index - 1);
1578
- const { notes } = await this.indexer.getNotesRange(i, end, batchSize);
1579
- for (let j = 0; j < notes.length; j++) {
1580
- try {
1581
- return null;
1582
- } catch (e) {
1583
- continue;
1584
- }
1646
+ const cleanCommit = commitment.replace(/^0x/, "").toLowerCase();
1647
+ const response = await fetch(
1648
+ `${this.apiUrl}/api/v1/deposit/${cleanCommit}?include_proof=true`
1649
+ );
1650
+ if (!response.ok) {
1651
+ if (response.status === 404) {
1652
+ return null;
1585
1653
  }
1654
+ throw new Error(`Failed to check deposit: ${response.status}`);
1586
1655
  }
1587
- return null;
1656
+ const data = await response.json();
1657
+ if (!data.merkle_proof) {
1658
+ const merkleProof = await this.indexer.getMerkleProof(data.leaf_index);
1659
+ return {
1660
+ leafIndex: data.leaf_index,
1661
+ root: merkleProof.root || "",
1662
+ slot: data.slot,
1663
+ merkleProof: {
1664
+ pathElements: merkleProof.pathElements,
1665
+ pathIndices: merkleProof.pathIndices
1666
+ }
1667
+ };
1668
+ }
1669
+ return {
1670
+ leafIndex: data.leaf_index,
1671
+ root: data.merkle_proof.root,
1672
+ slot: data.slot,
1673
+ merkleProof: {
1674
+ pathElements: data.merkle_proof.path_elements,
1675
+ pathIndices: data.merkle_proof.path_indices
1676
+ }
1677
+ };
1588
1678
  } catch (error) {
1589
1679
  return null;
1590
1680
  }
1591
1681
  }
1682
+ /**
1683
+ * Recover deposit by transaction signature
1684
+ * Uses the indexer's signature lookup endpoint
1685
+ */
1686
+ async recoverBySignature(signature) {
1687
+ try {
1688
+ const response = await fetch(
1689
+ `${this.apiUrl}/api/v1/deposit/tx/${signature}?include_proof=true`
1690
+ );
1691
+ if (!response.ok) {
1692
+ if (response.status === 404) {
1693
+ return {
1694
+ success: false,
1695
+ error: "Deposit not found for this transaction signature"
1696
+ };
1697
+ }
1698
+ const errorText = await response.text();
1699
+ return {
1700
+ success: false,
1701
+ error: `Failed to recover deposit: ${errorText}`
1702
+ };
1703
+ }
1704
+ const data = await response.json();
1705
+ if (!data.merkle_proof) {
1706
+ return {
1707
+ success: false,
1708
+ error: "Deposit found but merkle proof not available"
1709
+ };
1710
+ }
1711
+ return {
1712
+ success: true,
1713
+ leafIndex: data.leaf_index,
1714
+ root: data.merkle_proof.root,
1715
+ slot: data.slot,
1716
+ merkleProof: {
1717
+ pathElements: data.merkle_proof.path_elements,
1718
+ pathIndices: data.merkle_proof.path_indices
1719
+ }
1720
+ };
1721
+ } catch (error) {
1722
+ return {
1723
+ success: false,
1724
+ error: error instanceof Error ? error.message : String(error)
1725
+ };
1726
+ }
1727
+ }
1592
1728
  /**
1593
1729
  * Finalize a deposit via server API (alternative recovery method)
1594
1730
  *
@@ -1744,39 +1880,15 @@ function getShieldPoolPDAs(programId, mint) {
1744
1880
 
1745
1881
  // src/utils/proof-generation.ts
1746
1882
  var snarkjs = __toESM(require("snarkjs"), 1);
1747
- var path = null;
1748
- var fs = null;
1749
- async function loadNodeModules() {
1750
- const isBrowser = typeof window !== "undefined" || typeof globalThis !== "undefined" && globalThis.window;
1751
- if (!isBrowser && typeof process !== "undefined" && process.versions?.node) {
1752
- if (!path) {
1753
- path = await import("path");
1754
- }
1755
- if (!fs) {
1756
- fs = await import("fs");
1757
- }
1758
- return { path, fs };
1759
- }
1760
- return { path: null, fs: null };
1761
- }
1883
+ var IS_BROWSER = typeof window !== "undefined" || typeof globalThis !== "undefined" && typeof globalThis.document !== "undefined";
1762
1884
  function joinPath(...parts) {
1763
- const isBrowser = typeof window !== "undefined" || typeof globalThis !== "undefined" && globalThis.window;
1764
- if (!isBrowser && path) {
1765
- return path.join(...parts);
1885
+ if (IS_BROWSER) {
1886
+ return parts.join("/").replace(/\/+/g, "/");
1766
1887
  }
1767
1888
  return parts.join("/").replace(/\/+/g, "/");
1768
1889
  }
1769
1890
  async function fileExists(filePath) {
1770
- const { fs: fs2 } = await loadNodeModules();
1771
- if (fs2) {
1772
- try {
1773
- return fs2.existsSync(filePath);
1774
- } catch {
1775
- return false;
1776
- }
1777
- }
1778
- const isBrowser = typeof window !== "undefined" || typeof globalThis !== "undefined" && globalThis.window;
1779
- if (isBrowser) {
1891
+ if (IS_BROWSER) {
1780
1892
  try {
1781
1893
  const response = await fetch(filePath, { method: "HEAD" });
1782
1894
  return response.ok;
@@ -1784,18 +1896,26 @@ async function fileExists(filePath) {
1784
1896
  return false;
1785
1897
  }
1786
1898
  }
1787
- return false;
1899
+ try {
1900
+ const nodeFs2 = globalThis.require?.("fs") || (typeof require !== "undefined" ? require("fs") : null);
1901
+ if (nodeFs2) {
1902
+ return nodeFs2.existsSync(filePath);
1903
+ }
1904
+ const fsModule = await import("fs");
1905
+ return fsModule.existsSync(filePath);
1906
+ } catch {
1907
+ return false;
1908
+ }
1788
1909
  }
1789
- async function generateWithdrawRegularProof(inputs, circuitsPath) {
1790
- const isBrowser = typeof window !== "undefined" || typeof globalThis !== "undefined" && globalThis.window;
1910
+ async function generateWithdrawRegularProof(inputs, circuitsPath2) {
1791
1911
  let wasmPath;
1792
1912
  let zkeyPath;
1793
- if (isBrowser) {
1794
- wasmPath = `${circuitsPath}/withdraw_regular_js/withdraw_regular.wasm`;
1795
- zkeyPath = `${circuitsPath}/withdraw_regular_final.zkey`;
1913
+ if (IS_BROWSER) {
1914
+ wasmPath = `${circuitsPath2}/withdraw_regular_js/withdraw_regular.wasm`;
1915
+ zkeyPath = `${circuitsPath2}/withdraw_regular_final.zkey`;
1796
1916
  } else {
1797
- wasmPath = joinPath(circuitsPath, "build", "withdraw_regular_js", "withdraw_regular.wasm");
1798
- zkeyPath = joinPath(circuitsPath, "build", "withdraw_regular_final.zkey");
1917
+ wasmPath = joinPath(circuitsPath2, "build", "withdraw_regular_js", "withdraw_regular.wasm");
1918
+ zkeyPath = joinPath(circuitsPath2, "build", "withdraw_regular_final.zkey");
1799
1919
  const wasmExists = await fileExists(wasmPath);
1800
1920
  const zkeyExists = await fileExists(zkeyPath);
1801
1921
  if (!wasmExists) {
@@ -1837,16 +1957,15 @@ async function generateWithdrawRegularProof(inputs, circuitsPath) {
1837
1957
  // Not used, kept for compatibility
1838
1958
  };
1839
1959
  }
1840
- async function generateWithdrawSwapProof(inputs, circuitsPath) {
1841
- const isBrowser = typeof window !== "undefined" || typeof globalThis !== "undefined" && globalThis.window;
1960
+ async function generateWithdrawSwapProof(inputs, circuitsPath2) {
1842
1961
  let wasmPath;
1843
1962
  let zkeyPath;
1844
- if (isBrowser) {
1845
- wasmPath = `${circuitsPath}/withdraw_swap_js/withdraw_swap.wasm`;
1846
- zkeyPath = `${circuitsPath}/withdraw_swap_final.zkey`;
1963
+ if (IS_BROWSER) {
1964
+ wasmPath = `${circuitsPath2}/withdraw_swap_js/withdraw_swap.wasm`;
1965
+ zkeyPath = `${circuitsPath2}/withdraw_swap_final.zkey`;
1847
1966
  } else {
1848
- wasmPath = joinPath(circuitsPath, "build", "withdraw_swap_js", "withdraw_swap.wasm");
1849
- zkeyPath = joinPath(circuitsPath, "build", "withdraw_swap_final.zkey");
1967
+ wasmPath = joinPath(circuitsPath2, "build", "withdraw_swap_js", "withdraw_swap.wasm");
1968
+ zkeyPath = joinPath(circuitsPath2, "build", "withdraw_swap_final.zkey");
1850
1969
  const wasmExists = await fileExists(wasmPath);
1851
1970
  const zkeyExists = await fileExists(zkeyPath);
1852
1971
  if (!wasmExists) {
@@ -1895,32 +2014,35 @@ async function generateWithdrawSwapProof(inputs, circuitsPath) {
1895
2014
  // Not used, kept for compatibility
1896
2015
  };
1897
2016
  }
1898
- async function areCircuitsAvailable(circuitsPath) {
1899
- const isBrowser = typeof window !== "undefined" || typeof globalThis !== "undefined" && globalThis.window;
1900
- if (isBrowser) {
1901
- return Boolean(circuitsPath && circuitsPath !== "");
2017
+ async function areCircuitsAvailable(circuitsPath2) {
2018
+ if (IS_BROWSER) {
2019
+ return Boolean(circuitsPath2 && circuitsPath2 !== "");
1902
2020
  }
1903
- const wasmPath = joinPath(circuitsPath, "build", "withdraw_regular_js", "withdraw_regular.wasm");
1904
- const zkeyPath = joinPath(circuitsPath, "build", "withdraw_regular_final.zkey");
2021
+ const wasmPath = joinPath(circuitsPath2, "build", "withdraw_regular_js", "withdraw_regular.wasm");
2022
+ const zkeyPath = joinPath(circuitsPath2, "build", "withdraw_regular_final.zkey");
1905
2023
  const wasmExists = await fileExists(wasmPath);
1906
2024
  const zkeyExists = await fileExists(zkeyPath);
1907
2025
  return wasmExists && zkeyExists;
1908
2026
  }
1909
2027
  async function getDefaultCircuitsPath() {
1910
- const isBrowser = typeof window !== "undefined" || typeof globalThis !== "undefined" && globalThis.window;
1911
- if (isBrowser) {
2028
+ if (IS_BROWSER) {
1912
2029
  return "/circuits";
1913
2030
  }
1914
- const { path: path2 } = await loadNodeModules();
1915
- if (!path2 || typeof process === "undefined" || !process.cwd) {
2031
+ if (typeof process === "undefined" || !process.cwd) {
2032
+ return "/circuits";
2033
+ }
2034
+ let nodePath;
2035
+ try {
2036
+ nodePath = eval("require")("path");
2037
+ } catch {
1916
2038
  return "/circuits";
1917
2039
  }
1918
2040
  const possiblePaths = [
1919
- path2.resolve(process.cwd(), "../../packages-new/circuits"),
1920
- path2.resolve(process.cwd(), "../packages-new/circuits"),
1921
- path2.resolve(process.cwd(), "packages-new/circuits"),
1922
- path2.resolve(process.cwd(), "../../circuits"),
1923
- path2.resolve(process.cwd(), "../circuits")
2041
+ nodePath.resolve(process.cwd(), "../../packages-new/circuits"),
2042
+ nodePath.resolve(process.cwd(), "../packages-new/circuits"),
2043
+ nodePath.resolve(process.cwd(), "packages-new/circuits"),
2044
+ nodePath.resolve(process.cwd(), "../../circuits"),
2045
+ nodePath.resolve(process.cwd(), "../circuits")
1924
2046
  ];
1925
2047
  for (const p of possiblePaths) {
1926
2048
  if (await areCircuitsAvailable(p)) {
@@ -1929,6 +2051,90 @@ async function getDefaultCircuitsPath() {
1929
2051
  }
1930
2052
  return possiblePaths[0];
1931
2053
  }
2054
+ var EXPECTED_CIRCUIT_HASHES = {
2055
+ // SHA-256 of the verification key JSON from withdraw_regular circuit
2056
+ withdraw_regular_vkey: null,
2057
+ // Set to null to skip check during development
2058
+ // SHA-256 of the verification key JSON from withdraw_swap circuit
2059
+ withdraw_swap_vkey: null
2060
+ // Set to null to skip check during development
2061
+ };
2062
+ async function verifyCircuitIntegrity(circuitsPath, circuit) {
2063
+ const expectedHash = circuit === "withdraw_regular" ? EXPECTED_CIRCUIT_HASHES.withdraw_regular_vkey : EXPECTED_CIRCUIT_HASHES.withdraw_swap_vkey;
2064
+ if (!expectedHash) {
2065
+ return {
2066
+ valid: true,
2067
+ circuit,
2068
+ error: "Verification skipped (no expected hash configured)"
2069
+ };
2070
+ }
2071
+ try {
2072
+ const vkeyPath = IS_BROWSER ? `${circuitsPath}/${circuit}_verification_key.json` : joinPath(circuitsPath, "build", `${circuit}_verification_key.json`);
2073
+ let vkeyData;
2074
+ if (IS_BROWSER) {
2075
+ const response = await fetch(vkeyPath);
2076
+ if (!response.ok) {
2077
+ return {
2078
+ valid: false,
2079
+ circuit,
2080
+ error: `Failed to fetch verification key: ${response.status}`
2081
+ };
2082
+ }
2083
+ vkeyData = await response.text();
2084
+ } else {
2085
+ try {
2086
+ const nodeFs = eval("require")("fs");
2087
+ vkeyData = nodeFs.readFileSync(vkeyPath, "utf8");
2088
+ } catch (e) {
2089
+ return {
2090
+ valid: false,
2091
+ circuit,
2092
+ error: `Failed to read verification key: ${e}`
2093
+ };
2094
+ }
2095
+ }
2096
+ let computedHash;
2097
+ if (IS_BROWSER) {
2098
+ const encoder = new TextEncoder();
2099
+ const data = encoder.encode(vkeyData);
2100
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
2101
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
2102
+ computedHash = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
2103
+ } else {
2104
+ const nodeCrypto = eval("require")("crypto");
2105
+ computedHash = nodeCrypto.createHash("sha256").update(vkeyData).digest("hex");
2106
+ }
2107
+ if (computedHash === expectedHash) {
2108
+ return {
2109
+ valid: true,
2110
+ circuit,
2111
+ computedHash,
2112
+ expectedHash
2113
+ };
2114
+ } else {
2115
+ return {
2116
+ valid: false,
2117
+ circuit,
2118
+ error: `Verification key hash mismatch! This circuit may produce invalid proofs.`,
2119
+ computedHash,
2120
+ expectedHash
2121
+ };
2122
+ }
2123
+ } catch (error) {
2124
+ return {
2125
+ valid: false,
2126
+ circuit,
2127
+ error: `Circuit verification failed: ${error instanceof Error ? error.message : String(error)}`
2128
+ };
2129
+ }
2130
+ }
2131
+ async function verifyAllCircuits(circuitsPath2) {
2132
+ const results = await Promise.all([
2133
+ verifyCircuitIntegrity(circuitsPath2, "withdraw_regular"),
2134
+ verifyCircuitIntegrity(circuitsPath2, "withdraw_swap")
2135
+ ]);
2136
+ return results;
2137
+ }
1932
2138
 
1933
2139
  // src/core/CloakSDK.ts
1934
2140
  var CLOAK_PROGRAM_ID = new import_web35.PublicKey("c1oak6tetxYnNfvXKFkpn1d98FxtK7B68vBQLYQpWKp");
@@ -2043,14 +2249,35 @@ var CloakSDK = class {
2043
2249
  async deposit(connection, amountOrNote, options) {
2044
2250
  try {
2045
2251
  let note;
2252
+ let isNewNote = false;
2046
2253
  if (typeof amountOrNote === "number") {
2254
+ options?.onProgress?.("generating_note", { message: "Generating note with secrets..." });
2047
2255
  note = await generateNote(amountOrNote, this.config.network);
2256
+ isNewNote = true;
2048
2257
  } else {
2049
2258
  note = amountOrNote;
2050
2259
  if (note.depositSignature) {
2051
2260
  throw new Error("Note has already been deposited");
2052
2261
  }
2053
2262
  }
2263
+ if (isNewNote) {
2264
+ await this.storage.saveNote(note);
2265
+ if (options?.onNoteGenerated) {
2266
+ options?.onProgress?.("awaiting_note_acknowledgment", {
2267
+ message: "Waiting for note to be saved before proceeding with deposit..."
2268
+ });
2269
+ try {
2270
+ await options.onNoteGenerated(note);
2271
+ } catch (error) {
2272
+ throw new Error(
2273
+ `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.`
2274
+ );
2275
+ }
2276
+ }
2277
+ options?.onProgress?.("note_saved", {
2278
+ message: "Note saved. Proceeding with on-chain deposit..."
2279
+ });
2280
+ }
2054
2281
  const payerPubkey = this.getPublicKey();
2055
2282
  const balance = await connection.getBalance(payerPubkey);
2056
2283
  const requiredAmount = note.amount + 5e3;
@@ -2242,6 +2469,16 @@ var CloakSDK = class {
2242
2469
  pathIndices: merkleProof.pathIndices
2243
2470
  }
2244
2471
  });
2472
+ await this.storage.updateNote(note.commitment, {
2473
+ depositSignature: signature,
2474
+ depositSlot,
2475
+ leafIndex,
2476
+ root,
2477
+ merkleProof: {
2478
+ pathElements: merkleProof.pathElements,
2479
+ pathIndices: merkleProof.pathIndices
2480
+ }
2481
+ });
2245
2482
  return {
2246
2483
  note: updatedNote,
2247
2484
  signature,
@@ -2343,8 +2580,8 @@ var CloakSDK = class {
2343
2580
  }
2344
2581
  }
2345
2582
  const isBrowser = typeof window !== "undefined" || typeof globalThis !== "undefined" && globalThis.window;
2346
- const circuitsPath = isBrowser ? "/circuits" : typeof process !== "undefined" && process.env?.CIRCUITS_PATH || await getDefaultCircuitsPath();
2347
- const useDirectProof = await areCircuitsAvailable(circuitsPath);
2583
+ const circuitsPath2 = isBrowser ? "/circuits" : typeof process !== "undefined" && process.env?.CIRCUITS_PATH || await getDefaultCircuitsPath();
2584
+ const useDirectProof = await areCircuitsAvailable(circuitsPath2);
2348
2585
  let proofHex;
2349
2586
  let finalPublicInputs;
2350
2587
  if (useDirectProof) {
@@ -2406,7 +2643,7 @@ var CloakSDK = class {
2406
2643
  rem
2407
2644
  };
2408
2645
  options?.onProgress?.("proof_generating");
2409
- const proofResult = await generateWithdrawRegularProof(proofInputs, circuitsPath);
2646
+ const proofResult = await generateWithdrawRegularProof(proofInputs, circuitsPath2);
2410
2647
  options?.onProgress?.("proof_complete");
2411
2648
  proofHex = Buffer.from(proofResult.proofBytes).toString("hex");
2412
2649
  finalPublicInputs = {
@@ -2417,7 +2654,7 @@ var CloakSDK = class {
2417
2654
  };
2418
2655
  } else {
2419
2656
  throw new Error(
2420
- `Circuits not available at ${circuitsPath}. Make sure circuits are built and accessible. In browser, circuits should be served from /circuits. In Node.js, set CIRCUITS_PATH environment variable or ensure circuits are in the expected location.`
2657
+ `Circuits not available at ${circuitsPath2}. Make sure circuits are built and accessible. In browser, circuits should be served from /circuits. In Node.js, set CIRCUITS_PATH environment variable or ensure circuits are in the expected location.`
2421
2658
  );
2422
2659
  }
2423
2660
  const signature = await this.relay.submitWithdraw(
@@ -2614,8 +2851,8 @@ var CloakSDK = class {
2614
2851
  throw new Error("Merkle proof is invalid: missing path elements");
2615
2852
  }
2616
2853
  const envCircuitsPath = typeof window !== "undefined" ? typeof process !== "undefined" && process.env?.NEXT_PUBLIC_CIRCUITS_PATH || void 0 : typeof process !== "undefined" && process.env?.CIRCUITS_PATH || void 0;
2617
- const circuitsPath = envCircuitsPath || await getDefaultCircuitsPath();
2618
- const useDirectProof = await areCircuitsAvailable(circuitsPath);
2854
+ const circuitsPath2 = envCircuitsPath || await getDefaultCircuitsPath();
2855
+ const useDirectProof = await areCircuitsAvailable(circuitsPath2);
2619
2856
  let proofHex;
2620
2857
  let finalPublicInputs;
2621
2858
  if (useDirectProof) {
@@ -2658,7 +2895,7 @@ var CloakSDK = class {
2658
2895
  rem
2659
2896
  };
2660
2897
  options?.onProgress?.("proof_generating");
2661
- const proofResult = await generateWithdrawSwapProof(proofInputs, circuitsPath);
2898
+ const proofResult = await generateWithdrawSwapProof(proofInputs, circuitsPath2);
2662
2899
  options?.onProgress?.("proof_complete");
2663
2900
  proofHex = Buffer.from(proofResult.proofBytes).toString("hex");
2664
2901
  finalPublicInputs = {
@@ -2669,7 +2906,7 @@ var CloakSDK = class {
2669
2906
  };
2670
2907
  } else {
2671
2908
  throw new Error(
2672
- `Circuits not available at ${circuitsPath}. Make sure circuits are built and accessible. In browser, circuits should be served from /circuits. In Node.js, set CIRCUITS_PATH environment variable or ensure circuits are in the expected location.`
2909
+ `Circuits not available at ${circuitsPath2}. Make sure circuits are built and accessible. In browser, circuits should be served from /circuits. In Node.js, set CIRCUITS_PATH environment variable or ensure circuits are in the expected location.`
2673
2910
  );
2674
2911
  }
2675
2912
  const signature = await this.relay.submitSwap(
@@ -3044,6 +3281,63 @@ async function copyNoteToClipboard(note) {
3044
3281
  }
3045
3282
 
3046
3283
  // src/utils/errors.ts
3284
+ var ShieldPoolErrors = {
3285
+ // Root management errors
3286
+ 4096: "Invalid Merkle root",
3287
+ 4097: "Root not found in the roots ring",
3288
+ 4098: "Roots ring is full",
3289
+ // Proof verification errors
3290
+ 4112: "Zero-knowledge proof is invalid",
3291
+ 4113: "Invalid proof size (expected 260 bytes)",
3292
+ 4114: "Invalid public inputs",
3293
+ 4115: "Verification key mismatch",
3294
+ // Nullifier errors
3295
+ 4128: "Double spend detected - this note has already been spent",
3296
+ 4129: "Nullifier shard is full",
3297
+ 4130: "Invalid nullifier",
3298
+ // Transaction validation errors
3299
+ 4144: "Output addresses or amounts don't match the proof",
3300
+ 4145: "Amount conservation failed - outputs + fee must equal input amount",
3301
+ 4146: "Invalid outputs hash",
3302
+ 4147: "Invalid amount (must be greater than zero)",
3303
+ 4148: "Invalid recipient address",
3304
+ 4149: "Commitment already exists in the tree",
3305
+ 4150: "Commitment log is full",
3306
+ // Math errors
3307
+ 4160: "Math overflow occurred",
3308
+ 4161: "Division by zero",
3309
+ // Account errors
3310
+ 4176: "Account validation failed - please check your wallet balance and try again",
3311
+ 4177: "Pool account owner mismatch",
3312
+ 4178: "Treasury account owner mismatch",
3313
+ 4179: "Roots ring account owner mismatch",
3314
+ 4180: "Nullifier shard account owner mismatch",
3315
+ 4181: "Pool account is not writable",
3316
+ 4182: "Treasury account is not writable",
3317
+ 4183: "Recipient account is not writable",
3318
+ 4184: "Insufficient lamports in pool or account",
3319
+ 4185: "Invalid account owner",
3320
+ 4186: "Invalid account size",
3321
+ 4187: "Commitments account is not writable",
3322
+ 4188: "Invalid admin authority",
3323
+ // Instruction errors
3324
+ 4192: "Invalid instruction data length",
3325
+ 4193: "Invalid instruction data format",
3326
+ 4194: "Missing required accounts",
3327
+ 4195: "Invalid instruction tag",
3328
+ // PoW/Scrambler errors
3329
+ 4196: "Invalid miner account",
3330
+ 4197: "Invalid claim account",
3331
+ 4198: "Failed to consume claim",
3332
+ // Groth16 verifier errors
3333
+ 4208: "Invalid G1 point length",
3334
+ 4209: "Invalid G2 point length",
3335
+ 4210: "Invalid public inputs length",
3336
+ 4211: "Public input exceeds field size",
3337
+ 4212: "G1 multiplication failed during proof preparation",
3338
+ 4213: "G1 addition failed during proof preparation",
3339
+ 4214: "Proof verification failed"
3340
+ };
3047
3341
  var PROGRAM_ERRORS = {
3048
3342
  // Nullifier errors
3049
3343
  "NullifierAlreadyUsed": "This note has already been withdrawn. Each note can only be spent once.",
@@ -3072,6 +3366,438 @@ var PROGRAM_ERRORS = {
3072
3366
  "AccountNotFound": "Required account not found.",
3073
3367
  "InvalidInstruction": "Invalid instruction data."
3074
3368
  };
3369
+ var ErrorPatterns = [
3370
+ // Wallet/User action errors
3371
+ {
3372
+ patterns: [
3373
+ "User rejected",
3374
+ "user rejected",
3375
+ "User denied",
3376
+ "user denied",
3377
+ "Transaction cancelled",
3378
+ "Transaction rejected",
3379
+ /code.*4001/i
3380
+ ],
3381
+ result: {
3382
+ title: "Transaction Cancelled",
3383
+ message: "You cancelled the transaction.",
3384
+ category: "wallet",
3385
+ recoverable: true
3386
+ }
3387
+ },
3388
+ {
3389
+ patterns: [
3390
+ "Wallet not connected",
3391
+ "wallet not connected",
3392
+ "Please connect wallet",
3393
+ "No wallet connected"
3394
+ ],
3395
+ result: {
3396
+ title: "Wallet Not Connected",
3397
+ message: "Please connect your wallet to continue.",
3398
+ category: "wallet",
3399
+ suggestion: "Click the wallet button to connect.",
3400
+ recoverable: true
3401
+ }
3402
+ },
3403
+ {
3404
+ patterns: [
3405
+ "SDK not initialized",
3406
+ "sdk not initialized"
3407
+ ],
3408
+ result: {
3409
+ title: "Wallet Connection Required",
3410
+ message: "Your wallet connection was interrupted.",
3411
+ category: "wallet",
3412
+ suggestion: "Please reconnect your wallet and try again.",
3413
+ recoverable: true
3414
+ }
3415
+ },
3416
+ {
3417
+ patterns: [
3418
+ "Wallet does not support signing",
3419
+ "signTransaction"
3420
+ ],
3421
+ result: {
3422
+ title: "Wallet Feature Not Supported",
3423
+ message: "Your wallet doesn't support the required signing method.",
3424
+ category: "wallet",
3425
+ suggestion: "Try using a different wallet like Phantom or Solflare.",
3426
+ recoverable: true
3427
+ }
3428
+ },
3429
+ // Balance errors
3430
+ {
3431
+ patterns: [
3432
+ "insufficient lamports",
3433
+ "Insufficient lamports",
3434
+ "insufficient balance",
3435
+ "Insufficient balance",
3436
+ "Insufficient SOL",
3437
+ "not enough SOL",
3438
+ "Attempt to debit an account but found no record",
3439
+ /0x1$/
3440
+ ],
3441
+ result: {
3442
+ title: "Insufficient Balance",
3443
+ message: "You don't have enough SOL for this transaction.",
3444
+ category: "validation",
3445
+ suggestion: "Add more SOL to your wallet or reduce the amount.",
3446
+ recoverable: true
3447
+ }
3448
+ },
3449
+ {
3450
+ patterns: [
3451
+ "Insufficient funds",
3452
+ "insufficient funds"
3453
+ ],
3454
+ result: {
3455
+ title: "Insufficient Funds",
3456
+ message: "Your wallet doesn't have enough funds for this transaction.",
3457
+ category: "validation",
3458
+ suggestion: "Add more funds to your wallet and try again.",
3459
+ recoverable: true
3460
+ }
3461
+ },
3462
+ // Network errors
3463
+ {
3464
+ patterns: [
3465
+ "fetch failed",
3466
+ "Failed to fetch",
3467
+ "Network error",
3468
+ "network error",
3469
+ "ECONNREFUSED",
3470
+ "ETIMEDOUT",
3471
+ "ENOTFOUND",
3472
+ "NetworkError",
3473
+ "net::ERR",
3474
+ "Failed to load"
3475
+ ],
3476
+ result: {
3477
+ title: "Connection Error",
3478
+ message: "Unable to connect to the network.",
3479
+ category: "network",
3480
+ suggestion: "Check your internet connection and try again.",
3481
+ recoverable: true
3482
+ }
3483
+ },
3484
+ {
3485
+ patterns: [
3486
+ "timeout",
3487
+ "Timeout",
3488
+ "TIMEOUT",
3489
+ "timed out",
3490
+ "Timed out"
3491
+ ],
3492
+ result: {
3493
+ title: "Request Timed Out",
3494
+ message: "The request took too long to complete.",
3495
+ category: "network",
3496
+ suggestion: "The network may be congested. Please try again in a moment.",
3497
+ recoverable: true
3498
+ }
3499
+ },
3500
+ {
3501
+ patterns: [
3502
+ "blockhash not found",
3503
+ "Blockhash not found",
3504
+ "block height exceeded"
3505
+ ],
3506
+ result: {
3507
+ title: "Transaction Expired",
3508
+ message: "The transaction took too long and expired.",
3509
+ category: "network",
3510
+ suggestion: "Please try again. If this persists, the network may be congested.",
3511
+ recoverable: true
3512
+ }
3513
+ },
3514
+ // Service errors (Indexer/Relay)
3515
+ {
3516
+ patterns: [
3517
+ "Indexer",
3518
+ "indexer",
3519
+ /indexer.*unavailable/i,
3520
+ /failed.*indexer/i
3521
+ ],
3522
+ result: {
3523
+ title: "Service Temporarily Unavailable",
3524
+ message: "The privacy service is temporarily unavailable.",
3525
+ category: "service",
3526
+ suggestion: "Please wait a moment and try again.",
3527
+ recoverable: true
3528
+ }
3529
+ },
3530
+ {
3531
+ patterns: [
3532
+ "Relay",
3533
+ "relay",
3534
+ /relay.*unavailable/i,
3535
+ /failed.*relay/i,
3536
+ "failed to submit",
3537
+ "Failed to submit"
3538
+ ],
3539
+ result: {
3540
+ title: "Processing Service Busy",
3541
+ message: "The transaction processing service is busy.",
3542
+ category: "service",
3543
+ suggestion: "Please wait a moment and try again.",
3544
+ recoverable: true
3545
+ }
3546
+ },
3547
+ {
3548
+ patterns: [
3549
+ "429",
3550
+ "Too Many Requests",
3551
+ "rate limit",
3552
+ "Rate limit"
3553
+ ],
3554
+ result: {
3555
+ title: "Too Many Requests",
3556
+ message: "You're making requests too quickly.",
3557
+ category: "service",
3558
+ suggestion: "Please wait a moment before trying again.",
3559
+ recoverable: true
3560
+ }
3561
+ },
3562
+ {
3563
+ patterns: [
3564
+ "503",
3565
+ "Service Unavailable",
3566
+ "502",
3567
+ "Bad Gateway"
3568
+ ],
3569
+ result: {
3570
+ title: "Service Temporarily Unavailable",
3571
+ message: "Our servers are temporarily unavailable.",
3572
+ category: "service",
3573
+ suggestion: "Please try again in a few minutes.",
3574
+ recoverable: true
3575
+ }
3576
+ },
3577
+ // Proof/ZK errors
3578
+ {
3579
+ patterns: [
3580
+ "Circuits not available",
3581
+ "circuits not available",
3582
+ "Failed to load circuit",
3583
+ "WASM",
3584
+ "wasm",
3585
+ /circuit.*not.*found/i
3586
+ ],
3587
+ result: {
3588
+ title: "Loading Error",
3589
+ message: "Failed to load required cryptographic components.",
3590
+ category: "service",
3591
+ suggestion: "Try refreshing the page. If the problem persists, clear your browser cache.",
3592
+ recoverable: true
3593
+ }
3594
+ },
3595
+ {
3596
+ patterns: [
3597
+ "proof generation",
3598
+ "Proof generation",
3599
+ "Failed to generate proof",
3600
+ "proving error"
3601
+ ],
3602
+ result: {
3603
+ title: "Proof Generation Failed",
3604
+ message: "Failed to generate the privacy proof.",
3605
+ category: "service",
3606
+ suggestion: "Try again with a smaller amount or refresh the page.",
3607
+ recoverable: true
3608
+ }
3609
+ },
3610
+ // Validation errors
3611
+ {
3612
+ patterns: [
3613
+ "Invalid amount",
3614
+ "invalid amount",
3615
+ "Amount must be",
3616
+ "amount must be",
3617
+ "Amount too small"
3618
+ ],
3619
+ result: {
3620
+ title: "Invalid Amount",
3621
+ message: "The amount you entered is not valid.",
3622
+ category: "validation",
3623
+ suggestion: "Enter an amount greater than the minimum required.",
3624
+ recoverable: true
3625
+ }
3626
+ },
3627
+ {
3628
+ patterns: [
3629
+ "Invalid recipient",
3630
+ "invalid recipient",
3631
+ "Invalid address",
3632
+ "invalid address",
3633
+ "Invalid Solana address"
3634
+ ],
3635
+ result: {
3636
+ title: "Invalid Address",
3637
+ message: "The recipient address is not valid.",
3638
+ category: "validation",
3639
+ suggestion: "Check the address and make sure it's a valid Solana address.",
3640
+ recoverable: true
3641
+ }
3642
+ },
3643
+ {
3644
+ patterns: [
3645
+ "Invalid token",
3646
+ "invalid token",
3647
+ "Unsupported token"
3648
+ ],
3649
+ result: {
3650
+ title: "Unsupported Token",
3651
+ message: "This token is not supported.",
3652
+ category: "validation",
3653
+ suggestion: "Select a supported token and try again.",
3654
+ recoverable: true
3655
+ }
3656
+ },
3657
+ // Swap errors
3658
+ {
3659
+ patterns: [
3660
+ "Failed to get quote",
3661
+ "failed to get quote",
3662
+ "No route found",
3663
+ "no route found",
3664
+ "Insufficient liquidity",
3665
+ "insufficient liquidity"
3666
+ ],
3667
+ result: {
3668
+ title: "Swap Quote Unavailable",
3669
+ message: "Unable to get a price quote for this swap.",
3670
+ category: "service",
3671
+ suggestion: "Try a different amount or wait for better liquidity.",
3672
+ recoverable: true
3673
+ }
3674
+ },
3675
+ {
3676
+ patterns: [
3677
+ "Slippage",
3678
+ "slippage",
3679
+ "Price impact",
3680
+ "price impact"
3681
+ ],
3682
+ result: {
3683
+ title: "Price Changed",
3684
+ message: "The price changed too much during the transaction.",
3685
+ category: "transaction",
3686
+ suggestion: "Try again or increase your slippage tolerance.",
3687
+ recoverable: true
3688
+ }
3689
+ },
3690
+ // Transaction errors
3691
+ {
3692
+ patterns: [
3693
+ "Double spend",
3694
+ "double spend",
3695
+ "already been spent",
3696
+ "already spent"
3697
+ ],
3698
+ result: {
3699
+ title: "Already Spent",
3700
+ message: "This note has already been used.",
3701
+ category: "transaction",
3702
+ suggestion: "This funds have already been withdrawn.",
3703
+ recoverable: false
3704
+ }
3705
+ },
3706
+ {
3707
+ patterns: [
3708
+ "simulation failed",
3709
+ "Simulation failed",
3710
+ "Transaction simulation"
3711
+ ],
3712
+ result: {
3713
+ title: "Transaction Failed",
3714
+ message: "The transaction could not be completed.",
3715
+ category: "transaction",
3716
+ suggestion: "Please try again. If the problem persists, check your balance.",
3717
+ recoverable: true
3718
+ }
3719
+ },
3720
+ {
3721
+ patterns: [
3722
+ "confirmation timeout",
3723
+ "Confirmation timeout",
3724
+ "not confirmed"
3725
+ ],
3726
+ result: {
3727
+ title: "Confirmation Pending",
3728
+ message: "Transaction confirmation is taking longer than expected.",
3729
+ category: "network",
3730
+ suggestion: "Your transaction may still complete. Check your wallet or try again.",
3731
+ recoverable: true
3732
+ }
3733
+ }
3734
+ ];
3735
+ function parseError(error) {
3736
+ let errorMessage = "";
3737
+ let originalError = "";
3738
+ if (error instanceof Error) {
3739
+ errorMessage = error.message;
3740
+ originalError = error.stack || error.message;
3741
+ } else if (typeof error === "string") {
3742
+ errorMessage = error;
3743
+ originalError = error;
3744
+ } else if (error && typeof error === "object") {
3745
+ const err = error;
3746
+ errorMessage = String(err.message || err.error || err.msg || JSON.stringify(error));
3747
+ originalError = JSON.stringify(error);
3748
+ } else {
3749
+ errorMessage = String(error);
3750
+ originalError = String(error);
3751
+ }
3752
+ for (const { patterns, result } of ErrorPatterns) {
3753
+ for (const pattern of patterns) {
3754
+ if (typeof pattern === "string") {
3755
+ if (errorMessage.includes(pattern)) {
3756
+ return { ...result, originalError };
3757
+ }
3758
+ } else if (pattern instanceof RegExp) {
3759
+ if (pattern.test(errorMessage)) {
3760
+ return { ...result, originalError };
3761
+ }
3762
+ }
3763
+ }
3764
+ }
3765
+ const programError = tryParseProgramError(errorMessage);
3766
+ if (programError) {
3767
+ return { ...programError, originalError };
3768
+ }
3769
+ return {
3770
+ title: "Something Went Wrong",
3771
+ message: "An unexpected error occurred.",
3772
+ category: "unknown",
3773
+ suggestion: "Please try again. If the problem persists, refresh the page.",
3774
+ recoverable: true,
3775
+ originalError
3776
+ };
3777
+ }
3778
+ function tryParseProgramError(message) {
3779
+ const match = message.match(/\{"InstructionError":\[(\d+),\{"Custom":(\d+)\}\]\}/);
3780
+ if (match) {
3781
+ const errorCode = parseInt(match[2]);
3782
+ const friendlyMessage = ShieldPoolErrors[errorCode];
3783
+ if (friendlyMessage) {
3784
+ return {
3785
+ title: "Transaction Failed",
3786
+ message: friendlyMessage,
3787
+ category: "transaction",
3788
+ recoverable: !friendlyMessage.toLowerCase().includes("double spend")
3789
+ };
3790
+ }
3791
+ return {
3792
+ title: "Transaction Failed",
3793
+ message: `Transaction failed with error code ${errorCode}.`,
3794
+ category: "transaction",
3795
+ suggestion: "Please try again or contact support if this persists.",
3796
+ recoverable: true
3797
+ };
3798
+ }
3799
+ return null;
3800
+ }
3075
3801
  function parseTransactionError(error) {
3076
3802
  if (!error) return "An unknown error occurred";
3077
3803
  const errorStr = typeof error === "string" ? error : error.message || error.toString();
@@ -3251,6 +3977,173 @@ function keypairToAdapter(keypair) {
3251
3977
  };
3252
3978
  }
3253
3979
 
3980
+ // src/utils/pending-operations.ts
3981
+ var PENDING_DEPOSITS_KEY = "cloak_pending_deposits";
3982
+ var PENDING_WITHDRAWALS_KEY = "cloak_pending_withdrawals";
3983
+ function getStorage() {
3984
+ if (typeof globalThis !== "undefined" && globalThis.localStorage) {
3985
+ return globalThis.localStorage;
3986
+ }
3987
+ return null;
3988
+ }
3989
+ function savePendingDeposit(deposit) {
3990
+ const storage = getStorage();
3991
+ if (!storage) {
3992
+ console.warn("localStorage not available - pending deposit not persisted");
3993
+ return;
3994
+ }
3995
+ const deposits = loadPendingDeposits();
3996
+ const index = deposits.findIndex((d) => d.note.commitment === deposit.note.commitment);
3997
+ if (index >= 0) {
3998
+ deposits[index] = deposit;
3999
+ } else {
4000
+ deposits.push(deposit);
4001
+ }
4002
+ storage.setItem(PENDING_DEPOSITS_KEY, JSON.stringify(deposits));
4003
+ }
4004
+ function loadPendingDeposits() {
4005
+ const storage = getStorage();
4006
+ if (!storage) return [];
4007
+ const stored = storage.getItem(PENDING_DEPOSITS_KEY);
4008
+ if (!stored) return [];
4009
+ try {
4010
+ return JSON.parse(stored);
4011
+ } catch {
4012
+ return [];
4013
+ }
4014
+ }
4015
+ function updatePendingDeposit(commitment, updates) {
4016
+ const storage = getStorage();
4017
+ if (!storage) return;
4018
+ const deposits = loadPendingDeposits();
4019
+ const index = deposits.findIndex((d) => d.note.commitment === commitment);
4020
+ if (index >= 0) {
4021
+ deposits[index] = { ...deposits[index], ...updates };
4022
+ storage.setItem(PENDING_DEPOSITS_KEY, JSON.stringify(deposits));
4023
+ }
4024
+ }
4025
+ function removePendingDeposit(commitment) {
4026
+ const storage = getStorage();
4027
+ if (!storage) return;
4028
+ const deposits = loadPendingDeposits();
4029
+ const filtered = deposits.filter((d) => d.note.commitment !== commitment);
4030
+ storage.setItem(PENDING_DEPOSITS_KEY, JSON.stringify(filtered));
4031
+ }
4032
+ function clearPendingDeposits() {
4033
+ const storage = getStorage();
4034
+ if (storage) {
4035
+ storage.removeItem(PENDING_DEPOSITS_KEY);
4036
+ }
4037
+ }
4038
+ function savePendingWithdrawal(withdrawal) {
4039
+ const storage = getStorage();
4040
+ if (!storage) {
4041
+ console.warn("localStorage not available - pending withdrawal not persisted");
4042
+ return;
4043
+ }
4044
+ const withdrawals = loadPendingWithdrawals();
4045
+ const index = withdrawals.findIndex((w) => w.requestId === withdrawal.requestId);
4046
+ if (index >= 0) {
4047
+ withdrawals[index] = withdrawal;
4048
+ } else {
4049
+ withdrawals.push(withdrawal);
4050
+ }
4051
+ storage.setItem(PENDING_WITHDRAWALS_KEY, JSON.stringify(withdrawals));
4052
+ }
4053
+ function loadPendingWithdrawals() {
4054
+ const storage = getStorage();
4055
+ if (!storage) return [];
4056
+ const stored = storage.getItem(PENDING_WITHDRAWALS_KEY);
4057
+ if (!stored) return [];
4058
+ try {
4059
+ return JSON.parse(stored);
4060
+ } catch {
4061
+ return [];
4062
+ }
4063
+ }
4064
+ function updatePendingWithdrawal(requestId, updates) {
4065
+ const storage = getStorage();
4066
+ if (!storage) return;
4067
+ const withdrawals = loadPendingWithdrawals();
4068
+ const index = withdrawals.findIndex((w) => w.requestId === requestId);
4069
+ if (index >= 0) {
4070
+ withdrawals[index] = { ...withdrawals[index], ...updates };
4071
+ storage.setItem(PENDING_WITHDRAWALS_KEY, JSON.stringify(withdrawals));
4072
+ }
4073
+ }
4074
+ function removePendingWithdrawal(requestId) {
4075
+ const storage = getStorage();
4076
+ if (!storage) return;
4077
+ const withdrawals = loadPendingWithdrawals();
4078
+ const filtered = withdrawals.filter((w) => w.requestId !== requestId);
4079
+ storage.setItem(PENDING_WITHDRAWALS_KEY, JSON.stringify(filtered));
4080
+ }
4081
+ function clearPendingWithdrawals() {
4082
+ const storage = getStorage();
4083
+ if (storage) {
4084
+ storage.removeItem(PENDING_WITHDRAWALS_KEY);
4085
+ }
4086
+ }
4087
+ function hasPendingOperations() {
4088
+ const deposits = loadPendingDeposits();
4089
+ const withdrawals = loadPendingWithdrawals();
4090
+ const pendingDeposits = deposits.filter(
4091
+ (d) => d.status === "pending" || d.status === "tx_sent"
4092
+ );
4093
+ const pendingWithdrawals = withdrawals.filter(
4094
+ (w) => w.status === "pending" || w.status === "processing"
4095
+ );
4096
+ return pendingDeposits.length > 0 || pendingWithdrawals.length > 0;
4097
+ }
4098
+ function getPendingOperationsSummary() {
4099
+ const deposits = loadPendingDeposits().filter(
4100
+ (d) => d.status === "pending" || d.status === "tx_sent"
4101
+ );
4102
+ const withdrawals = loadPendingWithdrawals().filter(
4103
+ (w) => w.status === "pending" || w.status === "processing"
4104
+ );
4105
+ return {
4106
+ deposits,
4107
+ withdrawals,
4108
+ totalPending: deposits.length + withdrawals.length
4109
+ };
4110
+ }
4111
+ function cleanupStalePendingOperations(maxAgeMs = 24 * 60 * 60 * 1e3) {
4112
+ const now = Date.now();
4113
+ let removedDeposits = 0;
4114
+ let removedWithdrawals = 0;
4115
+ const deposits = loadPendingDeposits();
4116
+ const activeDeposits = deposits.filter((d) => {
4117
+ const age = now - d.startedAt;
4118
+ const isStale = age > maxAgeMs;
4119
+ const isTerminal = d.status === "confirmed" || d.status === "failed";
4120
+ if (isStale || isTerminal) {
4121
+ removedDeposits++;
4122
+ return false;
4123
+ }
4124
+ return true;
4125
+ });
4126
+ const storage = getStorage();
4127
+ if (storage) {
4128
+ storage.setItem(PENDING_DEPOSITS_KEY, JSON.stringify(activeDeposits));
4129
+ }
4130
+ const withdrawals = loadPendingWithdrawals();
4131
+ const activeWithdrawals = withdrawals.filter((w) => {
4132
+ const age = now - w.startedAt;
4133
+ const isStale = age > maxAgeMs;
4134
+ const isTerminal = w.status === "completed" || w.status === "failed";
4135
+ if (isStale || isTerminal) {
4136
+ removedWithdrawals++;
4137
+ return false;
4138
+ }
4139
+ return true;
4140
+ });
4141
+ if (storage) {
4142
+ storage.setItem(PENDING_WITHDRAWALS_KEY, JSON.stringify(activeWithdrawals));
4143
+ }
4144
+ return { removedDeposits, removedWithdrawals };
4145
+ }
4146
+
3254
4147
  // src/index.ts
3255
4148
  var VERSION = "1.0.0";
3256
4149
  // Annotate the CommonJS export names for ESM import in node:
@@ -3259,12 +4152,14 @@ var VERSION = "1.0.0";
3259
4152
  CloakError,
3260
4153
  CloakSDK,
3261
4154
  DepositRecoveryService,
4155
+ EXPECTED_CIRCUIT_HASHES,
3262
4156
  FIXED_FEE_LAMPORTS,
3263
4157
  IndexerService,
3264
4158
  LAMPORTS_PER_SOL,
3265
4159
  LocalStorageAdapter,
3266
4160
  MemoryStorageAdapter,
3267
4161
  RelayService,
4162
+ ShieldPoolErrors,
3268
4163
  VARIABLE_FEE_RATE,
3269
4164
  VERSION,
3270
4165
  bigintToBytes32,
@@ -3272,6 +4167,9 @@ var VERSION = "1.0.0";
3272
4167
  bytesToHex,
3273
4168
  calculateFee,
3274
4169
  calculateRelayFee,
4170
+ cleanupStalePendingOperations,
4171
+ clearPendingDeposits,
4172
+ clearPendingWithdrawals,
3275
4173
  computeCommitment,
3276
4174
  computeMerkleRoot,
3277
4175
  computeNullifier,
@@ -3311,12 +4209,14 @@ var VERSION = "1.0.0";
3311
4209
  getAddressExplorerUrl,
3312
4210
  getDistributableAmount,
3313
4211
  getExplorerUrl,
4212
+ getPendingOperationsSummary,
3314
4213
  getPublicKey,
3315
4214
  getPublicViewKey,
3316
4215
  getRecipientAmount,
3317
4216
  getRpcUrlForNetwork,
3318
4217
  getShieldPoolPDAs,
3319
4218
  getViewKey,
4219
+ hasPendingOperations,
3320
4220
  hexToBigint,
3321
4221
  hexToBytes,
3322
4222
  importKeys,
@@ -3326,7 +4226,10 @@ var VERSION = "1.0.0";
3326
4226
  isValidSolanaAddress,
3327
4227
  isWithdrawable,
3328
4228
  keypairToAdapter,
4229
+ loadPendingDeposits,
4230
+ loadPendingWithdrawals,
3329
4231
  parseAmount,
4232
+ parseError,
3330
4233
  parseNote,
3331
4234
  parseTransactionError,
3332
4235
  poseidonHash,
@@ -3335,6 +4238,10 @@ var VERSION = "1.0.0";
3335
4238
  proofToBytes,
3336
4239
  pubkeyToLimbs,
3337
4240
  randomBytes,
4241
+ removePendingDeposit,
4242
+ removePendingWithdrawal,
4243
+ savePendingDeposit,
4244
+ savePendingWithdrawal,
3338
4245
  scanNotesForWallet,
3339
4246
  sendTransaction,
3340
4247
  serializeNote,
@@ -3342,10 +4249,14 @@ var VERSION = "1.0.0";
3342
4249
  splitTo2Limbs,
3343
4250
  tryDecryptNote,
3344
4251
  updateNoteWithDeposit,
4252
+ updatePendingDeposit,
4253
+ updatePendingWithdrawal,
3345
4254
  validateDepositParams,
3346
4255
  validateNote,
3347
4256
  validateOutputsSum,
3348
4257
  validateTransfers,
3349
4258
  validateWalletConnected,
3350
- validateWithdrawableNote
4259
+ validateWithdrawableNote,
4260
+ verifyAllCircuits,
4261
+ verifyCircuitIntegrity
3351
4262
  });