@cloak.ag/sdk 1.0.3 → 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/README.md +39 -4
- package/dist/chunk-S76QK36U.js +303 -0
- package/dist/crypto-3TOLQOOK.js +54 -0
- package/dist/index.cjs +1044 -313
- package/dist/index.d.cts +285 -130
- package/dist/index.d.ts +285 -130
- package/dist/index.js +1025 -311
- package/package.json +9 -5
package/dist/index.cjs
CHANGED
|
@@ -34,13 +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
|
-
ProverService: () => ProverService,
|
|
43
43
|
RelayService: () => RelayService,
|
|
44
|
+
ShieldPoolErrors: () => ShieldPoolErrors,
|
|
44
45
|
VARIABLE_FEE_RATE: () => VARIABLE_FEE_RATE,
|
|
45
46
|
VERSION: () => VERSION,
|
|
46
47
|
bigintToBytes32: () => bigintToBytes32,
|
|
@@ -48,6 +49,9 @@ __export(index_exports, {
|
|
|
48
49
|
bytesToHex: () => bytesToHex,
|
|
49
50
|
calculateFee: () => calculateFee2,
|
|
50
51
|
calculateRelayFee: () => calculateRelayFee,
|
|
52
|
+
cleanupStalePendingOperations: () => cleanupStalePendingOperations,
|
|
53
|
+
clearPendingDeposits: () => clearPendingDeposits,
|
|
54
|
+
clearPendingWithdrawals: () => clearPendingWithdrawals,
|
|
51
55
|
computeCommitment: () => computeCommitment,
|
|
52
56
|
computeMerkleRoot: () => computeMerkleRoot,
|
|
53
57
|
computeNullifier: () => computeNullifier,
|
|
@@ -87,12 +91,14 @@ __export(index_exports, {
|
|
|
87
91
|
getAddressExplorerUrl: () => getAddressExplorerUrl,
|
|
88
92
|
getDistributableAmount: () => getDistributableAmount2,
|
|
89
93
|
getExplorerUrl: () => getExplorerUrl,
|
|
94
|
+
getPendingOperationsSummary: () => getPendingOperationsSummary,
|
|
90
95
|
getPublicKey: () => getPublicKey,
|
|
91
96
|
getPublicViewKey: () => getPublicViewKey,
|
|
92
97
|
getRecipientAmount: () => getRecipientAmount,
|
|
93
98
|
getRpcUrlForNetwork: () => getRpcUrlForNetwork,
|
|
94
99
|
getShieldPoolPDAs: () => getShieldPoolPDAs,
|
|
95
100
|
getViewKey: () => getViewKey,
|
|
101
|
+
hasPendingOperations: () => hasPendingOperations,
|
|
96
102
|
hexToBigint: () => hexToBigint,
|
|
97
103
|
hexToBytes: () => hexToBytes,
|
|
98
104
|
importKeys: () => importKeys,
|
|
@@ -102,7 +108,10 @@ __export(index_exports, {
|
|
|
102
108
|
isValidSolanaAddress: () => isValidSolanaAddress,
|
|
103
109
|
isWithdrawable: () => isWithdrawable,
|
|
104
110
|
keypairToAdapter: () => keypairToAdapter,
|
|
111
|
+
loadPendingDeposits: () => loadPendingDeposits,
|
|
112
|
+
loadPendingWithdrawals: () => loadPendingWithdrawals,
|
|
105
113
|
parseAmount: () => parseAmount,
|
|
114
|
+
parseError: () => parseError,
|
|
106
115
|
parseNote: () => parseNote,
|
|
107
116
|
parseTransactionError: () => parseTransactionError,
|
|
108
117
|
poseidonHash: () => poseidonHash,
|
|
@@ -111,6 +120,10 @@ __export(index_exports, {
|
|
|
111
120
|
proofToBytes: () => proofToBytes,
|
|
112
121
|
pubkeyToLimbs: () => pubkeyToLimbs,
|
|
113
122
|
randomBytes: () => randomBytes,
|
|
123
|
+
removePendingDeposit: () => removePendingDeposit,
|
|
124
|
+
removePendingWithdrawal: () => removePendingWithdrawal,
|
|
125
|
+
savePendingDeposit: () => savePendingDeposit,
|
|
126
|
+
savePendingWithdrawal: () => savePendingWithdrawal,
|
|
114
127
|
scanNotesForWallet: () => scanNotesForWallet,
|
|
115
128
|
sendTransaction: () => sendTransaction,
|
|
116
129
|
serializeNote: () => serializeNote,
|
|
@@ -118,12 +131,16 @@ __export(index_exports, {
|
|
|
118
131
|
splitTo2Limbs: () => splitTo2Limbs,
|
|
119
132
|
tryDecryptNote: () => tryDecryptNote,
|
|
120
133
|
updateNoteWithDeposit: () => updateNoteWithDeposit,
|
|
134
|
+
updatePendingDeposit: () => updatePendingDeposit,
|
|
135
|
+
updatePendingWithdrawal: () => updatePendingWithdrawal,
|
|
121
136
|
validateDepositParams: () => validateDepositParams,
|
|
122
137
|
validateNote: () => validateNote,
|
|
123
138
|
validateOutputsSum: () => validateOutputsSum,
|
|
124
139
|
validateTransfers: () => validateTransfers,
|
|
125
140
|
validateWalletConnected: () => validateWalletConnected,
|
|
126
|
-
validateWithdrawableNote: () => validateWithdrawableNote
|
|
141
|
+
validateWithdrawableNote: () => validateWithdrawableNote,
|
|
142
|
+
verifyAllCircuits: () => verifyAllCircuits,
|
|
143
|
+
verifyCircuitIntegrity: () => verifyCircuitIntegrity
|
|
127
144
|
});
|
|
128
145
|
module.exports = __toCommonJS(index_exports);
|
|
129
146
|
|
|
@@ -290,14 +307,14 @@ function randomBytes(length) {
|
|
|
290
307
|
} catch {
|
|
291
308
|
}
|
|
292
309
|
try {
|
|
293
|
-
const
|
|
294
|
-
if (
|
|
295
|
-
const buffer =
|
|
310
|
+
const nodeCrypto2 = require("crypto");
|
|
311
|
+
if (nodeCrypto2?.randomBytes) {
|
|
312
|
+
const buffer = nodeCrypto2.randomBytes(length);
|
|
296
313
|
bytes.set(buffer);
|
|
297
314
|
return bytes;
|
|
298
315
|
}
|
|
299
|
-
if (
|
|
300
|
-
|
|
316
|
+
if (nodeCrypto2?.webcrypto?.getRandomValues) {
|
|
317
|
+
nodeCrypto2.webcrypto.getRandomValues(bytes);
|
|
301
318
|
return bytes;
|
|
302
319
|
}
|
|
303
320
|
} catch {
|
|
@@ -419,8 +436,8 @@ function generateMasterSeed() {
|
|
|
419
436
|
cryptoObj.getRandomValues(seed);
|
|
420
437
|
} else {
|
|
421
438
|
try {
|
|
422
|
-
const
|
|
423
|
-
const buffer =
|
|
439
|
+
const nodeCrypto2 = require("crypto");
|
|
440
|
+
const buffer = nodeCrypto2.randomBytes(32);
|
|
424
441
|
seed.set(buffer);
|
|
425
442
|
} catch {
|
|
426
443
|
throw new Error("No secure random number generator available");
|
|
@@ -1161,6 +1178,8 @@ var RelayService = class {
|
|
|
1161
1178
|
*
|
|
1162
1179
|
* @param params - Withdrawal parameters
|
|
1163
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.
|
|
1164
1183
|
* @returns Transaction signature when completed
|
|
1165
1184
|
*
|
|
1166
1185
|
* @example
|
|
@@ -1170,11 +1189,12 @@ var RelayService = class {
|
|
|
1170
1189
|
* publicInputs: { root, nf, outputs_hash, amount },
|
|
1171
1190
|
* outputs: [{ recipient: addr, amount: lamports }],
|
|
1172
1191
|
* feeBps: 50
|
|
1173
|
-
* }, (status) => console.log(`Status: ${status}`)
|
|
1192
|
+
* }, (status) => console.log(`Status: ${status}`),
|
|
1193
|
+
* (requestId) => localStorage.setItem('pending_withdraw', requestId));
|
|
1174
1194
|
* console.log(`Transaction: ${signature}`);
|
|
1175
1195
|
* ```
|
|
1176
1196
|
*/
|
|
1177
|
-
async submitWithdraw(params, onStatusUpdate) {
|
|
1197
|
+
async submitWithdraw(params, onStatusUpdate, onRequestId) {
|
|
1178
1198
|
const proofBytes = hexToBytes(params.proof);
|
|
1179
1199
|
const proofBase64 = this.bytesToBase64(proofBytes);
|
|
1180
1200
|
const requestBody = {
|
|
@@ -1215,8 +1235,55 @@ var RelayService = class {
|
|
|
1215
1235
|
if (!requestId) {
|
|
1216
1236
|
throw new Error("Relay response missing request_id");
|
|
1217
1237
|
}
|
|
1238
|
+
if (onRequestId) {
|
|
1239
|
+
onRequestId(requestId);
|
|
1240
|
+
}
|
|
1218
1241
|
return this.pollForCompletion(requestId, onStatusUpdate);
|
|
1219
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
|
+
}
|
|
1220
1287
|
/**
|
|
1221
1288
|
* Poll for withdrawal completion
|
|
1222
1289
|
*
|
|
@@ -1289,7 +1356,7 @@ var RelayService = class {
|
|
|
1289
1356
|
* console.log(`Transaction: ${signature}`);
|
|
1290
1357
|
* ```
|
|
1291
1358
|
*/
|
|
1292
|
-
async submitSwap(params, onStatusUpdate) {
|
|
1359
|
+
async submitSwap(params, onStatusUpdate, onRequestId) {
|
|
1293
1360
|
const proofBytes = hexToBytes(params.proof);
|
|
1294
1361
|
const proofBase64 = this.bytesToBase64(proofBytes);
|
|
1295
1362
|
const requestBody = {
|
|
@@ -1331,6 +1398,9 @@ var RelayService = class {
|
|
|
1331
1398
|
if (!requestId) {
|
|
1332
1399
|
throw new Error("Relay response missing request_id");
|
|
1333
1400
|
}
|
|
1401
|
+
if (onRequestId) {
|
|
1402
|
+
onRequestId(requestId);
|
|
1403
|
+
}
|
|
1334
1404
|
return this.pollForCompletion(requestId, onStatusUpdate);
|
|
1335
1405
|
}
|
|
1336
1406
|
/**
|
|
@@ -1567,29 +1637,94 @@ var DepositRecoveryService = class {
|
|
|
1567
1637
|
}
|
|
1568
1638
|
/**
|
|
1569
1639
|
* Check if a deposit already exists in the indexer
|
|
1640
|
+
* Uses the enhanced deposit lookup endpoint with include_proof=true
|
|
1570
1641
|
*
|
|
1571
1642
|
* @private
|
|
1572
1643
|
*/
|
|
1573
|
-
async checkExistingDeposit(
|
|
1644
|
+
async checkExistingDeposit(commitment) {
|
|
1574
1645
|
try {
|
|
1575
|
-
const
|
|
1576
|
-
const
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
return null;
|
|
1583
|
-
} catch (e) {
|
|
1584
|
-
continue;
|
|
1585
|
-
}
|
|
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;
|
|
1586
1653
|
}
|
|
1654
|
+
throw new Error(`Failed to check deposit: ${response.status}`);
|
|
1587
1655
|
}
|
|
1588
|
-
|
|
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
|
+
};
|
|
1589
1678
|
} catch (error) {
|
|
1590
1679
|
return null;
|
|
1591
1680
|
}
|
|
1592
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
|
+
}
|
|
1593
1728
|
/**
|
|
1594
1729
|
* Finalize a deposit via server API (alternative recovery method)
|
|
1595
1730
|
*
|
|
@@ -1745,39 +1880,15 @@ function getShieldPoolPDAs(programId, mint) {
|
|
|
1745
1880
|
|
|
1746
1881
|
// src/utils/proof-generation.ts
|
|
1747
1882
|
var snarkjs = __toESM(require("snarkjs"), 1);
|
|
1748
|
-
var
|
|
1749
|
-
var fs = null;
|
|
1750
|
-
async function loadNodeModules() {
|
|
1751
|
-
const isBrowser = typeof window !== "undefined" || typeof globalThis !== "undefined" && globalThis.window;
|
|
1752
|
-
if (!isBrowser && typeof process !== "undefined" && process.versions?.node) {
|
|
1753
|
-
if (!path) {
|
|
1754
|
-
path = await import("path");
|
|
1755
|
-
}
|
|
1756
|
-
if (!fs) {
|
|
1757
|
-
fs = await import("fs");
|
|
1758
|
-
}
|
|
1759
|
-
return { path, fs };
|
|
1760
|
-
}
|
|
1761
|
-
return { path: null, fs: null };
|
|
1762
|
-
}
|
|
1883
|
+
var IS_BROWSER = typeof window !== "undefined" || typeof globalThis !== "undefined" && typeof globalThis.document !== "undefined";
|
|
1763
1884
|
function joinPath(...parts) {
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
return path.join(...parts);
|
|
1885
|
+
if (IS_BROWSER) {
|
|
1886
|
+
return parts.join("/").replace(/\/+/g, "/");
|
|
1767
1887
|
}
|
|
1768
1888
|
return parts.join("/").replace(/\/+/g, "/");
|
|
1769
1889
|
}
|
|
1770
1890
|
async function fileExists(filePath) {
|
|
1771
|
-
|
|
1772
|
-
if (fs2) {
|
|
1773
|
-
try {
|
|
1774
|
-
return fs2.existsSync(filePath);
|
|
1775
|
-
} catch {
|
|
1776
|
-
return false;
|
|
1777
|
-
}
|
|
1778
|
-
}
|
|
1779
|
-
const isBrowser = typeof window !== "undefined" || typeof globalThis !== "undefined" && globalThis.window;
|
|
1780
|
-
if (isBrowser) {
|
|
1891
|
+
if (IS_BROWSER) {
|
|
1781
1892
|
try {
|
|
1782
1893
|
const response = await fetch(filePath, { method: "HEAD" });
|
|
1783
1894
|
return response.ok;
|
|
@@ -1785,18 +1896,26 @@ async function fileExists(filePath) {
|
|
|
1785
1896
|
return false;
|
|
1786
1897
|
}
|
|
1787
1898
|
}
|
|
1788
|
-
|
|
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
|
+
}
|
|
1789
1909
|
}
|
|
1790
|
-
async function generateWithdrawRegularProof(inputs,
|
|
1791
|
-
const isBrowser = typeof window !== "undefined" || typeof globalThis !== "undefined" && globalThis.window;
|
|
1910
|
+
async function generateWithdrawRegularProof(inputs, circuitsPath2) {
|
|
1792
1911
|
let wasmPath;
|
|
1793
1912
|
let zkeyPath;
|
|
1794
|
-
if (
|
|
1795
|
-
wasmPath = `${
|
|
1796
|
-
zkeyPath = `${
|
|
1913
|
+
if (IS_BROWSER) {
|
|
1914
|
+
wasmPath = `${circuitsPath2}/withdraw_regular_js/withdraw_regular.wasm`;
|
|
1915
|
+
zkeyPath = `${circuitsPath2}/withdraw_regular_final.zkey`;
|
|
1797
1916
|
} else {
|
|
1798
|
-
wasmPath = joinPath(
|
|
1799
|
-
zkeyPath = joinPath(
|
|
1917
|
+
wasmPath = joinPath(circuitsPath2, "build", "withdraw_regular_js", "withdraw_regular.wasm");
|
|
1918
|
+
zkeyPath = joinPath(circuitsPath2, "build", "withdraw_regular_final.zkey");
|
|
1800
1919
|
const wasmExists = await fileExists(wasmPath);
|
|
1801
1920
|
const zkeyExists = await fileExists(zkeyPath);
|
|
1802
1921
|
if (!wasmExists) {
|
|
@@ -1828,11 +1947,7 @@ async function generateWithdrawRegularProof(inputs, circuitsPath) {
|
|
|
1828
1947
|
var_fee: inputs.var_fee.toString(),
|
|
1829
1948
|
rem: inputs.rem.toString()
|
|
1830
1949
|
};
|
|
1831
|
-
const { proof, publicSignals } = await snarkjs.groth16.fullProve(
|
|
1832
|
-
circuitInputs,
|
|
1833
|
-
wasmPath,
|
|
1834
|
-
zkeyPath
|
|
1835
|
-
);
|
|
1950
|
+
const { proof, publicSignals } = await snarkjs.groth16.fullProve(circuitInputs, wasmPath, zkeyPath);
|
|
1836
1951
|
const proofBytes = proofToBytes(proof);
|
|
1837
1952
|
return {
|
|
1838
1953
|
proof,
|
|
@@ -1842,16 +1957,15 @@ async function generateWithdrawRegularProof(inputs, circuitsPath) {
|
|
|
1842
1957
|
// Not used, kept for compatibility
|
|
1843
1958
|
};
|
|
1844
1959
|
}
|
|
1845
|
-
async function generateWithdrawSwapProof(inputs,
|
|
1846
|
-
const isBrowser = typeof window !== "undefined" || typeof globalThis !== "undefined" && globalThis.window;
|
|
1960
|
+
async function generateWithdrawSwapProof(inputs, circuitsPath2) {
|
|
1847
1961
|
let wasmPath;
|
|
1848
1962
|
let zkeyPath;
|
|
1849
|
-
if (
|
|
1850
|
-
wasmPath = `${
|
|
1851
|
-
zkeyPath = `${
|
|
1963
|
+
if (IS_BROWSER) {
|
|
1964
|
+
wasmPath = `${circuitsPath2}/withdraw_swap_js/withdraw_swap.wasm`;
|
|
1965
|
+
zkeyPath = `${circuitsPath2}/withdraw_swap_final.zkey`;
|
|
1852
1966
|
} else {
|
|
1853
|
-
wasmPath = joinPath(
|
|
1854
|
-
zkeyPath = joinPath(
|
|
1967
|
+
wasmPath = joinPath(circuitsPath2, "build", "withdraw_swap_js", "withdraw_swap.wasm");
|
|
1968
|
+
zkeyPath = joinPath(circuitsPath2, "build", "withdraw_swap_final.zkey");
|
|
1855
1969
|
const wasmExists = await fileExists(wasmPath);
|
|
1856
1970
|
const zkeyExists = await fileExists(zkeyPath);
|
|
1857
1971
|
if (!wasmExists) {
|
|
@@ -1900,32 +2014,35 @@ async function generateWithdrawSwapProof(inputs, circuitsPath) {
|
|
|
1900
2014
|
// Not used, kept for compatibility
|
|
1901
2015
|
};
|
|
1902
2016
|
}
|
|
1903
|
-
async function areCircuitsAvailable(
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
return Boolean(circuitsPath && circuitsPath !== "");
|
|
2017
|
+
async function areCircuitsAvailable(circuitsPath2) {
|
|
2018
|
+
if (IS_BROWSER) {
|
|
2019
|
+
return Boolean(circuitsPath2 && circuitsPath2 !== "");
|
|
1907
2020
|
}
|
|
1908
|
-
const wasmPath = joinPath(
|
|
1909
|
-
const zkeyPath = joinPath(
|
|
2021
|
+
const wasmPath = joinPath(circuitsPath2, "build", "withdraw_regular_js", "withdraw_regular.wasm");
|
|
2022
|
+
const zkeyPath = joinPath(circuitsPath2, "build", "withdraw_regular_final.zkey");
|
|
1910
2023
|
const wasmExists = await fileExists(wasmPath);
|
|
1911
2024
|
const zkeyExists = await fileExists(zkeyPath);
|
|
1912
2025
|
return wasmExists && zkeyExists;
|
|
1913
2026
|
}
|
|
1914
2027
|
async function getDefaultCircuitsPath() {
|
|
1915
|
-
|
|
1916
|
-
|
|
2028
|
+
if (IS_BROWSER) {
|
|
2029
|
+
return "/circuits";
|
|
2030
|
+
}
|
|
2031
|
+
if (typeof process === "undefined" || !process.cwd) {
|
|
1917
2032
|
return "/circuits";
|
|
1918
2033
|
}
|
|
1919
|
-
|
|
1920
|
-
|
|
2034
|
+
let nodePath;
|
|
2035
|
+
try {
|
|
2036
|
+
nodePath = eval("require")("path");
|
|
2037
|
+
} catch {
|
|
1921
2038
|
return "/circuits";
|
|
1922
2039
|
}
|
|
1923
2040
|
const possiblePaths = [
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
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")
|
|
1929
2046
|
];
|
|
1930
2047
|
for (const p of possiblePaths) {
|
|
1931
2048
|
if (await areCircuitsAvailable(p)) {
|
|
@@ -1934,6 +2051,90 @@ async function getDefaultCircuitsPath() {
|
|
|
1934
2051
|
}
|
|
1935
2052
|
return possiblePaths[0];
|
|
1936
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
|
+
}
|
|
1937
2138
|
|
|
1938
2139
|
// src/core/CloakSDK.ts
|
|
1939
2140
|
var CLOAK_PROGRAM_ID = new import_web35.PublicKey("c1oak6tetxYnNfvXKFkpn1d98FxtK7B68vBQLYQpWKp");
|
|
@@ -2048,14 +2249,35 @@ var CloakSDK = class {
|
|
|
2048
2249
|
async deposit(connection, amountOrNote, options) {
|
|
2049
2250
|
try {
|
|
2050
2251
|
let note;
|
|
2252
|
+
let isNewNote = false;
|
|
2051
2253
|
if (typeof amountOrNote === "number") {
|
|
2254
|
+
options?.onProgress?.("generating_note", { message: "Generating note with secrets..." });
|
|
2052
2255
|
note = await generateNote(amountOrNote, this.config.network);
|
|
2256
|
+
isNewNote = true;
|
|
2053
2257
|
} else {
|
|
2054
2258
|
note = amountOrNote;
|
|
2055
2259
|
if (note.depositSignature) {
|
|
2056
2260
|
throw new Error("Note has already been deposited");
|
|
2057
2261
|
}
|
|
2058
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
|
+
}
|
|
2059
2281
|
const payerPubkey = this.getPublicKey();
|
|
2060
2282
|
const balance = await connection.getBalance(payerPubkey);
|
|
2061
2283
|
const requiredAmount = note.amount + 5e3;
|
|
@@ -2247,6 +2469,16 @@ var CloakSDK = class {
|
|
|
2247
2469
|
pathIndices: merkleProof.pathIndices
|
|
2248
2470
|
}
|
|
2249
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
|
+
});
|
|
2250
2482
|
return {
|
|
2251
2483
|
note: updatedNote,
|
|
2252
2484
|
signature,
|
|
@@ -2308,17 +2540,10 @@ var CloakSDK = class {
|
|
|
2308
2540
|
const feeBps = Math.ceil(protocolFee * 1e4 / note.amount);
|
|
2309
2541
|
const distributableAmount = getDistributableAmount2(note.amount);
|
|
2310
2542
|
validateTransfers(recipients, distributableAmount);
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
if (
|
|
2314
|
-
|
|
2315
|
-
pathElements: note.merkleProof.pathElements,
|
|
2316
|
-
pathIndices: note.merkleProof.pathIndices
|
|
2317
|
-
};
|
|
2318
|
-
merkleRoot = note.root;
|
|
2319
|
-
} else {
|
|
2320
|
-
merkleProof = await this.indexer.getMerkleProof(note.leafIndex);
|
|
2321
|
-
merkleRoot = merkleProof.root || (await this.indexer.getMerkleRoot()).root;
|
|
2543
|
+
const merkleProof = await this.indexer.getMerkleProof(note.leafIndex);
|
|
2544
|
+
const merkleRoot = merkleProof.root || (await this.indexer.getMerkleRoot()).root;
|
|
2545
|
+
if (!merkleRoot) {
|
|
2546
|
+
throw new Error("Failed to get Merkle root from indexer");
|
|
2322
2547
|
}
|
|
2323
2548
|
const nullifier = await computeNullifierAsync(note.sk_spend, note.leafIndex);
|
|
2324
2549
|
const nullifierHex = nullifier.toString(16).padStart(64, "0");
|
|
@@ -2355,8 +2580,8 @@ var CloakSDK = class {
|
|
|
2355
2580
|
}
|
|
2356
2581
|
}
|
|
2357
2582
|
const isBrowser = typeof window !== "undefined" || typeof globalThis !== "undefined" && globalThis.window;
|
|
2358
|
-
const
|
|
2359
|
-
const useDirectProof = await areCircuitsAvailable(
|
|
2583
|
+
const circuitsPath2 = isBrowser ? "/circuits" : typeof process !== "undefined" && process.env?.CIRCUITS_PATH || await getDefaultCircuitsPath();
|
|
2584
|
+
const useDirectProof = await areCircuitsAvailable(circuitsPath2);
|
|
2360
2585
|
let proofHex;
|
|
2361
2586
|
let finalPublicInputs;
|
|
2362
2587
|
if (useDirectProof) {
|
|
@@ -2392,6 +2617,13 @@ var CloakSDK = class {
|
|
|
2392
2617
|
}
|
|
2393
2618
|
const sk = splitTo2Limbs(sk_spend_bigint);
|
|
2394
2619
|
const r = splitTo2Limbs(r_bigint);
|
|
2620
|
+
const computedCommitment = await computeCommitment(amount_bigint, r_bigint, sk_spend_bigint);
|
|
2621
|
+
const noteCommitment = BigInt("0x" + note.commitment);
|
|
2622
|
+
if (computedCommitment !== noteCommitment) {
|
|
2623
|
+
throw new Error(
|
|
2624
|
+
`Commitment mismatch! Computed: ${computedCommitment.toString(16)}, Note: ${note.commitment}. This means the note's sk_spend, r, or amount doesn't match what was deposited.`
|
|
2625
|
+
);
|
|
2626
|
+
}
|
|
2395
2627
|
const proofInputs = {
|
|
2396
2628
|
root: root_bigint,
|
|
2397
2629
|
nullifier: nullifier_bigint,
|
|
@@ -2410,7 +2642,9 @@ var CloakSDK = class {
|
|
|
2410
2642
|
var_fee: varFee,
|
|
2411
2643
|
rem
|
|
2412
2644
|
};
|
|
2413
|
-
|
|
2645
|
+
options?.onProgress?.("proof_generating");
|
|
2646
|
+
const proofResult = await generateWithdrawRegularProof(proofInputs, circuitsPath2);
|
|
2647
|
+
options?.onProgress?.("proof_complete");
|
|
2414
2648
|
proofHex = Buffer.from(proofResult.proofBytes).toString("hex");
|
|
2415
2649
|
finalPublicInputs = {
|
|
2416
2650
|
root: merkleRoot,
|
|
@@ -2420,7 +2654,7 @@ var CloakSDK = class {
|
|
|
2420
2654
|
};
|
|
2421
2655
|
} else {
|
|
2422
2656
|
throw new Error(
|
|
2423
|
-
`Circuits not available at ${
|
|
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.`
|
|
2424
2658
|
);
|
|
2425
2659
|
}
|
|
2426
2660
|
const signature = await this.relay.submitWithdraw(
|
|
@@ -2576,30 +2810,27 @@ var CloakSDK = class {
|
|
|
2576
2810
|
);
|
|
2577
2811
|
}
|
|
2578
2812
|
let recipientAta;
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2813
|
+
if (options.recipientAta) {
|
|
2814
|
+
recipientAta = new import_web35.PublicKey(options.recipientAta);
|
|
2815
|
+
} else {
|
|
2816
|
+
try {
|
|
2817
|
+
const splTokenModule = await import("@solana/spl-token");
|
|
2818
|
+
const getAssociatedTokenAddress = splTokenModule.getAssociatedTokenAddress;
|
|
2819
|
+
if (!getAssociatedTokenAddress) {
|
|
2820
|
+
throw new Error("getAssociatedTokenAddress not found");
|
|
2821
|
+
}
|
|
2822
|
+
const outputMint2 = new import_web35.PublicKey(options.outputMint);
|
|
2823
|
+
recipientAta = await getAssociatedTokenAddress(outputMint2, recipient);
|
|
2824
|
+
} catch (error) {
|
|
2825
|
+
throw new Error(
|
|
2826
|
+
`Failed to get associated token account: ${error instanceof Error ? error.message : String(error)}. Please install @solana/spl-token or provide recipientAta in options.`
|
|
2827
|
+
);
|
|
2584
2828
|
}
|
|
2585
|
-
const outputMint2 = new import_web35.PublicKey(options.outputMint);
|
|
2586
|
-
recipientAta = await getAssociatedTokenAddress(outputMint2, recipient);
|
|
2587
|
-
} catch (error) {
|
|
2588
|
-
throw new Error(
|
|
2589
|
-
`Failed to get associated token account: ${error instanceof Error ? error.message : String(error)}. Please install @solana/spl-token or provide recipientAta in options.`
|
|
2590
|
-
);
|
|
2591
2829
|
}
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
if (
|
|
2595
|
-
|
|
2596
|
-
pathElements: note.merkleProof.pathElements,
|
|
2597
|
-
pathIndices: note.merkleProof.pathIndices
|
|
2598
|
-
};
|
|
2599
|
-
merkleRoot = note.root;
|
|
2600
|
-
} else {
|
|
2601
|
-
merkleProof = await this.indexer.getMerkleProof(note.leafIndex);
|
|
2602
|
-
merkleRoot = merkleProof.root || (await this.indexer.getMerkleRoot()).root;
|
|
2830
|
+
const merkleProof = await this.indexer.getMerkleProof(note.leafIndex);
|
|
2831
|
+
const merkleRoot = merkleProof.root || (await this.indexer.getMerkleRoot()).root;
|
|
2832
|
+
if (!merkleRoot) {
|
|
2833
|
+
throw new Error("Failed to get Merkle root from indexer");
|
|
2603
2834
|
}
|
|
2604
2835
|
const nullifier = await computeNullifierAsync(note.sk_spend, note.leafIndex);
|
|
2605
2836
|
const nullifierHex = nullifier.toString(16).padStart(64, "0");
|
|
@@ -2620,8 +2851,8 @@ var CloakSDK = class {
|
|
|
2620
2851
|
throw new Error("Merkle proof is invalid: missing path elements");
|
|
2621
2852
|
}
|
|
2622
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;
|
|
2623
|
-
const
|
|
2624
|
-
const useDirectProof = await areCircuitsAvailable(
|
|
2854
|
+
const circuitsPath2 = envCircuitsPath || await getDefaultCircuitsPath();
|
|
2855
|
+
const useDirectProof = await areCircuitsAvailable(circuitsPath2);
|
|
2625
2856
|
let proofHex;
|
|
2626
2857
|
let finalPublicInputs;
|
|
2627
2858
|
if (useDirectProof) {
|
|
@@ -2635,6 +2866,13 @@ var CloakSDK = class {
|
|
|
2635
2866
|
const inputMintLimbs = pubkeyToLimbs(inputMint.toBytes());
|
|
2636
2867
|
const outputMintLimbs = pubkeyToLimbs(outputMint.toBytes());
|
|
2637
2868
|
const recipientAtaLimbs = pubkeyToLimbs(recipientAta.toBytes());
|
|
2869
|
+
const computedCommitment = await computeCommitment(amount_bigint, r_bigint, sk_spend_bigint);
|
|
2870
|
+
const noteCommitment = BigInt("0x" + note.commitment);
|
|
2871
|
+
if (computedCommitment !== noteCommitment) {
|
|
2872
|
+
throw new Error(
|
|
2873
|
+
`Commitment mismatch! Computed: ${computedCommitment.toString(16)}, Note: ${note.commitment}. This means the note's sk_spend, r, or amount doesn't match what was deposited.`
|
|
2874
|
+
);
|
|
2875
|
+
}
|
|
2638
2876
|
const t = amount_bigint * 5n;
|
|
2639
2877
|
const varFee = t / 1000n;
|
|
2640
2878
|
const rem = t % 1000n;
|
|
@@ -2656,7 +2894,9 @@ var CloakSDK = class {
|
|
|
2656
2894
|
var_fee: varFee,
|
|
2657
2895
|
rem
|
|
2658
2896
|
};
|
|
2659
|
-
|
|
2897
|
+
options?.onProgress?.("proof_generating");
|
|
2898
|
+
const proofResult = await generateWithdrawSwapProof(proofInputs, circuitsPath2);
|
|
2899
|
+
options?.onProgress?.("proof_complete");
|
|
2660
2900
|
proofHex = Buffer.from(proofResult.proofBytes).toString("hex");
|
|
2661
2901
|
finalPublicInputs = {
|
|
2662
2902
|
root: merkleRoot,
|
|
@@ -2666,7 +2906,7 @@ var CloakSDK = class {
|
|
|
2666
2906
|
};
|
|
2667
2907
|
} else {
|
|
2668
2908
|
throw new Error(
|
|
2669
|
-
`Circuits not available at ${
|
|
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.`
|
|
2670
2910
|
);
|
|
2671
2911
|
}
|
|
2672
2912
|
const signature = await this.relay.submitSwap(
|
|
@@ -3041,6 +3281,63 @@ async function copyNoteToClipboard(note) {
|
|
|
3041
3281
|
}
|
|
3042
3282
|
|
|
3043
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
|
+
};
|
|
3044
3341
|
var PROGRAM_ERRORS = {
|
|
3045
3342
|
// Nullifier errors
|
|
3046
3343
|
"NullifierAlreadyUsed": "This note has already been withdrawn. Each note can only be spent once.",
|
|
@@ -3069,6 +3366,438 @@ var PROGRAM_ERRORS = {
|
|
|
3069
3366
|
"AccountNotFound": "Required account not found.",
|
|
3070
3367
|
"InvalidInstruction": "Invalid instruction data."
|
|
3071
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
|
+
}
|
|
3072
3801
|
function parseTransactionError(error) {
|
|
3073
3802
|
if (!error) return "An unknown error occurred";
|
|
3074
3803
|
const errorStr = typeof error === "string" ? error : error.message || error.toString();
|
|
@@ -3176,188 +3905,6 @@ function formatErrorForLogging(error) {
|
|
|
3176
3905
|
return String(error);
|
|
3177
3906
|
}
|
|
3178
3907
|
|
|
3179
|
-
// src/services/ProverService.ts
|
|
3180
|
-
var ProverService = class {
|
|
3181
|
-
/**
|
|
3182
|
-
* Create a new Prover Service client
|
|
3183
|
-
*
|
|
3184
|
-
* @param indexerUrl - Indexer/Prover service base URL
|
|
3185
|
-
* @param timeout - Proof generation timeout in ms (default: 5 minutes)
|
|
3186
|
-
*/
|
|
3187
|
-
constructor(indexerUrl, timeout = 5 * 60 * 1e3) {
|
|
3188
|
-
this.indexerUrl = indexerUrl.replace(/\/$/, "");
|
|
3189
|
-
this.timeout = timeout;
|
|
3190
|
-
}
|
|
3191
|
-
/**
|
|
3192
|
-
* Generate a zero-knowledge proof for withdrawal
|
|
3193
|
-
*
|
|
3194
|
-
* This process typically takes 30-180 seconds depending on the backend.
|
|
3195
|
-
*
|
|
3196
|
-
* @param inputs - Circuit inputs (private + public + outputs)
|
|
3197
|
-
* @param options - Optional progress tracking and callbacks
|
|
3198
|
-
* @returns Proof result with hex-encoded proof and public inputs
|
|
3199
|
-
*
|
|
3200
|
-
* @example
|
|
3201
|
-
* ```typescript
|
|
3202
|
-
* const result = await prover.generateProof(inputs);
|
|
3203
|
-
* if (result.success) {
|
|
3204
|
-
* console.log(`Proof: ${result.proof}`);
|
|
3205
|
-
* }
|
|
3206
|
-
* ```
|
|
3207
|
-
*
|
|
3208
|
-
* @example
|
|
3209
|
-
* ```typescript
|
|
3210
|
-
* // With progress tracking
|
|
3211
|
-
* const result = await prover.generateProof(inputs, {
|
|
3212
|
-
* onProgress: (progress) => console.log(`Progress: ${progress}%`),
|
|
3213
|
-
* onStart: () => console.log("Starting proof generation..."),
|
|
3214
|
-
* onSuccess: (result) => console.log("Proof generated!"),
|
|
3215
|
-
* onError: (error) => console.error("Failed:", error)
|
|
3216
|
-
* });
|
|
3217
|
-
* ```
|
|
3218
|
-
*/
|
|
3219
|
-
async generateProof(inputs, options) {
|
|
3220
|
-
const startTime = Date.now();
|
|
3221
|
-
const actualTimeout = options?.timeout || this.timeout;
|
|
3222
|
-
options?.onStart?.();
|
|
3223
|
-
let progressInterval;
|
|
3224
|
-
try {
|
|
3225
|
-
const requestBody = {
|
|
3226
|
-
private_inputs: JSON.stringify(inputs.privateInputs),
|
|
3227
|
-
public_inputs: JSON.stringify(inputs.publicInputs),
|
|
3228
|
-
outputs: JSON.stringify(inputs.outputs)
|
|
3229
|
-
};
|
|
3230
|
-
if (inputs.swapParams) {
|
|
3231
|
-
requestBody.swap_params = inputs.swapParams;
|
|
3232
|
-
}
|
|
3233
|
-
const controller = new AbortController();
|
|
3234
|
-
const timeoutId = setTimeout(() => controller.abort(), actualTimeout);
|
|
3235
|
-
if (options?.onProgress) {
|
|
3236
|
-
let progress = 0;
|
|
3237
|
-
progressInterval = setInterval(() => {
|
|
3238
|
-
progress = Math.min(90, progress + Math.random() * 10);
|
|
3239
|
-
options.onProgress(Math.floor(progress));
|
|
3240
|
-
}, 2e3);
|
|
3241
|
-
}
|
|
3242
|
-
const response = await fetch(`${this.indexerUrl}/api/v1/prove`, {
|
|
3243
|
-
method: "POST",
|
|
3244
|
-
headers: {
|
|
3245
|
-
"Content-Type": "application/json"
|
|
3246
|
-
},
|
|
3247
|
-
body: JSON.stringify(requestBody),
|
|
3248
|
-
signal: controller.signal
|
|
3249
|
-
});
|
|
3250
|
-
clearTimeout(timeoutId);
|
|
3251
|
-
if (progressInterval) clearInterval(progressInterval);
|
|
3252
|
-
if (!response.ok) {
|
|
3253
|
-
let errorMessage = `${response.status} ${response.statusText}`;
|
|
3254
|
-
try {
|
|
3255
|
-
const errorText = await response.text();
|
|
3256
|
-
try {
|
|
3257
|
-
const errorJson = JSON.parse(errorText);
|
|
3258
|
-
errorMessage = errorJson.error || errorJson.message || errorText;
|
|
3259
|
-
} catch {
|
|
3260
|
-
errorMessage = errorText || errorMessage;
|
|
3261
|
-
}
|
|
3262
|
-
} catch {
|
|
3263
|
-
}
|
|
3264
|
-
options?.onError?.(errorMessage);
|
|
3265
|
-
return {
|
|
3266
|
-
success: false,
|
|
3267
|
-
generationTimeMs: Date.now() - startTime,
|
|
3268
|
-
error: errorMessage
|
|
3269
|
-
};
|
|
3270
|
-
}
|
|
3271
|
-
options?.onProgress?.(100);
|
|
3272
|
-
const rawData = await response.json();
|
|
3273
|
-
const result = {
|
|
3274
|
-
success: rawData.success,
|
|
3275
|
-
proof: rawData.proof,
|
|
3276
|
-
publicInputs: rawData.public_inputs,
|
|
3277
|
-
// Map snake_case
|
|
3278
|
-
generationTimeMs: rawData.generation_time_ms || Date.now() - startTime,
|
|
3279
|
-
error: rawData.error
|
|
3280
|
-
};
|
|
3281
|
-
if (!result.success && rawData.execution_report) {
|
|
3282
|
-
}
|
|
3283
|
-
if (!result.success && result.error) {
|
|
3284
|
-
try {
|
|
3285
|
-
const errorObj = typeof result.error === "string" ? JSON.parse(result.error) : result.error;
|
|
3286
|
-
if (errorObj?.error && typeof errorObj.error === "string") {
|
|
3287
|
-
result.error = errorObj.error;
|
|
3288
|
-
} else if (typeof errorObj === "string") {
|
|
3289
|
-
result.error = errorObj;
|
|
3290
|
-
}
|
|
3291
|
-
if (errorObj?.execution_report && typeof errorObj.execution_report === "string") {
|
|
3292
|
-
result.error += `
|
|
3293
|
-
Execution report: ${errorObj.execution_report}`;
|
|
3294
|
-
}
|
|
3295
|
-
if (errorObj?.total_cycles !== void 0) {
|
|
3296
|
-
result.error += `
|
|
3297
|
-
Total cycles: ${errorObj.total_cycles}`;
|
|
3298
|
-
}
|
|
3299
|
-
if (errorObj?.total_syscalls !== void 0) {
|
|
3300
|
-
result.error += `
|
|
3301
|
-
Total syscalls: ${errorObj.total_syscalls}`;
|
|
3302
|
-
}
|
|
3303
|
-
} catch {
|
|
3304
|
-
}
|
|
3305
|
-
}
|
|
3306
|
-
if (result.success) {
|
|
3307
|
-
options?.onSuccess?.(result);
|
|
3308
|
-
} else if (result.error) {
|
|
3309
|
-
options?.onError?.(result.error);
|
|
3310
|
-
}
|
|
3311
|
-
return result;
|
|
3312
|
-
} catch (error) {
|
|
3313
|
-
const totalTime = Date.now() - startTime;
|
|
3314
|
-
if (progressInterval) clearInterval(progressInterval);
|
|
3315
|
-
let errorMessage;
|
|
3316
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
3317
|
-
errorMessage = `Proof generation timed out after ${actualTimeout}ms`;
|
|
3318
|
-
} else {
|
|
3319
|
-
errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
3320
|
-
}
|
|
3321
|
-
options?.onError?.(errorMessage);
|
|
3322
|
-
return {
|
|
3323
|
-
success: false,
|
|
3324
|
-
generationTimeMs: totalTime,
|
|
3325
|
-
error: errorMessage
|
|
3326
|
-
};
|
|
3327
|
-
}
|
|
3328
|
-
}
|
|
3329
|
-
/**
|
|
3330
|
-
* Check if the prover service is available
|
|
3331
|
-
*
|
|
3332
|
-
* @returns True if service is healthy
|
|
3333
|
-
*/
|
|
3334
|
-
async healthCheck() {
|
|
3335
|
-
try {
|
|
3336
|
-
const response = await fetch(`${this.indexerUrl}/health`, {
|
|
3337
|
-
method: "GET"
|
|
3338
|
-
});
|
|
3339
|
-
return response.ok;
|
|
3340
|
-
} catch {
|
|
3341
|
-
return false;
|
|
3342
|
-
}
|
|
3343
|
-
}
|
|
3344
|
-
/**
|
|
3345
|
-
* Get the configured timeout
|
|
3346
|
-
*/
|
|
3347
|
-
getTimeout() {
|
|
3348
|
-
return this.timeout;
|
|
3349
|
-
}
|
|
3350
|
-
/**
|
|
3351
|
-
* Set a new timeout
|
|
3352
|
-
*/
|
|
3353
|
-
setTimeout(timeout) {
|
|
3354
|
-
if (timeout <= 0) {
|
|
3355
|
-
throw new Error("Timeout must be positive");
|
|
3356
|
-
}
|
|
3357
|
-
this.timeout = timeout;
|
|
3358
|
-
}
|
|
3359
|
-
};
|
|
3360
|
-
|
|
3361
3908
|
// src/helpers/wallet-integration.ts
|
|
3362
3909
|
var import_web36 = require("@solana/web3.js");
|
|
3363
3910
|
function validateWalletConnected(wallet) {
|
|
@@ -3430,6 +3977,173 @@ function keypairToAdapter(keypair) {
|
|
|
3430
3977
|
};
|
|
3431
3978
|
}
|
|
3432
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
|
+
|
|
3433
4147
|
// src/index.ts
|
|
3434
4148
|
var VERSION = "1.0.0";
|
|
3435
4149
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -3438,13 +4152,14 @@ var VERSION = "1.0.0";
|
|
|
3438
4152
|
CloakError,
|
|
3439
4153
|
CloakSDK,
|
|
3440
4154
|
DepositRecoveryService,
|
|
4155
|
+
EXPECTED_CIRCUIT_HASHES,
|
|
3441
4156
|
FIXED_FEE_LAMPORTS,
|
|
3442
4157
|
IndexerService,
|
|
3443
4158
|
LAMPORTS_PER_SOL,
|
|
3444
4159
|
LocalStorageAdapter,
|
|
3445
4160
|
MemoryStorageAdapter,
|
|
3446
|
-
ProverService,
|
|
3447
4161
|
RelayService,
|
|
4162
|
+
ShieldPoolErrors,
|
|
3448
4163
|
VARIABLE_FEE_RATE,
|
|
3449
4164
|
VERSION,
|
|
3450
4165
|
bigintToBytes32,
|
|
@@ -3452,6 +4167,9 @@ var VERSION = "1.0.0";
|
|
|
3452
4167
|
bytesToHex,
|
|
3453
4168
|
calculateFee,
|
|
3454
4169
|
calculateRelayFee,
|
|
4170
|
+
cleanupStalePendingOperations,
|
|
4171
|
+
clearPendingDeposits,
|
|
4172
|
+
clearPendingWithdrawals,
|
|
3455
4173
|
computeCommitment,
|
|
3456
4174
|
computeMerkleRoot,
|
|
3457
4175
|
computeNullifier,
|
|
@@ -3491,12 +4209,14 @@ var VERSION = "1.0.0";
|
|
|
3491
4209
|
getAddressExplorerUrl,
|
|
3492
4210
|
getDistributableAmount,
|
|
3493
4211
|
getExplorerUrl,
|
|
4212
|
+
getPendingOperationsSummary,
|
|
3494
4213
|
getPublicKey,
|
|
3495
4214
|
getPublicViewKey,
|
|
3496
4215
|
getRecipientAmount,
|
|
3497
4216
|
getRpcUrlForNetwork,
|
|
3498
4217
|
getShieldPoolPDAs,
|
|
3499
4218
|
getViewKey,
|
|
4219
|
+
hasPendingOperations,
|
|
3500
4220
|
hexToBigint,
|
|
3501
4221
|
hexToBytes,
|
|
3502
4222
|
importKeys,
|
|
@@ -3506,7 +4226,10 @@ var VERSION = "1.0.0";
|
|
|
3506
4226
|
isValidSolanaAddress,
|
|
3507
4227
|
isWithdrawable,
|
|
3508
4228
|
keypairToAdapter,
|
|
4229
|
+
loadPendingDeposits,
|
|
4230
|
+
loadPendingWithdrawals,
|
|
3509
4231
|
parseAmount,
|
|
4232
|
+
parseError,
|
|
3510
4233
|
parseNote,
|
|
3511
4234
|
parseTransactionError,
|
|
3512
4235
|
poseidonHash,
|
|
@@ -3515,6 +4238,10 @@ var VERSION = "1.0.0";
|
|
|
3515
4238
|
proofToBytes,
|
|
3516
4239
|
pubkeyToLimbs,
|
|
3517
4240
|
randomBytes,
|
|
4241
|
+
removePendingDeposit,
|
|
4242
|
+
removePendingWithdrawal,
|
|
4243
|
+
savePendingDeposit,
|
|
4244
|
+
savePendingWithdrawal,
|
|
3518
4245
|
scanNotesForWallet,
|
|
3519
4246
|
sendTransaction,
|
|
3520
4247
|
serializeNote,
|
|
@@ -3522,10 +4249,14 @@ var VERSION = "1.0.0";
|
|
|
3522
4249
|
splitTo2Limbs,
|
|
3523
4250
|
tryDecryptNote,
|
|
3524
4251
|
updateNoteWithDeposit,
|
|
4252
|
+
updatePendingDeposit,
|
|
4253
|
+
updatePendingWithdrawal,
|
|
3525
4254
|
validateDepositParams,
|
|
3526
4255
|
validateNote,
|
|
3527
4256
|
validateOutputsSum,
|
|
3528
4257
|
validateTransfers,
|
|
3529
4258
|
validateWalletConnected,
|
|
3530
|
-
validateWithdrawableNote
|
|
4259
|
+
validateWithdrawableNote,
|
|
4260
|
+
verifyAllCircuits,
|
|
4261
|
+
verifyCircuitIntegrity
|
|
3531
4262
|
});
|