@cloak.ag/sdk 1.0.4 → 1.0.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
@@ -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
 
@@ -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,30 @@ 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 };
1883
+ var IS_BROWSER = typeof window !== "undefined" || typeof globalThis !== "undefined" && typeof globalThis.document !== "undefined";
1884
+ var _nodePath = null;
1885
+ var _nodeFs = null;
1886
+ var _nodeCrypto = null;
1887
+ async function getNodePath() {
1888
+ if (_nodePath) return _nodePath;
1889
+ _nodePath = await import("path");
1890
+ return _nodePath;
1891
+ }
1892
+ async function getNodeFs() {
1893
+ if (_nodeFs) return _nodeFs;
1894
+ _nodeFs = await import("fs");
1895
+ return _nodeFs;
1896
+ }
1897
+ async function getNodeCrypto() {
1898
+ if (_nodeCrypto) return _nodeCrypto;
1899
+ _nodeCrypto = await import("crypto");
1900
+ return _nodeCrypto;
1761
1901
  }
1762
1902
  function joinPath(...parts) {
1763
- const isBrowser = typeof window !== "undefined" || typeof globalThis !== "undefined" && globalThis.window;
1764
- if (!isBrowser && path) {
1765
- return path.join(...parts);
1766
- }
1767
1903
  return parts.join("/").replace(/\/+/g, "/");
1768
1904
  }
1769
1905
  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) {
1906
+ if (IS_BROWSER) {
1780
1907
  try {
1781
1908
  const response = await fetch(filePath, { method: "HEAD" });
1782
1909
  return response.ok;
@@ -1784,13 +1911,21 @@ async function fileExists(filePath) {
1784
1911
  return false;
1785
1912
  }
1786
1913
  }
1787
- return false;
1914
+ try {
1915
+ const nodeFs = globalThis.require?.("fs") || (typeof require !== "undefined" ? require("fs") : null);
1916
+ if (nodeFs) {
1917
+ return nodeFs.existsSync(filePath);
1918
+ }
1919
+ const fsModule = await import("fs");
1920
+ return fsModule.existsSync(filePath);
1921
+ } catch {
1922
+ return false;
1923
+ }
1788
1924
  }
1789
1925
  async function generateWithdrawRegularProof(inputs, circuitsPath) {
1790
- const isBrowser = typeof window !== "undefined" || typeof globalThis !== "undefined" && globalThis.window;
1791
1926
  let wasmPath;
1792
1927
  let zkeyPath;
1793
- if (isBrowser) {
1928
+ if (IS_BROWSER) {
1794
1929
  wasmPath = `${circuitsPath}/withdraw_regular_js/withdraw_regular.wasm`;
1795
1930
  zkeyPath = `${circuitsPath}/withdraw_regular_final.zkey`;
1796
1931
  } else {
@@ -1838,10 +1973,9 @@ async function generateWithdrawRegularProof(inputs, circuitsPath) {
1838
1973
  };
1839
1974
  }
1840
1975
  async function generateWithdrawSwapProof(inputs, circuitsPath) {
1841
- const isBrowser = typeof window !== "undefined" || typeof globalThis !== "undefined" && globalThis.window;
1842
1976
  let wasmPath;
1843
1977
  let zkeyPath;
1844
- if (isBrowser) {
1978
+ if (IS_BROWSER) {
1845
1979
  wasmPath = `${circuitsPath}/withdraw_swap_js/withdraw_swap.wasm`;
1846
1980
  zkeyPath = `${circuitsPath}/withdraw_swap_final.zkey`;
1847
1981
  } else {
@@ -1896,8 +2030,7 @@ async function generateWithdrawSwapProof(inputs, circuitsPath) {
1896
2030
  };
1897
2031
  }
1898
2032
  async function areCircuitsAvailable(circuitsPath) {
1899
- const isBrowser = typeof window !== "undefined" || typeof globalThis !== "undefined" && globalThis.window;
1900
- if (isBrowser) {
2033
+ if (IS_BROWSER) {
1901
2034
  return Boolean(circuitsPath && circuitsPath !== "");
1902
2035
  }
1903
2036
  const wasmPath = joinPath(circuitsPath, "build", "withdraw_regular_js", "withdraw_regular.wasm");
@@ -1907,20 +2040,24 @@ async function areCircuitsAvailable(circuitsPath) {
1907
2040
  return wasmExists && zkeyExists;
1908
2041
  }
1909
2042
  async function getDefaultCircuitsPath() {
1910
- const isBrowser = typeof window !== "undefined" || typeof globalThis !== "undefined" && globalThis.window;
1911
- if (isBrowser) {
2043
+ if (IS_BROWSER) {
2044
+ return "/circuits";
2045
+ }
2046
+ if (typeof process === "undefined" || !process.cwd) {
1912
2047
  return "/circuits";
1913
2048
  }
1914
- const { path: path2 } = await loadNodeModules();
1915
- if (!path2 || typeof process === "undefined" || !process.cwd) {
2049
+ let nodePath;
2050
+ try {
2051
+ nodePath = await getNodePath();
2052
+ } catch {
1916
2053
  return "/circuits";
1917
2054
  }
1918
2055
  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")
2056
+ nodePath.resolve(process.cwd(), "../../packages-new/circuits"),
2057
+ nodePath.resolve(process.cwd(), "../packages-new/circuits"),
2058
+ nodePath.resolve(process.cwd(), "packages-new/circuits"),
2059
+ nodePath.resolve(process.cwd(), "../../circuits"),
2060
+ nodePath.resolve(process.cwd(), "../circuits")
1924
2061
  ];
1925
2062
  for (const p of possiblePaths) {
1926
2063
  if (await areCircuitsAvailable(p)) {
@@ -1929,8 +2066,103 @@ async function getDefaultCircuitsPath() {
1929
2066
  }
1930
2067
  return possiblePaths[0];
1931
2068
  }
2069
+ var EXPECTED_CIRCUIT_HASHES = {
2070
+ // SHA-256 of the verification key JSON from withdraw_regular circuit
2071
+ withdraw_regular_vkey: null,
2072
+ // Set to null to skip check during development
2073
+ // SHA-256 of the verification key JSON from withdraw_swap circuit
2074
+ withdraw_swap_vkey: null
2075
+ // Set to null to skip check during development
2076
+ };
2077
+ async function verifyCircuitIntegrity(circuitsPath, circuit) {
2078
+ const expectedHash = circuit === "withdraw_regular" ? EXPECTED_CIRCUIT_HASHES.withdraw_regular_vkey : EXPECTED_CIRCUIT_HASHES.withdraw_swap_vkey;
2079
+ if (!expectedHash) {
2080
+ return {
2081
+ valid: true,
2082
+ circuit,
2083
+ error: "Verification skipped (no expected hash configured)"
2084
+ };
2085
+ }
2086
+ try {
2087
+ const vkeyPath = IS_BROWSER ? `${circuitsPath}/${circuit}_verification_key.json` : joinPath(circuitsPath, "build", `${circuit}_verification_key.json`);
2088
+ let vkeyData;
2089
+ if (IS_BROWSER) {
2090
+ const response = await fetch(vkeyPath);
2091
+ if (!response.ok) {
2092
+ return {
2093
+ valid: false,
2094
+ circuit,
2095
+ error: `Failed to fetch verification key: ${response.status}`
2096
+ };
2097
+ }
2098
+ vkeyData = await response.text();
2099
+ } else {
2100
+ try {
2101
+ const nodeFs = await getNodeFs();
2102
+ vkeyData = nodeFs.readFileSync(vkeyPath, "utf8");
2103
+ } catch (e) {
2104
+ return {
2105
+ valid: false,
2106
+ circuit,
2107
+ error: `Failed to read verification key: ${e}`
2108
+ };
2109
+ }
2110
+ }
2111
+ let computedHash;
2112
+ if (IS_BROWSER) {
2113
+ const encoder = new TextEncoder();
2114
+ const data = encoder.encode(vkeyData);
2115
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
2116
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
2117
+ computedHash = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
2118
+ } else {
2119
+ const nodeCrypto = await getNodeCrypto();
2120
+ computedHash = nodeCrypto.createHash("sha256").update(vkeyData).digest("hex");
2121
+ }
2122
+ if (computedHash === expectedHash) {
2123
+ return {
2124
+ valid: true,
2125
+ circuit,
2126
+ computedHash,
2127
+ expectedHash
2128
+ };
2129
+ } else {
2130
+ return {
2131
+ valid: false,
2132
+ circuit,
2133
+ error: `Verification key hash mismatch! This circuit may produce invalid proofs.`,
2134
+ computedHash,
2135
+ expectedHash
2136
+ };
2137
+ }
2138
+ } catch (error) {
2139
+ return {
2140
+ valid: false,
2141
+ circuit,
2142
+ error: `Circuit verification failed: ${error instanceof Error ? error.message : String(error)}`
2143
+ };
2144
+ }
2145
+ }
2146
+ async function verifyAllCircuits(circuitsPath) {
2147
+ const results = await Promise.all([
2148
+ verifyCircuitIntegrity(circuitsPath, "withdraw_regular"),
2149
+ verifyCircuitIntegrity(circuitsPath, "withdraw_swap")
2150
+ ]);
2151
+ return results;
2152
+ }
1932
2153
 
1933
2154
  // src/core/CloakSDK.ts
2155
+ var COMPUTE_BUDGET_PROGRAM_ID = new import_web35.PublicKey("ComputeBudget111111111111111111111111111111");
2156
+ function createSetLoadedAccountsDataSizeLimitInstruction(bytes) {
2157
+ const data = Buffer.alloc(5);
2158
+ data.writeUInt8(4, 0);
2159
+ data.writeUInt32LE(bytes, 1);
2160
+ return new import_web35.TransactionInstruction({
2161
+ keys: [],
2162
+ programId: COMPUTE_BUDGET_PROGRAM_ID,
2163
+ data
2164
+ });
2165
+ }
1934
2166
  var CLOAK_PROGRAM_ID = new import_web35.PublicKey("c1oak6tetxYnNfvXKFkpn1d98FxtK7B68vBQLYQpWKp");
1935
2167
  var CLOAK_API_URL = "https://api.cloak.ag";
1936
2168
  var CloakSDK = class {
@@ -2043,14 +2275,35 @@ var CloakSDK = class {
2043
2275
  async deposit(connection, amountOrNote, options) {
2044
2276
  try {
2045
2277
  let note;
2278
+ let isNewNote = false;
2046
2279
  if (typeof amountOrNote === "number") {
2280
+ options?.onProgress?.("generating_note", { message: "Generating note with secrets..." });
2047
2281
  note = await generateNote(amountOrNote, this.config.network);
2282
+ isNewNote = true;
2048
2283
  } else {
2049
2284
  note = amountOrNote;
2050
2285
  if (note.depositSignature) {
2051
2286
  throw new Error("Note has already been deposited");
2052
2287
  }
2053
2288
  }
2289
+ if (isNewNote) {
2290
+ await this.storage.saveNote(note);
2291
+ if (options?.onNoteGenerated) {
2292
+ options?.onProgress?.("awaiting_note_acknowledgment", {
2293
+ message: "Waiting for note to be saved before proceeding with deposit..."
2294
+ });
2295
+ try {
2296
+ await options.onNoteGenerated(note);
2297
+ } catch (error) {
2298
+ throw new Error(
2299
+ `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.`
2300
+ );
2301
+ }
2302
+ }
2303
+ options?.onProgress?.("note_saved", {
2304
+ message: "Note saved. Proceeding with on-chain deposit..."
2305
+ });
2306
+ }
2054
2307
  const payerPubkey = this.getPublicKey();
2055
2308
  const balance = await connection.getBalance(payerPubkey);
2056
2309
  const requiredAmount = note.amount + 5e3;
@@ -2070,10 +2323,51 @@ var CloakSDK = class {
2070
2323
  commitment: commitmentBytes
2071
2324
  });
2072
2325
  const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
2326
+ const priorityFee = options?.priorityFee ?? 1e4;
2327
+ const cuPriceIx = import_web35.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priorityFee });
2328
+ const dataSizeLimit = options?.loadedAccountsDataSizeLimit ?? 256 * 1024;
2329
+ const dataSizeLimitIx = dataSizeLimit > 0 ? createSetLoadedAccountsDataSizeLimitInstruction(dataSizeLimit) : null;
2330
+ let computeUnits;
2331
+ if (options?.optimizeCU && this.keypair) {
2332
+ options?.onProgress?.("simulating", { message: "Simulating transaction for optimal CU..." });
2333
+ const simCuLimitIx = import_web35.ComputeBudgetProgram.setComputeUnitLimit({ units: 2e5 });
2334
+ const simTransaction = new import_web35.Transaction({
2335
+ feePayer: payerPubkey,
2336
+ recentBlockhash: blockhash
2337
+ }).add(simCuLimitIx).add(cuPriceIx);
2338
+ if (dataSizeLimitIx) {
2339
+ simTransaction.add(dataSizeLimitIx);
2340
+ }
2341
+ simTransaction.add(depositIx);
2342
+ try {
2343
+ const simulation = await connection.simulateTransaction(simTransaction, [this.keypair]);
2344
+ if (simulation.value.err) {
2345
+ console.warn("Simulation failed, using default CU:", simulation.value.err);
2346
+ computeUnits = 4e4;
2347
+ } else {
2348
+ const simulatedCU = simulation.value.unitsConsumed ?? 3e4;
2349
+ computeUnits = Math.ceil(simulatedCU * 1.2);
2350
+ console.log(`CU optimization: ${simulatedCU} simulated \u2192 ${computeUnits} limit`);
2351
+ }
2352
+ } catch (simError) {
2353
+ console.warn("Simulation RPC error, using default CU:", simError);
2354
+ computeUnits = 4e4;
2355
+ }
2356
+ } else if (options?.optimizeCU && this.wallet) {
2357
+ console.warn("CU optimization via simulation not available in wallet mode (would require double signing). Using default.");
2358
+ computeUnits = options?.computeUnits ?? 4e4;
2359
+ } else {
2360
+ computeUnits = options?.computeUnits ?? 4e4;
2361
+ }
2362
+ const cuLimitIx = import_web35.ComputeBudgetProgram.setComputeUnitLimit({ units: computeUnits });
2073
2363
  const transaction = new import_web35.Transaction({
2074
2364
  feePayer: payerPubkey,
2075
2365
  recentBlockhash: blockhash
2076
- }).add(depositIx);
2366
+ }).add(cuLimitIx).add(cuPriceIx);
2367
+ if (dataSizeLimitIx) {
2368
+ transaction.add(dataSizeLimitIx);
2369
+ }
2370
+ transaction.add(depositIx);
2077
2371
  if (!transaction.feePayer) {
2078
2372
  throw new Error("Transaction feePayer is not set");
2079
2373
  }
@@ -2242,6 +2536,16 @@ var CloakSDK = class {
2242
2536
  pathIndices: merkleProof.pathIndices
2243
2537
  }
2244
2538
  });
2539
+ await this.storage.updateNote(note.commitment, {
2540
+ depositSignature: signature,
2541
+ depositSlot,
2542
+ leafIndex,
2543
+ root,
2544
+ merkleProof: {
2545
+ pathElements: merkleProof.pathElements,
2546
+ pathIndices: merkleProof.pathIndices
2547
+ }
2548
+ });
2245
2549
  return {
2246
2550
  note: updatedNote,
2247
2551
  signature,
@@ -3044,6 +3348,63 @@ async function copyNoteToClipboard(note) {
3044
3348
  }
3045
3349
 
3046
3350
  // src/utils/errors.ts
3351
+ var ShieldPoolErrors = {
3352
+ // Root management errors
3353
+ 4096: "Invalid Merkle root",
3354
+ 4097: "Root not found in the roots ring",
3355
+ 4098: "Roots ring is full",
3356
+ // Proof verification errors
3357
+ 4112: "Zero-knowledge proof is invalid",
3358
+ 4113: "Invalid proof size (expected 260 bytes)",
3359
+ 4114: "Invalid public inputs",
3360
+ 4115: "Verification key mismatch",
3361
+ // Nullifier errors
3362
+ 4128: "Double spend detected - this note has already been spent",
3363
+ 4129: "Nullifier shard is full",
3364
+ 4130: "Invalid nullifier",
3365
+ // Transaction validation errors
3366
+ 4144: "Output addresses or amounts don't match the proof",
3367
+ 4145: "Amount conservation failed - outputs + fee must equal input amount",
3368
+ 4146: "Invalid outputs hash",
3369
+ 4147: "Invalid amount (must be greater than zero)",
3370
+ 4148: "Invalid recipient address",
3371
+ 4149: "Commitment already exists in the tree",
3372
+ 4150: "Commitment log is full",
3373
+ // Math errors
3374
+ 4160: "Math overflow occurred",
3375
+ 4161: "Division by zero",
3376
+ // Account errors
3377
+ 4176: "Account validation failed - please check your wallet balance and try again",
3378
+ 4177: "Pool account owner mismatch",
3379
+ 4178: "Treasury account owner mismatch",
3380
+ 4179: "Roots ring account owner mismatch",
3381
+ 4180: "Nullifier shard account owner mismatch",
3382
+ 4181: "Pool account is not writable",
3383
+ 4182: "Treasury account is not writable",
3384
+ 4183: "Recipient account is not writable",
3385
+ 4184: "Insufficient lamports in pool or account",
3386
+ 4185: "Invalid account owner",
3387
+ 4186: "Invalid account size",
3388
+ 4187: "Commitments account is not writable",
3389
+ 4188: "Invalid admin authority",
3390
+ // Instruction errors
3391
+ 4192: "Invalid instruction data length",
3392
+ 4193: "Invalid instruction data format",
3393
+ 4194: "Missing required accounts",
3394
+ 4195: "Invalid instruction tag",
3395
+ // PoW/Scrambler errors
3396
+ 4196: "Invalid miner account",
3397
+ 4197: "Invalid claim account",
3398
+ 4198: "Failed to consume claim",
3399
+ // Groth16 verifier errors
3400
+ 4208: "Invalid G1 point length",
3401
+ 4209: "Invalid G2 point length",
3402
+ 4210: "Invalid public inputs length",
3403
+ 4211: "Public input exceeds field size",
3404
+ 4212: "G1 multiplication failed during proof preparation",
3405
+ 4213: "G1 addition failed during proof preparation",
3406
+ 4214: "Proof verification failed"
3407
+ };
3047
3408
  var PROGRAM_ERRORS = {
3048
3409
  // Nullifier errors
3049
3410
  "NullifierAlreadyUsed": "This note has already been withdrawn. Each note can only be spent once.",
@@ -3072,6 +3433,438 @@ var PROGRAM_ERRORS = {
3072
3433
  "AccountNotFound": "Required account not found.",
3073
3434
  "InvalidInstruction": "Invalid instruction data."
3074
3435
  };
3436
+ var ErrorPatterns = [
3437
+ // Wallet/User action errors
3438
+ {
3439
+ patterns: [
3440
+ "User rejected",
3441
+ "user rejected",
3442
+ "User denied",
3443
+ "user denied",
3444
+ "Transaction cancelled",
3445
+ "Transaction rejected",
3446
+ /code.*4001/i
3447
+ ],
3448
+ result: {
3449
+ title: "Transaction Cancelled",
3450
+ message: "You cancelled the transaction.",
3451
+ category: "wallet",
3452
+ recoverable: true
3453
+ }
3454
+ },
3455
+ {
3456
+ patterns: [
3457
+ "Wallet not connected",
3458
+ "wallet not connected",
3459
+ "Please connect wallet",
3460
+ "No wallet connected"
3461
+ ],
3462
+ result: {
3463
+ title: "Wallet Not Connected",
3464
+ message: "Please connect your wallet to continue.",
3465
+ category: "wallet",
3466
+ suggestion: "Click the wallet button to connect.",
3467
+ recoverable: true
3468
+ }
3469
+ },
3470
+ {
3471
+ patterns: [
3472
+ "SDK not initialized",
3473
+ "sdk not initialized"
3474
+ ],
3475
+ result: {
3476
+ title: "Wallet Connection Required",
3477
+ message: "Your wallet connection was interrupted.",
3478
+ category: "wallet",
3479
+ suggestion: "Please reconnect your wallet and try again.",
3480
+ recoverable: true
3481
+ }
3482
+ },
3483
+ {
3484
+ patterns: [
3485
+ "Wallet does not support signing",
3486
+ "signTransaction"
3487
+ ],
3488
+ result: {
3489
+ title: "Wallet Feature Not Supported",
3490
+ message: "Your wallet doesn't support the required signing method.",
3491
+ category: "wallet",
3492
+ suggestion: "Try using a different wallet like Phantom or Solflare.",
3493
+ recoverable: true
3494
+ }
3495
+ },
3496
+ // Balance errors
3497
+ {
3498
+ patterns: [
3499
+ "insufficient lamports",
3500
+ "Insufficient lamports",
3501
+ "insufficient balance",
3502
+ "Insufficient balance",
3503
+ "Insufficient SOL",
3504
+ "not enough SOL",
3505
+ "Attempt to debit an account but found no record",
3506
+ /0x1$/
3507
+ ],
3508
+ result: {
3509
+ title: "Insufficient Balance",
3510
+ message: "You don't have enough SOL for this transaction.",
3511
+ category: "validation",
3512
+ suggestion: "Add more SOL to your wallet or reduce the amount.",
3513
+ recoverable: true
3514
+ }
3515
+ },
3516
+ {
3517
+ patterns: [
3518
+ "Insufficient funds",
3519
+ "insufficient funds"
3520
+ ],
3521
+ result: {
3522
+ title: "Insufficient Funds",
3523
+ message: "Your wallet doesn't have enough funds for this transaction.",
3524
+ category: "validation",
3525
+ suggestion: "Add more funds to your wallet and try again.",
3526
+ recoverable: true
3527
+ }
3528
+ },
3529
+ // Network errors
3530
+ {
3531
+ patterns: [
3532
+ "fetch failed",
3533
+ "Failed to fetch",
3534
+ "Network error",
3535
+ "network error",
3536
+ "ECONNREFUSED",
3537
+ "ETIMEDOUT",
3538
+ "ENOTFOUND",
3539
+ "NetworkError",
3540
+ "net::ERR",
3541
+ "Failed to load"
3542
+ ],
3543
+ result: {
3544
+ title: "Connection Error",
3545
+ message: "Unable to connect to the network.",
3546
+ category: "network",
3547
+ suggestion: "Check your internet connection and try again.",
3548
+ recoverable: true
3549
+ }
3550
+ },
3551
+ {
3552
+ patterns: [
3553
+ "timeout",
3554
+ "Timeout",
3555
+ "TIMEOUT",
3556
+ "timed out",
3557
+ "Timed out"
3558
+ ],
3559
+ result: {
3560
+ title: "Request Timed Out",
3561
+ message: "The request took too long to complete.",
3562
+ category: "network",
3563
+ suggestion: "The network may be congested. Please try again in a moment.",
3564
+ recoverable: true
3565
+ }
3566
+ },
3567
+ {
3568
+ patterns: [
3569
+ "blockhash not found",
3570
+ "Blockhash not found",
3571
+ "block height exceeded"
3572
+ ],
3573
+ result: {
3574
+ title: "Transaction Expired",
3575
+ message: "The transaction took too long and expired.",
3576
+ category: "network",
3577
+ suggestion: "Please try again. If this persists, the network may be congested.",
3578
+ recoverable: true
3579
+ }
3580
+ },
3581
+ // Service errors (Indexer/Relay)
3582
+ {
3583
+ patterns: [
3584
+ "Indexer",
3585
+ "indexer",
3586
+ /indexer.*unavailable/i,
3587
+ /failed.*indexer/i
3588
+ ],
3589
+ result: {
3590
+ title: "Service Temporarily Unavailable",
3591
+ message: "The privacy service is temporarily unavailable.",
3592
+ category: "service",
3593
+ suggestion: "Please wait a moment and try again.",
3594
+ recoverable: true
3595
+ }
3596
+ },
3597
+ {
3598
+ patterns: [
3599
+ "Relay",
3600
+ "relay",
3601
+ /relay.*unavailable/i,
3602
+ /failed.*relay/i,
3603
+ "failed to submit",
3604
+ "Failed to submit"
3605
+ ],
3606
+ result: {
3607
+ title: "Processing Service Busy",
3608
+ message: "The transaction processing service is busy.",
3609
+ category: "service",
3610
+ suggestion: "Please wait a moment and try again.",
3611
+ recoverable: true
3612
+ }
3613
+ },
3614
+ {
3615
+ patterns: [
3616
+ "429",
3617
+ "Too Many Requests",
3618
+ "rate limit",
3619
+ "Rate limit"
3620
+ ],
3621
+ result: {
3622
+ title: "Too Many Requests",
3623
+ message: "You're making requests too quickly.",
3624
+ category: "service",
3625
+ suggestion: "Please wait a moment before trying again.",
3626
+ recoverable: true
3627
+ }
3628
+ },
3629
+ {
3630
+ patterns: [
3631
+ "503",
3632
+ "Service Unavailable",
3633
+ "502",
3634
+ "Bad Gateway"
3635
+ ],
3636
+ result: {
3637
+ title: "Service Temporarily Unavailable",
3638
+ message: "Our servers are temporarily unavailable.",
3639
+ category: "service",
3640
+ suggestion: "Please try again in a few minutes.",
3641
+ recoverable: true
3642
+ }
3643
+ },
3644
+ // Proof/ZK errors
3645
+ {
3646
+ patterns: [
3647
+ "Circuits not available",
3648
+ "circuits not available",
3649
+ "Failed to load circuit",
3650
+ "WASM",
3651
+ "wasm",
3652
+ /circuit.*not.*found/i
3653
+ ],
3654
+ result: {
3655
+ title: "Loading Error",
3656
+ message: "Failed to load required cryptographic components.",
3657
+ category: "service",
3658
+ suggestion: "Try refreshing the page. If the problem persists, clear your browser cache.",
3659
+ recoverable: true
3660
+ }
3661
+ },
3662
+ {
3663
+ patterns: [
3664
+ "proof generation",
3665
+ "Proof generation",
3666
+ "Failed to generate proof",
3667
+ "proving error"
3668
+ ],
3669
+ result: {
3670
+ title: "Proof Generation Failed",
3671
+ message: "Failed to generate the privacy proof.",
3672
+ category: "service",
3673
+ suggestion: "Try again with a smaller amount or refresh the page.",
3674
+ recoverable: true
3675
+ }
3676
+ },
3677
+ // Validation errors
3678
+ {
3679
+ patterns: [
3680
+ "Invalid amount",
3681
+ "invalid amount",
3682
+ "Amount must be",
3683
+ "amount must be",
3684
+ "Amount too small"
3685
+ ],
3686
+ result: {
3687
+ title: "Invalid Amount",
3688
+ message: "The amount you entered is not valid.",
3689
+ category: "validation",
3690
+ suggestion: "Enter an amount greater than the minimum required.",
3691
+ recoverable: true
3692
+ }
3693
+ },
3694
+ {
3695
+ patterns: [
3696
+ "Invalid recipient",
3697
+ "invalid recipient",
3698
+ "Invalid address",
3699
+ "invalid address",
3700
+ "Invalid Solana address"
3701
+ ],
3702
+ result: {
3703
+ title: "Invalid Address",
3704
+ message: "The recipient address is not valid.",
3705
+ category: "validation",
3706
+ suggestion: "Check the address and make sure it's a valid Solana address.",
3707
+ recoverable: true
3708
+ }
3709
+ },
3710
+ {
3711
+ patterns: [
3712
+ "Invalid token",
3713
+ "invalid token",
3714
+ "Unsupported token"
3715
+ ],
3716
+ result: {
3717
+ title: "Unsupported Token",
3718
+ message: "This token is not supported.",
3719
+ category: "validation",
3720
+ suggestion: "Select a supported token and try again.",
3721
+ recoverable: true
3722
+ }
3723
+ },
3724
+ // Swap errors
3725
+ {
3726
+ patterns: [
3727
+ "Failed to get quote",
3728
+ "failed to get quote",
3729
+ "No route found",
3730
+ "no route found",
3731
+ "Insufficient liquidity",
3732
+ "insufficient liquidity"
3733
+ ],
3734
+ result: {
3735
+ title: "Swap Quote Unavailable",
3736
+ message: "Unable to get a price quote for this swap.",
3737
+ category: "service",
3738
+ suggestion: "Try a different amount or wait for better liquidity.",
3739
+ recoverable: true
3740
+ }
3741
+ },
3742
+ {
3743
+ patterns: [
3744
+ "Slippage",
3745
+ "slippage",
3746
+ "Price impact",
3747
+ "price impact"
3748
+ ],
3749
+ result: {
3750
+ title: "Price Changed",
3751
+ message: "The price changed too much during the transaction.",
3752
+ category: "transaction",
3753
+ suggestion: "Try again or increase your slippage tolerance.",
3754
+ recoverable: true
3755
+ }
3756
+ },
3757
+ // Transaction errors
3758
+ {
3759
+ patterns: [
3760
+ "Double spend",
3761
+ "double spend",
3762
+ "already been spent",
3763
+ "already spent"
3764
+ ],
3765
+ result: {
3766
+ title: "Already Spent",
3767
+ message: "This note has already been used.",
3768
+ category: "transaction",
3769
+ suggestion: "This funds have already been withdrawn.",
3770
+ recoverable: false
3771
+ }
3772
+ },
3773
+ {
3774
+ patterns: [
3775
+ "simulation failed",
3776
+ "Simulation failed",
3777
+ "Transaction simulation"
3778
+ ],
3779
+ result: {
3780
+ title: "Transaction Failed",
3781
+ message: "The transaction could not be completed.",
3782
+ category: "transaction",
3783
+ suggestion: "Please try again. If the problem persists, check your balance.",
3784
+ recoverable: true
3785
+ }
3786
+ },
3787
+ {
3788
+ patterns: [
3789
+ "confirmation timeout",
3790
+ "Confirmation timeout",
3791
+ "not confirmed"
3792
+ ],
3793
+ result: {
3794
+ title: "Confirmation Pending",
3795
+ message: "Transaction confirmation is taking longer than expected.",
3796
+ category: "network",
3797
+ suggestion: "Your transaction may still complete. Check your wallet or try again.",
3798
+ recoverable: true
3799
+ }
3800
+ }
3801
+ ];
3802
+ function parseError(error) {
3803
+ let errorMessage = "";
3804
+ let originalError = "";
3805
+ if (error instanceof Error) {
3806
+ errorMessage = error.message;
3807
+ originalError = error.stack || error.message;
3808
+ } else if (typeof error === "string") {
3809
+ errorMessage = error;
3810
+ originalError = error;
3811
+ } else if (error && typeof error === "object") {
3812
+ const err = error;
3813
+ errorMessage = String(err.message || err.error || err.msg || JSON.stringify(error));
3814
+ originalError = JSON.stringify(error);
3815
+ } else {
3816
+ errorMessage = String(error);
3817
+ originalError = String(error);
3818
+ }
3819
+ for (const { patterns, result } of ErrorPatterns) {
3820
+ for (const pattern of patterns) {
3821
+ if (typeof pattern === "string") {
3822
+ if (errorMessage.includes(pattern)) {
3823
+ return { ...result, originalError };
3824
+ }
3825
+ } else if (pattern instanceof RegExp) {
3826
+ if (pattern.test(errorMessage)) {
3827
+ return { ...result, originalError };
3828
+ }
3829
+ }
3830
+ }
3831
+ }
3832
+ const programError = tryParseProgramError(errorMessage);
3833
+ if (programError) {
3834
+ return { ...programError, originalError };
3835
+ }
3836
+ return {
3837
+ title: "Something Went Wrong",
3838
+ message: "An unexpected error occurred.",
3839
+ category: "unknown",
3840
+ suggestion: "Please try again. If the problem persists, refresh the page.",
3841
+ recoverable: true,
3842
+ originalError
3843
+ };
3844
+ }
3845
+ function tryParseProgramError(message) {
3846
+ const match = message.match(/\{"InstructionError":\[(\d+),\{"Custom":(\d+)\}\]\}/);
3847
+ if (match) {
3848
+ const errorCode = parseInt(match[2]);
3849
+ const friendlyMessage = ShieldPoolErrors[errorCode];
3850
+ if (friendlyMessage) {
3851
+ return {
3852
+ title: "Transaction Failed",
3853
+ message: friendlyMessage,
3854
+ category: "transaction",
3855
+ recoverable: !friendlyMessage.toLowerCase().includes("double spend")
3856
+ };
3857
+ }
3858
+ return {
3859
+ title: "Transaction Failed",
3860
+ message: `Transaction failed with error code ${errorCode}.`,
3861
+ category: "transaction",
3862
+ suggestion: "Please try again or contact support if this persists.",
3863
+ recoverable: true
3864
+ };
3865
+ }
3866
+ return null;
3867
+ }
3075
3868
  function parseTransactionError(error) {
3076
3869
  if (!error) return "An unknown error occurred";
3077
3870
  const errorStr = typeof error === "string" ? error : error.message || error.toString();
@@ -3251,6 +4044,173 @@ function keypairToAdapter(keypair) {
3251
4044
  };
3252
4045
  }
3253
4046
 
4047
+ // src/utils/pending-operations.ts
4048
+ var PENDING_DEPOSITS_KEY = "cloak_pending_deposits";
4049
+ var PENDING_WITHDRAWALS_KEY = "cloak_pending_withdrawals";
4050
+ function getStorage() {
4051
+ if (typeof globalThis !== "undefined" && globalThis.localStorage) {
4052
+ return globalThis.localStorage;
4053
+ }
4054
+ return null;
4055
+ }
4056
+ function savePendingDeposit(deposit) {
4057
+ const storage = getStorage();
4058
+ if (!storage) {
4059
+ console.warn("localStorage not available - pending deposit not persisted");
4060
+ return;
4061
+ }
4062
+ const deposits = loadPendingDeposits();
4063
+ const index = deposits.findIndex((d) => d.note.commitment === deposit.note.commitment);
4064
+ if (index >= 0) {
4065
+ deposits[index] = deposit;
4066
+ } else {
4067
+ deposits.push(deposit);
4068
+ }
4069
+ storage.setItem(PENDING_DEPOSITS_KEY, JSON.stringify(deposits));
4070
+ }
4071
+ function loadPendingDeposits() {
4072
+ const storage = getStorage();
4073
+ if (!storage) return [];
4074
+ const stored = storage.getItem(PENDING_DEPOSITS_KEY);
4075
+ if (!stored) return [];
4076
+ try {
4077
+ return JSON.parse(stored);
4078
+ } catch {
4079
+ return [];
4080
+ }
4081
+ }
4082
+ function updatePendingDeposit(commitment, updates) {
4083
+ const storage = getStorage();
4084
+ if (!storage) return;
4085
+ const deposits = loadPendingDeposits();
4086
+ const index = deposits.findIndex((d) => d.note.commitment === commitment);
4087
+ if (index >= 0) {
4088
+ deposits[index] = { ...deposits[index], ...updates };
4089
+ storage.setItem(PENDING_DEPOSITS_KEY, JSON.stringify(deposits));
4090
+ }
4091
+ }
4092
+ function removePendingDeposit(commitment) {
4093
+ const storage = getStorage();
4094
+ if (!storage) return;
4095
+ const deposits = loadPendingDeposits();
4096
+ const filtered = deposits.filter((d) => d.note.commitment !== commitment);
4097
+ storage.setItem(PENDING_DEPOSITS_KEY, JSON.stringify(filtered));
4098
+ }
4099
+ function clearPendingDeposits() {
4100
+ const storage = getStorage();
4101
+ if (storage) {
4102
+ storage.removeItem(PENDING_DEPOSITS_KEY);
4103
+ }
4104
+ }
4105
+ function savePendingWithdrawal(withdrawal) {
4106
+ const storage = getStorage();
4107
+ if (!storage) {
4108
+ console.warn("localStorage not available - pending withdrawal not persisted");
4109
+ return;
4110
+ }
4111
+ const withdrawals = loadPendingWithdrawals();
4112
+ const index = withdrawals.findIndex((w) => w.requestId === withdrawal.requestId);
4113
+ if (index >= 0) {
4114
+ withdrawals[index] = withdrawal;
4115
+ } else {
4116
+ withdrawals.push(withdrawal);
4117
+ }
4118
+ storage.setItem(PENDING_WITHDRAWALS_KEY, JSON.stringify(withdrawals));
4119
+ }
4120
+ function loadPendingWithdrawals() {
4121
+ const storage = getStorage();
4122
+ if (!storage) return [];
4123
+ const stored = storage.getItem(PENDING_WITHDRAWALS_KEY);
4124
+ if (!stored) return [];
4125
+ try {
4126
+ return JSON.parse(stored);
4127
+ } catch {
4128
+ return [];
4129
+ }
4130
+ }
4131
+ function updatePendingWithdrawal(requestId, updates) {
4132
+ const storage = getStorage();
4133
+ if (!storage) return;
4134
+ const withdrawals = loadPendingWithdrawals();
4135
+ const index = withdrawals.findIndex((w) => w.requestId === requestId);
4136
+ if (index >= 0) {
4137
+ withdrawals[index] = { ...withdrawals[index], ...updates };
4138
+ storage.setItem(PENDING_WITHDRAWALS_KEY, JSON.stringify(withdrawals));
4139
+ }
4140
+ }
4141
+ function removePendingWithdrawal(requestId) {
4142
+ const storage = getStorage();
4143
+ if (!storage) return;
4144
+ const withdrawals = loadPendingWithdrawals();
4145
+ const filtered = withdrawals.filter((w) => w.requestId !== requestId);
4146
+ storage.setItem(PENDING_WITHDRAWALS_KEY, JSON.stringify(filtered));
4147
+ }
4148
+ function clearPendingWithdrawals() {
4149
+ const storage = getStorage();
4150
+ if (storage) {
4151
+ storage.removeItem(PENDING_WITHDRAWALS_KEY);
4152
+ }
4153
+ }
4154
+ function hasPendingOperations() {
4155
+ const deposits = loadPendingDeposits();
4156
+ const withdrawals = loadPendingWithdrawals();
4157
+ const pendingDeposits = deposits.filter(
4158
+ (d) => d.status === "pending" || d.status === "tx_sent"
4159
+ );
4160
+ const pendingWithdrawals = withdrawals.filter(
4161
+ (w) => w.status === "pending" || w.status === "processing"
4162
+ );
4163
+ return pendingDeposits.length > 0 || pendingWithdrawals.length > 0;
4164
+ }
4165
+ function getPendingOperationsSummary() {
4166
+ const deposits = loadPendingDeposits().filter(
4167
+ (d) => d.status === "pending" || d.status === "tx_sent"
4168
+ );
4169
+ const withdrawals = loadPendingWithdrawals().filter(
4170
+ (w) => w.status === "pending" || w.status === "processing"
4171
+ );
4172
+ return {
4173
+ deposits,
4174
+ withdrawals,
4175
+ totalPending: deposits.length + withdrawals.length
4176
+ };
4177
+ }
4178
+ function cleanupStalePendingOperations(maxAgeMs = 24 * 60 * 60 * 1e3) {
4179
+ const now = Date.now();
4180
+ let removedDeposits = 0;
4181
+ let removedWithdrawals = 0;
4182
+ const deposits = loadPendingDeposits();
4183
+ const activeDeposits = deposits.filter((d) => {
4184
+ const age = now - d.startedAt;
4185
+ const isStale = age > maxAgeMs;
4186
+ const isTerminal = d.status === "confirmed" || d.status === "failed";
4187
+ if (isStale || isTerminal) {
4188
+ removedDeposits++;
4189
+ return false;
4190
+ }
4191
+ return true;
4192
+ });
4193
+ const storage = getStorage();
4194
+ if (storage) {
4195
+ storage.setItem(PENDING_DEPOSITS_KEY, JSON.stringify(activeDeposits));
4196
+ }
4197
+ const withdrawals = loadPendingWithdrawals();
4198
+ const activeWithdrawals = withdrawals.filter((w) => {
4199
+ const age = now - w.startedAt;
4200
+ const isStale = age > maxAgeMs;
4201
+ const isTerminal = w.status === "completed" || w.status === "failed";
4202
+ if (isStale || isTerminal) {
4203
+ removedWithdrawals++;
4204
+ return false;
4205
+ }
4206
+ return true;
4207
+ });
4208
+ if (storage) {
4209
+ storage.setItem(PENDING_WITHDRAWALS_KEY, JSON.stringify(activeWithdrawals));
4210
+ }
4211
+ return { removedDeposits, removedWithdrawals };
4212
+ }
4213
+
3254
4214
  // src/index.ts
3255
4215
  var VERSION = "1.0.0";
3256
4216
  // Annotate the CommonJS export names for ESM import in node:
@@ -3259,12 +4219,14 @@ var VERSION = "1.0.0";
3259
4219
  CloakError,
3260
4220
  CloakSDK,
3261
4221
  DepositRecoveryService,
4222
+ EXPECTED_CIRCUIT_HASHES,
3262
4223
  FIXED_FEE_LAMPORTS,
3263
4224
  IndexerService,
3264
4225
  LAMPORTS_PER_SOL,
3265
4226
  LocalStorageAdapter,
3266
4227
  MemoryStorageAdapter,
3267
4228
  RelayService,
4229
+ ShieldPoolErrors,
3268
4230
  VARIABLE_FEE_RATE,
3269
4231
  VERSION,
3270
4232
  bigintToBytes32,
@@ -3272,6 +4234,9 @@ var VERSION = "1.0.0";
3272
4234
  bytesToHex,
3273
4235
  calculateFee,
3274
4236
  calculateRelayFee,
4237
+ cleanupStalePendingOperations,
4238
+ clearPendingDeposits,
4239
+ clearPendingWithdrawals,
3275
4240
  computeCommitment,
3276
4241
  computeMerkleRoot,
3277
4242
  computeNullifier,
@@ -3311,12 +4276,14 @@ var VERSION = "1.0.0";
3311
4276
  getAddressExplorerUrl,
3312
4277
  getDistributableAmount,
3313
4278
  getExplorerUrl,
4279
+ getPendingOperationsSummary,
3314
4280
  getPublicKey,
3315
4281
  getPublicViewKey,
3316
4282
  getRecipientAmount,
3317
4283
  getRpcUrlForNetwork,
3318
4284
  getShieldPoolPDAs,
3319
4285
  getViewKey,
4286
+ hasPendingOperations,
3320
4287
  hexToBigint,
3321
4288
  hexToBytes,
3322
4289
  importKeys,
@@ -3326,7 +4293,10 @@ var VERSION = "1.0.0";
3326
4293
  isValidSolanaAddress,
3327
4294
  isWithdrawable,
3328
4295
  keypairToAdapter,
4296
+ loadPendingDeposits,
4297
+ loadPendingWithdrawals,
3329
4298
  parseAmount,
4299
+ parseError,
3330
4300
  parseNote,
3331
4301
  parseTransactionError,
3332
4302
  poseidonHash,
@@ -3335,6 +4305,10 @@ var VERSION = "1.0.0";
3335
4305
  proofToBytes,
3336
4306
  pubkeyToLimbs,
3337
4307
  randomBytes,
4308
+ removePendingDeposit,
4309
+ removePendingWithdrawal,
4310
+ savePendingDeposit,
4311
+ savePendingWithdrawal,
3338
4312
  scanNotesForWallet,
3339
4313
  sendTransaction,
3340
4314
  serializeNote,
@@ -3342,10 +4316,14 @@ var VERSION = "1.0.0";
3342
4316
  splitTo2Limbs,
3343
4317
  tryDecryptNote,
3344
4318
  updateNoteWithDeposit,
4319
+ updatePendingDeposit,
4320
+ updatePendingWithdrawal,
3345
4321
  validateDepositParams,
3346
4322
  validateNote,
3347
4323
  validateOutputsSum,
3348
4324
  validateTransfers,
3349
4325
  validateWalletConnected,
3350
- validateWithdrawableNote
4326
+ validateWithdrawableNote,
4327
+ verifyAllCircuits,
4328
+ verifyCircuitIntegrity
3351
4329
  });