@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.js
CHANGED
|
@@ -172,14 +172,14 @@ function randomBytes(length) {
|
|
|
172
172
|
} catch {
|
|
173
173
|
}
|
|
174
174
|
try {
|
|
175
|
-
const
|
|
176
|
-
if (
|
|
177
|
-
const buffer =
|
|
175
|
+
const nodeCrypto2 = __require("crypto");
|
|
176
|
+
if (nodeCrypto2?.randomBytes) {
|
|
177
|
+
const buffer = nodeCrypto2.randomBytes(length);
|
|
178
178
|
bytes.set(buffer);
|
|
179
179
|
return bytes;
|
|
180
180
|
}
|
|
181
|
-
if (
|
|
182
|
-
|
|
181
|
+
if (nodeCrypto2?.webcrypto?.getRandomValues) {
|
|
182
|
+
nodeCrypto2.webcrypto.getRandomValues(bytes);
|
|
183
183
|
return bytes;
|
|
184
184
|
}
|
|
185
185
|
} catch {
|
|
@@ -301,8 +301,8 @@ function generateMasterSeed() {
|
|
|
301
301
|
cryptoObj.getRandomValues(seed);
|
|
302
302
|
} else {
|
|
303
303
|
try {
|
|
304
|
-
const
|
|
305
|
-
const buffer =
|
|
304
|
+
const nodeCrypto2 = __require("crypto");
|
|
305
|
+
const buffer = nodeCrypto2.randomBytes(32);
|
|
306
306
|
seed.set(buffer);
|
|
307
307
|
} catch {
|
|
308
308
|
throw new Error("No secure random number generator available");
|
|
@@ -1043,6 +1043,8 @@ var RelayService = class {
|
|
|
1043
1043
|
*
|
|
1044
1044
|
* @param params - Withdrawal parameters
|
|
1045
1045
|
* @param onStatusUpdate - Optional callback for status updates
|
|
1046
|
+
* @param onRequestId - CRITICAL: Callback when request_id is received.
|
|
1047
|
+
* Persist this ID to recover if browser crashes during polling.
|
|
1046
1048
|
* @returns Transaction signature when completed
|
|
1047
1049
|
*
|
|
1048
1050
|
* @example
|
|
@@ -1052,11 +1054,12 @@ var RelayService = class {
|
|
|
1052
1054
|
* publicInputs: { root, nf, outputs_hash, amount },
|
|
1053
1055
|
* outputs: [{ recipient: addr, amount: lamports }],
|
|
1054
1056
|
* feeBps: 50
|
|
1055
|
-
* }, (status) => console.log(`Status: ${status}`)
|
|
1057
|
+
* }, (status) => console.log(`Status: ${status}`),
|
|
1058
|
+
* (requestId) => localStorage.setItem('pending_withdraw', requestId));
|
|
1056
1059
|
* console.log(`Transaction: ${signature}`);
|
|
1057
1060
|
* ```
|
|
1058
1061
|
*/
|
|
1059
|
-
async submitWithdraw(params, onStatusUpdate) {
|
|
1062
|
+
async submitWithdraw(params, onStatusUpdate, onRequestId) {
|
|
1060
1063
|
const proofBytes = hexToBytes(params.proof);
|
|
1061
1064
|
const proofBase64 = this.bytesToBase64(proofBytes);
|
|
1062
1065
|
const requestBody = {
|
|
@@ -1097,8 +1100,55 @@ var RelayService = class {
|
|
|
1097
1100
|
if (!requestId) {
|
|
1098
1101
|
throw new Error("Relay response missing request_id");
|
|
1099
1102
|
}
|
|
1103
|
+
if (onRequestId) {
|
|
1104
|
+
onRequestId(requestId);
|
|
1105
|
+
}
|
|
1100
1106
|
return this.pollForCompletion(requestId, onStatusUpdate);
|
|
1101
1107
|
}
|
|
1108
|
+
/**
|
|
1109
|
+
* Resume polling for a withdrawal that was previously started
|
|
1110
|
+
*
|
|
1111
|
+
* Use this after page reload to check status of a pending withdrawal.
|
|
1112
|
+
* The requestId should have been persisted via the onRequestId callback.
|
|
1113
|
+
*
|
|
1114
|
+
* @param requestId - Request ID from a previous submitWithdraw call
|
|
1115
|
+
* @param onStatusUpdate - Optional callback for status updates
|
|
1116
|
+
* @returns Transaction signature if completed, null if still pending/failed
|
|
1117
|
+
*
|
|
1118
|
+
* @example
|
|
1119
|
+
* ```typescript
|
|
1120
|
+
* // On page load, check for pending withdrawal
|
|
1121
|
+
* const pendingId = localStorage.getItem('pending_withdraw');
|
|
1122
|
+
* if (pendingId) {
|
|
1123
|
+
* const result = await relay.resumeWithdraw(pendingId);
|
|
1124
|
+
* if (result.status === 'completed') {
|
|
1125
|
+
* console.log('Withdrawal completed:', result.signature);
|
|
1126
|
+
* localStorage.removeItem('pending_withdraw');
|
|
1127
|
+
* }
|
|
1128
|
+
* }
|
|
1129
|
+
* ```
|
|
1130
|
+
*/
|
|
1131
|
+
async resumeWithdraw(requestId, onStatusUpdate) {
|
|
1132
|
+
try {
|
|
1133
|
+
const status = await this.getStatus(requestId);
|
|
1134
|
+
if (onStatusUpdate) {
|
|
1135
|
+
onStatusUpdate(status.status);
|
|
1136
|
+
}
|
|
1137
|
+
if (status.status === "completed") {
|
|
1138
|
+
return { status: "completed", signature: status.txId };
|
|
1139
|
+
} else if (status.status === "failed") {
|
|
1140
|
+
return { status: "failed", error: status.error };
|
|
1141
|
+
} else {
|
|
1142
|
+
const signature = await this.pollForCompletion(requestId, onStatusUpdate);
|
|
1143
|
+
return { status: "completed", signature };
|
|
1144
|
+
}
|
|
1145
|
+
} catch (error) {
|
|
1146
|
+
return {
|
|
1147
|
+
status: "failed",
|
|
1148
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1102
1152
|
/**
|
|
1103
1153
|
* Poll for withdrawal completion
|
|
1104
1154
|
*
|
|
@@ -1171,7 +1221,7 @@ var RelayService = class {
|
|
|
1171
1221
|
* console.log(`Transaction: ${signature}`);
|
|
1172
1222
|
* ```
|
|
1173
1223
|
*/
|
|
1174
|
-
async submitSwap(params, onStatusUpdate) {
|
|
1224
|
+
async submitSwap(params, onStatusUpdate, onRequestId) {
|
|
1175
1225
|
const proofBytes = hexToBytes(params.proof);
|
|
1176
1226
|
const proofBase64 = this.bytesToBase64(proofBytes);
|
|
1177
1227
|
const requestBody = {
|
|
@@ -1213,6 +1263,9 @@ var RelayService = class {
|
|
|
1213
1263
|
if (!requestId) {
|
|
1214
1264
|
throw new Error("Relay response missing request_id");
|
|
1215
1265
|
}
|
|
1266
|
+
if (onRequestId) {
|
|
1267
|
+
onRequestId(requestId);
|
|
1268
|
+
}
|
|
1216
1269
|
return this.pollForCompletion(requestId, onStatusUpdate);
|
|
1217
1270
|
}
|
|
1218
1271
|
/**
|
|
@@ -1449,29 +1502,94 @@ var DepositRecoveryService = class {
|
|
|
1449
1502
|
}
|
|
1450
1503
|
/**
|
|
1451
1504
|
* Check if a deposit already exists in the indexer
|
|
1505
|
+
* Uses the enhanced deposit lookup endpoint with include_proof=true
|
|
1452
1506
|
*
|
|
1453
1507
|
* @private
|
|
1454
1508
|
*/
|
|
1455
|
-
async checkExistingDeposit(
|
|
1509
|
+
async checkExistingDeposit(commitment) {
|
|
1456
1510
|
try {
|
|
1457
|
-
const
|
|
1458
|
-
const
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
return null;
|
|
1465
|
-
} catch (e) {
|
|
1466
|
-
continue;
|
|
1467
|
-
}
|
|
1511
|
+
const cleanCommit = commitment.replace(/^0x/, "").toLowerCase();
|
|
1512
|
+
const response = await fetch(
|
|
1513
|
+
`${this.apiUrl}/api/v1/deposit/${cleanCommit}?include_proof=true`
|
|
1514
|
+
);
|
|
1515
|
+
if (!response.ok) {
|
|
1516
|
+
if (response.status === 404) {
|
|
1517
|
+
return null;
|
|
1468
1518
|
}
|
|
1519
|
+
throw new Error(`Failed to check deposit: ${response.status}`);
|
|
1469
1520
|
}
|
|
1470
|
-
|
|
1521
|
+
const data = await response.json();
|
|
1522
|
+
if (!data.merkle_proof) {
|
|
1523
|
+
const merkleProof = await this.indexer.getMerkleProof(data.leaf_index);
|
|
1524
|
+
return {
|
|
1525
|
+
leafIndex: data.leaf_index,
|
|
1526
|
+
root: merkleProof.root || "",
|
|
1527
|
+
slot: data.slot,
|
|
1528
|
+
merkleProof: {
|
|
1529
|
+
pathElements: merkleProof.pathElements,
|
|
1530
|
+
pathIndices: merkleProof.pathIndices
|
|
1531
|
+
}
|
|
1532
|
+
};
|
|
1533
|
+
}
|
|
1534
|
+
return {
|
|
1535
|
+
leafIndex: data.leaf_index,
|
|
1536
|
+
root: data.merkle_proof.root,
|
|
1537
|
+
slot: data.slot,
|
|
1538
|
+
merkleProof: {
|
|
1539
|
+
pathElements: data.merkle_proof.path_elements,
|
|
1540
|
+
pathIndices: data.merkle_proof.path_indices
|
|
1541
|
+
}
|
|
1542
|
+
};
|
|
1471
1543
|
} catch (error) {
|
|
1472
1544
|
return null;
|
|
1473
1545
|
}
|
|
1474
1546
|
}
|
|
1547
|
+
/**
|
|
1548
|
+
* Recover deposit by transaction signature
|
|
1549
|
+
* Uses the indexer's signature lookup endpoint
|
|
1550
|
+
*/
|
|
1551
|
+
async recoverBySignature(signature) {
|
|
1552
|
+
try {
|
|
1553
|
+
const response = await fetch(
|
|
1554
|
+
`${this.apiUrl}/api/v1/deposit/tx/${signature}?include_proof=true`
|
|
1555
|
+
);
|
|
1556
|
+
if (!response.ok) {
|
|
1557
|
+
if (response.status === 404) {
|
|
1558
|
+
return {
|
|
1559
|
+
success: false,
|
|
1560
|
+
error: "Deposit not found for this transaction signature"
|
|
1561
|
+
};
|
|
1562
|
+
}
|
|
1563
|
+
const errorText = await response.text();
|
|
1564
|
+
return {
|
|
1565
|
+
success: false,
|
|
1566
|
+
error: `Failed to recover deposit: ${errorText}`
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
const data = await response.json();
|
|
1570
|
+
if (!data.merkle_proof) {
|
|
1571
|
+
return {
|
|
1572
|
+
success: false,
|
|
1573
|
+
error: "Deposit found but merkle proof not available"
|
|
1574
|
+
};
|
|
1575
|
+
}
|
|
1576
|
+
return {
|
|
1577
|
+
success: true,
|
|
1578
|
+
leafIndex: data.leaf_index,
|
|
1579
|
+
root: data.merkle_proof.root,
|
|
1580
|
+
slot: data.slot,
|
|
1581
|
+
merkleProof: {
|
|
1582
|
+
pathElements: data.merkle_proof.path_elements,
|
|
1583
|
+
pathIndices: data.merkle_proof.path_indices
|
|
1584
|
+
}
|
|
1585
|
+
};
|
|
1586
|
+
} catch (error) {
|
|
1587
|
+
return {
|
|
1588
|
+
success: false,
|
|
1589
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1590
|
+
};
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1475
1593
|
/**
|
|
1476
1594
|
* Finalize a deposit via server API (alternative recovery method)
|
|
1477
1595
|
*
|
|
@@ -1631,39 +1749,15 @@ function getShieldPoolPDAs(programId, mint) {
|
|
|
1631
1749
|
|
|
1632
1750
|
// src/utils/proof-generation.ts
|
|
1633
1751
|
import * as snarkjs from "snarkjs";
|
|
1634
|
-
var
|
|
1635
|
-
var fs = null;
|
|
1636
|
-
async function loadNodeModules() {
|
|
1637
|
-
const isBrowser = typeof window !== "undefined" || typeof globalThis !== "undefined" && globalThis.window;
|
|
1638
|
-
if (!isBrowser && typeof process !== "undefined" && process.versions?.node) {
|
|
1639
|
-
if (!path) {
|
|
1640
|
-
path = await import("path");
|
|
1641
|
-
}
|
|
1642
|
-
if (!fs) {
|
|
1643
|
-
fs = await import("fs");
|
|
1644
|
-
}
|
|
1645
|
-
return { path, fs };
|
|
1646
|
-
}
|
|
1647
|
-
return { path: null, fs: null };
|
|
1648
|
-
}
|
|
1752
|
+
var IS_BROWSER = typeof window !== "undefined" || typeof globalThis !== "undefined" && typeof globalThis.document !== "undefined";
|
|
1649
1753
|
function joinPath(...parts) {
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
return path.join(...parts);
|
|
1754
|
+
if (IS_BROWSER) {
|
|
1755
|
+
return parts.join("/").replace(/\/+/g, "/");
|
|
1653
1756
|
}
|
|
1654
1757
|
return parts.join("/").replace(/\/+/g, "/");
|
|
1655
1758
|
}
|
|
1656
1759
|
async function fileExists(filePath) {
|
|
1657
|
-
|
|
1658
|
-
if (fs2) {
|
|
1659
|
-
try {
|
|
1660
|
-
return fs2.existsSync(filePath);
|
|
1661
|
-
} catch {
|
|
1662
|
-
return false;
|
|
1663
|
-
}
|
|
1664
|
-
}
|
|
1665
|
-
const isBrowser = typeof window !== "undefined" || typeof globalThis !== "undefined" && globalThis.window;
|
|
1666
|
-
if (isBrowser) {
|
|
1760
|
+
if (IS_BROWSER) {
|
|
1667
1761
|
try {
|
|
1668
1762
|
const response = await fetch(filePath, { method: "HEAD" });
|
|
1669
1763
|
return response.ok;
|
|
@@ -1671,18 +1765,26 @@ async function fileExists(filePath) {
|
|
|
1671
1765
|
return false;
|
|
1672
1766
|
}
|
|
1673
1767
|
}
|
|
1674
|
-
|
|
1768
|
+
try {
|
|
1769
|
+
const nodeFs2 = globalThis.require?.("fs") || (typeof __require !== "undefined" ? __require("fs") : null);
|
|
1770
|
+
if (nodeFs2) {
|
|
1771
|
+
return nodeFs2.existsSync(filePath);
|
|
1772
|
+
}
|
|
1773
|
+
const fsModule = await import("fs");
|
|
1774
|
+
return fsModule.existsSync(filePath);
|
|
1775
|
+
} catch {
|
|
1776
|
+
return false;
|
|
1777
|
+
}
|
|
1675
1778
|
}
|
|
1676
|
-
async function generateWithdrawRegularProof(inputs,
|
|
1677
|
-
const isBrowser = typeof window !== "undefined" || typeof globalThis !== "undefined" && globalThis.window;
|
|
1779
|
+
async function generateWithdrawRegularProof(inputs, circuitsPath2) {
|
|
1678
1780
|
let wasmPath;
|
|
1679
1781
|
let zkeyPath;
|
|
1680
|
-
if (
|
|
1681
|
-
wasmPath = `${
|
|
1682
|
-
zkeyPath = `${
|
|
1782
|
+
if (IS_BROWSER) {
|
|
1783
|
+
wasmPath = `${circuitsPath2}/withdraw_regular_js/withdraw_regular.wasm`;
|
|
1784
|
+
zkeyPath = `${circuitsPath2}/withdraw_regular_final.zkey`;
|
|
1683
1785
|
} else {
|
|
1684
|
-
wasmPath = joinPath(
|
|
1685
|
-
zkeyPath = joinPath(
|
|
1786
|
+
wasmPath = joinPath(circuitsPath2, "build", "withdraw_regular_js", "withdraw_regular.wasm");
|
|
1787
|
+
zkeyPath = joinPath(circuitsPath2, "build", "withdraw_regular_final.zkey");
|
|
1686
1788
|
const wasmExists = await fileExists(wasmPath);
|
|
1687
1789
|
const zkeyExists = await fileExists(zkeyPath);
|
|
1688
1790
|
if (!wasmExists) {
|
|
@@ -1714,11 +1816,7 @@ async function generateWithdrawRegularProof(inputs, circuitsPath) {
|
|
|
1714
1816
|
var_fee: inputs.var_fee.toString(),
|
|
1715
1817
|
rem: inputs.rem.toString()
|
|
1716
1818
|
};
|
|
1717
|
-
const { proof, publicSignals } = await snarkjs.groth16.fullProve(
|
|
1718
|
-
circuitInputs,
|
|
1719
|
-
wasmPath,
|
|
1720
|
-
zkeyPath
|
|
1721
|
-
);
|
|
1819
|
+
const { proof, publicSignals } = await snarkjs.groth16.fullProve(circuitInputs, wasmPath, zkeyPath);
|
|
1722
1820
|
const proofBytes = proofToBytes(proof);
|
|
1723
1821
|
return {
|
|
1724
1822
|
proof,
|
|
@@ -1728,16 +1826,15 @@ async function generateWithdrawRegularProof(inputs, circuitsPath) {
|
|
|
1728
1826
|
// Not used, kept for compatibility
|
|
1729
1827
|
};
|
|
1730
1828
|
}
|
|
1731
|
-
async function generateWithdrawSwapProof(inputs,
|
|
1732
|
-
const isBrowser = typeof window !== "undefined" || typeof globalThis !== "undefined" && globalThis.window;
|
|
1829
|
+
async function generateWithdrawSwapProof(inputs, circuitsPath2) {
|
|
1733
1830
|
let wasmPath;
|
|
1734
1831
|
let zkeyPath;
|
|
1735
|
-
if (
|
|
1736
|
-
wasmPath = `${
|
|
1737
|
-
zkeyPath = `${
|
|
1832
|
+
if (IS_BROWSER) {
|
|
1833
|
+
wasmPath = `${circuitsPath2}/withdraw_swap_js/withdraw_swap.wasm`;
|
|
1834
|
+
zkeyPath = `${circuitsPath2}/withdraw_swap_final.zkey`;
|
|
1738
1835
|
} else {
|
|
1739
|
-
wasmPath = joinPath(
|
|
1740
|
-
zkeyPath = joinPath(
|
|
1836
|
+
wasmPath = joinPath(circuitsPath2, "build", "withdraw_swap_js", "withdraw_swap.wasm");
|
|
1837
|
+
zkeyPath = joinPath(circuitsPath2, "build", "withdraw_swap_final.zkey");
|
|
1741
1838
|
const wasmExists = await fileExists(wasmPath);
|
|
1742
1839
|
const zkeyExists = await fileExists(zkeyPath);
|
|
1743
1840
|
if (!wasmExists) {
|
|
@@ -1786,32 +1883,35 @@ async function generateWithdrawSwapProof(inputs, circuitsPath) {
|
|
|
1786
1883
|
// Not used, kept for compatibility
|
|
1787
1884
|
};
|
|
1788
1885
|
}
|
|
1789
|
-
async function areCircuitsAvailable(
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
return Boolean(circuitsPath && circuitsPath !== "");
|
|
1886
|
+
async function areCircuitsAvailable(circuitsPath2) {
|
|
1887
|
+
if (IS_BROWSER) {
|
|
1888
|
+
return Boolean(circuitsPath2 && circuitsPath2 !== "");
|
|
1793
1889
|
}
|
|
1794
|
-
const wasmPath = joinPath(
|
|
1795
|
-
const zkeyPath = joinPath(
|
|
1890
|
+
const wasmPath = joinPath(circuitsPath2, "build", "withdraw_regular_js", "withdraw_regular.wasm");
|
|
1891
|
+
const zkeyPath = joinPath(circuitsPath2, "build", "withdraw_regular_final.zkey");
|
|
1796
1892
|
const wasmExists = await fileExists(wasmPath);
|
|
1797
1893
|
const zkeyExists = await fileExists(zkeyPath);
|
|
1798
1894
|
return wasmExists && zkeyExists;
|
|
1799
1895
|
}
|
|
1800
1896
|
async function getDefaultCircuitsPath() {
|
|
1801
|
-
|
|
1802
|
-
|
|
1897
|
+
if (IS_BROWSER) {
|
|
1898
|
+
return "/circuits";
|
|
1899
|
+
}
|
|
1900
|
+
if (typeof process === "undefined" || !process.cwd) {
|
|
1803
1901
|
return "/circuits";
|
|
1804
1902
|
}
|
|
1805
|
-
|
|
1806
|
-
|
|
1903
|
+
let nodePath;
|
|
1904
|
+
try {
|
|
1905
|
+
nodePath = eval("require")("path");
|
|
1906
|
+
} catch {
|
|
1807
1907
|
return "/circuits";
|
|
1808
1908
|
}
|
|
1809
1909
|
const possiblePaths = [
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1910
|
+
nodePath.resolve(process.cwd(), "../../packages-new/circuits"),
|
|
1911
|
+
nodePath.resolve(process.cwd(), "../packages-new/circuits"),
|
|
1912
|
+
nodePath.resolve(process.cwd(), "packages-new/circuits"),
|
|
1913
|
+
nodePath.resolve(process.cwd(), "../../circuits"),
|
|
1914
|
+
nodePath.resolve(process.cwd(), "../circuits")
|
|
1815
1915
|
];
|
|
1816
1916
|
for (const p of possiblePaths) {
|
|
1817
1917
|
if (await areCircuitsAvailable(p)) {
|
|
@@ -1820,6 +1920,90 @@ async function getDefaultCircuitsPath() {
|
|
|
1820
1920
|
}
|
|
1821
1921
|
return possiblePaths[0];
|
|
1822
1922
|
}
|
|
1923
|
+
var EXPECTED_CIRCUIT_HASHES = {
|
|
1924
|
+
// SHA-256 of the verification key JSON from withdraw_regular circuit
|
|
1925
|
+
withdraw_regular_vkey: null,
|
|
1926
|
+
// Set to null to skip check during development
|
|
1927
|
+
// SHA-256 of the verification key JSON from withdraw_swap circuit
|
|
1928
|
+
withdraw_swap_vkey: null
|
|
1929
|
+
// Set to null to skip check during development
|
|
1930
|
+
};
|
|
1931
|
+
async function verifyCircuitIntegrity(circuitsPath, circuit) {
|
|
1932
|
+
const expectedHash = circuit === "withdraw_regular" ? EXPECTED_CIRCUIT_HASHES.withdraw_regular_vkey : EXPECTED_CIRCUIT_HASHES.withdraw_swap_vkey;
|
|
1933
|
+
if (!expectedHash) {
|
|
1934
|
+
return {
|
|
1935
|
+
valid: true,
|
|
1936
|
+
circuit,
|
|
1937
|
+
error: "Verification skipped (no expected hash configured)"
|
|
1938
|
+
};
|
|
1939
|
+
}
|
|
1940
|
+
try {
|
|
1941
|
+
const vkeyPath = IS_BROWSER ? `${circuitsPath}/${circuit}_verification_key.json` : joinPath(circuitsPath, "build", `${circuit}_verification_key.json`);
|
|
1942
|
+
let vkeyData;
|
|
1943
|
+
if (IS_BROWSER) {
|
|
1944
|
+
const response = await fetch(vkeyPath);
|
|
1945
|
+
if (!response.ok) {
|
|
1946
|
+
return {
|
|
1947
|
+
valid: false,
|
|
1948
|
+
circuit,
|
|
1949
|
+
error: `Failed to fetch verification key: ${response.status}`
|
|
1950
|
+
};
|
|
1951
|
+
}
|
|
1952
|
+
vkeyData = await response.text();
|
|
1953
|
+
} else {
|
|
1954
|
+
try {
|
|
1955
|
+
const nodeFs = eval("require")("fs");
|
|
1956
|
+
vkeyData = nodeFs.readFileSync(vkeyPath, "utf8");
|
|
1957
|
+
} catch (e) {
|
|
1958
|
+
return {
|
|
1959
|
+
valid: false,
|
|
1960
|
+
circuit,
|
|
1961
|
+
error: `Failed to read verification key: ${e}`
|
|
1962
|
+
};
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
let computedHash;
|
|
1966
|
+
if (IS_BROWSER) {
|
|
1967
|
+
const encoder = new TextEncoder();
|
|
1968
|
+
const data = encoder.encode(vkeyData);
|
|
1969
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
1970
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
1971
|
+
computedHash = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1972
|
+
} else {
|
|
1973
|
+
const nodeCrypto = eval("require")("crypto");
|
|
1974
|
+
computedHash = nodeCrypto.createHash("sha256").update(vkeyData).digest("hex");
|
|
1975
|
+
}
|
|
1976
|
+
if (computedHash === expectedHash) {
|
|
1977
|
+
return {
|
|
1978
|
+
valid: true,
|
|
1979
|
+
circuit,
|
|
1980
|
+
computedHash,
|
|
1981
|
+
expectedHash
|
|
1982
|
+
};
|
|
1983
|
+
} else {
|
|
1984
|
+
return {
|
|
1985
|
+
valid: false,
|
|
1986
|
+
circuit,
|
|
1987
|
+
error: `Verification key hash mismatch! This circuit may produce invalid proofs.`,
|
|
1988
|
+
computedHash,
|
|
1989
|
+
expectedHash
|
|
1990
|
+
};
|
|
1991
|
+
}
|
|
1992
|
+
} catch (error) {
|
|
1993
|
+
return {
|
|
1994
|
+
valid: false,
|
|
1995
|
+
circuit,
|
|
1996
|
+
error: `Circuit verification failed: ${error instanceof Error ? error.message : String(error)}`
|
|
1997
|
+
};
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
async function verifyAllCircuits(circuitsPath2) {
|
|
2001
|
+
const results = await Promise.all([
|
|
2002
|
+
verifyCircuitIntegrity(circuitsPath2, "withdraw_regular"),
|
|
2003
|
+
verifyCircuitIntegrity(circuitsPath2, "withdraw_swap")
|
|
2004
|
+
]);
|
|
2005
|
+
return results;
|
|
2006
|
+
}
|
|
1823
2007
|
|
|
1824
2008
|
// src/core/CloakSDK.ts
|
|
1825
2009
|
var CLOAK_PROGRAM_ID = new PublicKey4("c1oak6tetxYnNfvXKFkpn1d98FxtK7B68vBQLYQpWKp");
|
|
@@ -1934,14 +2118,35 @@ var CloakSDK = class {
|
|
|
1934
2118
|
async deposit(connection, amountOrNote, options) {
|
|
1935
2119
|
try {
|
|
1936
2120
|
let note;
|
|
2121
|
+
let isNewNote = false;
|
|
1937
2122
|
if (typeof amountOrNote === "number") {
|
|
2123
|
+
options?.onProgress?.("generating_note", { message: "Generating note with secrets..." });
|
|
1938
2124
|
note = await generateNote(amountOrNote, this.config.network);
|
|
2125
|
+
isNewNote = true;
|
|
1939
2126
|
} else {
|
|
1940
2127
|
note = amountOrNote;
|
|
1941
2128
|
if (note.depositSignature) {
|
|
1942
2129
|
throw new Error("Note has already been deposited");
|
|
1943
2130
|
}
|
|
1944
2131
|
}
|
|
2132
|
+
if (isNewNote) {
|
|
2133
|
+
await this.storage.saveNote(note);
|
|
2134
|
+
if (options?.onNoteGenerated) {
|
|
2135
|
+
options?.onProgress?.("awaiting_note_acknowledgment", {
|
|
2136
|
+
message: "Waiting for note to be saved before proceeding with deposit..."
|
|
2137
|
+
});
|
|
2138
|
+
try {
|
|
2139
|
+
await options.onNoteGenerated(note);
|
|
2140
|
+
} catch (error) {
|
|
2141
|
+
throw new Error(
|
|
2142
|
+
`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.`
|
|
2143
|
+
);
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
options?.onProgress?.("note_saved", {
|
|
2147
|
+
message: "Note saved. Proceeding with on-chain deposit..."
|
|
2148
|
+
});
|
|
2149
|
+
}
|
|
1945
2150
|
const payerPubkey = this.getPublicKey();
|
|
1946
2151
|
const balance = await connection.getBalance(payerPubkey);
|
|
1947
2152
|
const requiredAmount = note.amount + 5e3;
|
|
@@ -2133,6 +2338,16 @@ var CloakSDK = class {
|
|
|
2133
2338
|
pathIndices: merkleProof.pathIndices
|
|
2134
2339
|
}
|
|
2135
2340
|
});
|
|
2341
|
+
await this.storage.updateNote(note.commitment, {
|
|
2342
|
+
depositSignature: signature,
|
|
2343
|
+
depositSlot,
|
|
2344
|
+
leafIndex,
|
|
2345
|
+
root,
|
|
2346
|
+
merkleProof: {
|
|
2347
|
+
pathElements: merkleProof.pathElements,
|
|
2348
|
+
pathIndices: merkleProof.pathIndices
|
|
2349
|
+
}
|
|
2350
|
+
});
|
|
2136
2351
|
return {
|
|
2137
2352
|
note: updatedNote,
|
|
2138
2353
|
signature,
|
|
@@ -2194,17 +2409,10 @@ var CloakSDK = class {
|
|
|
2194
2409
|
const feeBps = Math.ceil(protocolFee * 1e4 / note.amount);
|
|
2195
2410
|
const distributableAmount = getDistributableAmount2(note.amount);
|
|
2196
2411
|
validateTransfers(recipients, distributableAmount);
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
if (
|
|
2200
|
-
|
|
2201
|
-
pathElements: note.merkleProof.pathElements,
|
|
2202
|
-
pathIndices: note.merkleProof.pathIndices
|
|
2203
|
-
};
|
|
2204
|
-
merkleRoot = note.root;
|
|
2205
|
-
} else {
|
|
2206
|
-
merkleProof = await this.indexer.getMerkleProof(note.leafIndex);
|
|
2207
|
-
merkleRoot = merkleProof.root || (await this.indexer.getMerkleRoot()).root;
|
|
2412
|
+
const merkleProof = await this.indexer.getMerkleProof(note.leafIndex);
|
|
2413
|
+
const merkleRoot = merkleProof.root || (await this.indexer.getMerkleRoot()).root;
|
|
2414
|
+
if (!merkleRoot) {
|
|
2415
|
+
throw new Error("Failed to get Merkle root from indexer");
|
|
2208
2416
|
}
|
|
2209
2417
|
const nullifier = await computeNullifierAsync(note.sk_spend, note.leafIndex);
|
|
2210
2418
|
const nullifierHex = nullifier.toString(16).padStart(64, "0");
|
|
@@ -2241,8 +2449,8 @@ var CloakSDK = class {
|
|
|
2241
2449
|
}
|
|
2242
2450
|
}
|
|
2243
2451
|
const isBrowser = typeof window !== "undefined" || typeof globalThis !== "undefined" && globalThis.window;
|
|
2244
|
-
const
|
|
2245
|
-
const useDirectProof = await areCircuitsAvailable(
|
|
2452
|
+
const circuitsPath2 = isBrowser ? "/circuits" : typeof process !== "undefined" && process.env?.CIRCUITS_PATH || await getDefaultCircuitsPath();
|
|
2453
|
+
const useDirectProof = await areCircuitsAvailable(circuitsPath2);
|
|
2246
2454
|
let proofHex;
|
|
2247
2455
|
let finalPublicInputs;
|
|
2248
2456
|
if (useDirectProof) {
|
|
@@ -2278,6 +2486,13 @@ var CloakSDK = class {
|
|
|
2278
2486
|
}
|
|
2279
2487
|
const sk = splitTo2Limbs(sk_spend_bigint);
|
|
2280
2488
|
const r = splitTo2Limbs(r_bigint);
|
|
2489
|
+
const computedCommitment = await computeCommitment(amount_bigint, r_bigint, sk_spend_bigint);
|
|
2490
|
+
const noteCommitment = BigInt("0x" + note.commitment);
|
|
2491
|
+
if (computedCommitment !== noteCommitment) {
|
|
2492
|
+
throw new Error(
|
|
2493
|
+
`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.`
|
|
2494
|
+
);
|
|
2495
|
+
}
|
|
2281
2496
|
const proofInputs = {
|
|
2282
2497
|
root: root_bigint,
|
|
2283
2498
|
nullifier: nullifier_bigint,
|
|
@@ -2296,7 +2511,9 @@ var CloakSDK = class {
|
|
|
2296
2511
|
var_fee: varFee,
|
|
2297
2512
|
rem
|
|
2298
2513
|
};
|
|
2299
|
-
|
|
2514
|
+
options?.onProgress?.("proof_generating");
|
|
2515
|
+
const proofResult = await generateWithdrawRegularProof(proofInputs, circuitsPath2);
|
|
2516
|
+
options?.onProgress?.("proof_complete");
|
|
2300
2517
|
proofHex = Buffer.from(proofResult.proofBytes).toString("hex");
|
|
2301
2518
|
finalPublicInputs = {
|
|
2302
2519
|
root: merkleRoot,
|
|
@@ -2306,7 +2523,7 @@ var CloakSDK = class {
|
|
|
2306
2523
|
};
|
|
2307
2524
|
} else {
|
|
2308
2525
|
throw new Error(
|
|
2309
|
-
`Circuits not available at ${
|
|
2526
|
+
`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.`
|
|
2310
2527
|
);
|
|
2311
2528
|
}
|
|
2312
2529
|
const signature = await this.relay.submitWithdraw(
|
|
@@ -2462,30 +2679,27 @@ var CloakSDK = class {
|
|
|
2462
2679
|
);
|
|
2463
2680
|
}
|
|
2464
2681
|
let recipientAta;
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2682
|
+
if (options.recipientAta) {
|
|
2683
|
+
recipientAta = new PublicKey4(options.recipientAta);
|
|
2684
|
+
} else {
|
|
2685
|
+
try {
|
|
2686
|
+
const splTokenModule = await import("@solana/spl-token");
|
|
2687
|
+
const getAssociatedTokenAddress = splTokenModule.getAssociatedTokenAddress;
|
|
2688
|
+
if (!getAssociatedTokenAddress) {
|
|
2689
|
+
throw new Error("getAssociatedTokenAddress not found");
|
|
2690
|
+
}
|
|
2691
|
+
const outputMint2 = new PublicKey4(options.outputMint);
|
|
2692
|
+
recipientAta = await getAssociatedTokenAddress(outputMint2, recipient);
|
|
2693
|
+
} catch (error) {
|
|
2694
|
+
throw new Error(
|
|
2695
|
+
`Failed to get associated token account: ${error instanceof Error ? error.message : String(error)}. Please install @solana/spl-token or provide recipientAta in options.`
|
|
2696
|
+
);
|
|
2470
2697
|
}
|
|
2471
|
-
const outputMint2 = new PublicKey4(options.outputMint);
|
|
2472
|
-
recipientAta = await getAssociatedTokenAddress(outputMint2, recipient);
|
|
2473
|
-
} catch (error) {
|
|
2474
|
-
throw new Error(
|
|
2475
|
-
`Failed to get associated token account: ${error instanceof Error ? error.message : String(error)}. Please install @solana/spl-token or provide recipientAta in options.`
|
|
2476
|
-
);
|
|
2477
2698
|
}
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
if (
|
|
2481
|
-
|
|
2482
|
-
pathElements: note.merkleProof.pathElements,
|
|
2483
|
-
pathIndices: note.merkleProof.pathIndices
|
|
2484
|
-
};
|
|
2485
|
-
merkleRoot = note.root;
|
|
2486
|
-
} else {
|
|
2487
|
-
merkleProof = await this.indexer.getMerkleProof(note.leafIndex);
|
|
2488
|
-
merkleRoot = merkleProof.root || (await this.indexer.getMerkleRoot()).root;
|
|
2699
|
+
const merkleProof = await this.indexer.getMerkleProof(note.leafIndex);
|
|
2700
|
+
const merkleRoot = merkleProof.root || (await this.indexer.getMerkleRoot()).root;
|
|
2701
|
+
if (!merkleRoot) {
|
|
2702
|
+
throw new Error("Failed to get Merkle root from indexer");
|
|
2489
2703
|
}
|
|
2490
2704
|
const nullifier = await computeNullifierAsync(note.sk_spend, note.leafIndex);
|
|
2491
2705
|
const nullifierHex = nullifier.toString(16).padStart(64, "0");
|
|
@@ -2506,8 +2720,8 @@ var CloakSDK = class {
|
|
|
2506
2720
|
throw new Error("Merkle proof is invalid: missing path elements");
|
|
2507
2721
|
}
|
|
2508
2722
|
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;
|
|
2509
|
-
const
|
|
2510
|
-
const useDirectProof = await areCircuitsAvailable(
|
|
2723
|
+
const circuitsPath2 = envCircuitsPath || await getDefaultCircuitsPath();
|
|
2724
|
+
const useDirectProof = await areCircuitsAvailable(circuitsPath2);
|
|
2511
2725
|
let proofHex;
|
|
2512
2726
|
let finalPublicInputs;
|
|
2513
2727
|
if (useDirectProof) {
|
|
@@ -2521,6 +2735,13 @@ var CloakSDK = class {
|
|
|
2521
2735
|
const inputMintLimbs = pubkeyToLimbs(inputMint.toBytes());
|
|
2522
2736
|
const outputMintLimbs = pubkeyToLimbs(outputMint.toBytes());
|
|
2523
2737
|
const recipientAtaLimbs = pubkeyToLimbs(recipientAta.toBytes());
|
|
2738
|
+
const computedCommitment = await computeCommitment(amount_bigint, r_bigint, sk_spend_bigint);
|
|
2739
|
+
const noteCommitment = BigInt("0x" + note.commitment);
|
|
2740
|
+
if (computedCommitment !== noteCommitment) {
|
|
2741
|
+
throw new Error(
|
|
2742
|
+
`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.`
|
|
2743
|
+
);
|
|
2744
|
+
}
|
|
2524
2745
|
const t = amount_bigint * 5n;
|
|
2525
2746
|
const varFee = t / 1000n;
|
|
2526
2747
|
const rem = t % 1000n;
|
|
@@ -2542,7 +2763,9 @@ var CloakSDK = class {
|
|
|
2542
2763
|
var_fee: varFee,
|
|
2543
2764
|
rem
|
|
2544
2765
|
};
|
|
2545
|
-
|
|
2766
|
+
options?.onProgress?.("proof_generating");
|
|
2767
|
+
const proofResult = await generateWithdrawSwapProof(proofInputs, circuitsPath2);
|
|
2768
|
+
options?.onProgress?.("proof_complete");
|
|
2546
2769
|
proofHex = Buffer.from(proofResult.proofBytes).toString("hex");
|
|
2547
2770
|
finalPublicInputs = {
|
|
2548
2771
|
root: merkleRoot,
|
|
@@ -2552,7 +2775,7 @@ var CloakSDK = class {
|
|
|
2552
2775
|
};
|
|
2553
2776
|
} else {
|
|
2554
2777
|
throw new Error(
|
|
2555
|
-
`Circuits not available at ${
|
|
2778
|
+
`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.`
|
|
2556
2779
|
);
|
|
2557
2780
|
}
|
|
2558
2781
|
const signature = await this.relay.submitSwap(
|
|
@@ -2927,6 +3150,63 @@ async function copyNoteToClipboard(note) {
|
|
|
2927
3150
|
}
|
|
2928
3151
|
|
|
2929
3152
|
// src/utils/errors.ts
|
|
3153
|
+
var ShieldPoolErrors = {
|
|
3154
|
+
// Root management errors
|
|
3155
|
+
4096: "Invalid Merkle root",
|
|
3156
|
+
4097: "Root not found in the roots ring",
|
|
3157
|
+
4098: "Roots ring is full",
|
|
3158
|
+
// Proof verification errors
|
|
3159
|
+
4112: "Zero-knowledge proof is invalid",
|
|
3160
|
+
4113: "Invalid proof size (expected 260 bytes)",
|
|
3161
|
+
4114: "Invalid public inputs",
|
|
3162
|
+
4115: "Verification key mismatch",
|
|
3163
|
+
// Nullifier errors
|
|
3164
|
+
4128: "Double spend detected - this note has already been spent",
|
|
3165
|
+
4129: "Nullifier shard is full",
|
|
3166
|
+
4130: "Invalid nullifier",
|
|
3167
|
+
// Transaction validation errors
|
|
3168
|
+
4144: "Output addresses or amounts don't match the proof",
|
|
3169
|
+
4145: "Amount conservation failed - outputs + fee must equal input amount",
|
|
3170
|
+
4146: "Invalid outputs hash",
|
|
3171
|
+
4147: "Invalid amount (must be greater than zero)",
|
|
3172
|
+
4148: "Invalid recipient address",
|
|
3173
|
+
4149: "Commitment already exists in the tree",
|
|
3174
|
+
4150: "Commitment log is full",
|
|
3175
|
+
// Math errors
|
|
3176
|
+
4160: "Math overflow occurred",
|
|
3177
|
+
4161: "Division by zero",
|
|
3178
|
+
// Account errors
|
|
3179
|
+
4176: "Account validation failed - please check your wallet balance and try again",
|
|
3180
|
+
4177: "Pool account owner mismatch",
|
|
3181
|
+
4178: "Treasury account owner mismatch",
|
|
3182
|
+
4179: "Roots ring account owner mismatch",
|
|
3183
|
+
4180: "Nullifier shard account owner mismatch",
|
|
3184
|
+
4181: "Pool account is not writable",
|
|
3185
|
+
4182: "Treasury account is not writable",
|
|
3186
|
+
4183: "Recipient account is not writable",
|
|
3187
|
+
4184: "Insufficient lamports in pool or account",
|
|
3188
|
+
4185: "Invalid account owner",
|
|
3189
|
+
4186: "Invalid account size",
|
|
3190
|
+
4187: "Commitments account is not writable",
|
|
3191
|
+
4188: "Invalid admin authority",
|
|
3192
|
+
// Instruction errors
|
|
3193
|
+
4192: "Invalid instruction data length",
|
|
3194
|
+
4193: "Invalid instruction data format",
|
|
3195
|
+
4194: "Missing required accounts",
|
|
3196
|
+
4195: "Invalid instruction tag",
|
|
3197
|
+
// PoW/Scrambler errors
|
|
3198
|
+
4196: "Invalid miner account",
|
|
3199
|
+
4197: "Invalid claim account",
|
|
3200
|
+
4198: "Failed to consume claim",
|
|
3201
|
+
// Groth16 verifier errors
|
|
3202
|
+
4208: "Invalid G1 point length",
|
|
3203
|
+
4209: "Invalid G2 point length",
|
|
3204
|
+
4210: "Invalid public inputs length",
|
|
3205
|
+
4211: "Public input exceeds field size",
|
|
3206
|
+
4212: "G1 multiplication failed during proof preparation",
|
|
3207
|
+
4213: "G1 addition failed during proof preparation",
|
|
3208
|
+
4214: "Proof verification failed"
|
|
3209
|
+
};
|
|
2930
3210
|
var PROGRAM_ERRORS = {
|
|
2931
3211
|
// Nullifier errors
|
|
2932
3212
|
"NullifierAlreadyUsed": "This note has already been withdrawn. Each note can only be spent once.",
|
|
@@ -2955,6 +3235,438 @@ var PROGRAM_ERRORS = {
|
|
|
2955
3235
|
"AccountNotFound": "Required account not found.",
|
|
2956
3236
|
"InvalidInstruction": "Invalid instruction data."
|
|
2957
3237
|
};
|
|
3238
|
+
var ErrorPatterns = [
|
|
3239
|
+
// Wallet/User action errors
|
|
3240
|
+
{
|
|
3241
|
+
patterns: [
|
|
3242
|
+
"User rejected",
|
|
3243
|
+
"user rejected",
|
|
3244
|
+
"User denied",
|
|
3245
|
+
"user denied",
|
|
3246
|
+
"Transaction cancelled",
|
|
3247
|
+
"Transaction rejected",
|
|
3248
|
+
/code.*4001/i
|
|
3249
|
+
],
|
|
3250
|
+
result: {
|
|
3251
|
+
title: "Transaction Cancelled",
|
|
3252
|
+
message: "You cancelled the transaction.",
|
|
3253
|
+
category: "wallet",
|
|
3254
|
+
recoverable: true
|
|
3255
|
+
}
|
|
3256
|
+
},
|
|
3257
|
+
{
|
|
3258
|
+
patterns: [
|
|
3259
|
+
"Wallet not connected",
|
|
3260
|
+
"wallet not connected",
|
|
3261
|
+
"Please connect wallet",
|
|
3262
|
+
"No wallet connected"
|
|
3263
|
+
],
|
|
3264
|
+
result: {
|
|
3265
|
+
title: "Wallet Not Connected",
|
|
3266
|
+
message: "Please connect your wallet to continue.",
|
|
3267
|
+
category: "wallet",
|
|
3268
|
+
suggestion: "Click the wallet button to connect.",
|
|
3269
|
+
recoverable: true
|
|
3270
|
+
}
|
|
3271
|
+
},
|
|
3272
|
+
{
|
|
3273
|
+
patterns: [
|
|
3274
|
+
"SDK not initialized",
|
|
3275
|
+
"sdk not initialized"
|
|
3276
|
+
],
|
|
3277
|
+
result: {
|
|
3278
|
+
title: "Wallet Connection Required",
|
|
3279
|
+
message: "Your wallet connection was interrupted.",
|
|
3280
|
+
category: "wallet",
|
|
3281
|
+
suggestion: "Please reconnect your wallet and try again.",
|
|
3282
|
+
recoverable: true
|
|
3283
|
+
}
|
|
3284
|
+
},
|
|
3285
|
+
{
|
|
3286
|
+
patterns: [
|
|
3287
|
+
"Wallet does not support signing",
|
|
3288
|
+
"signTransaction"
|
|
3289
|
+
],
|
|
3290
|
+
result: {
|
|
3291
|
+
title: "Wallet Feature Not Supported",
|
|
3292
|
+
message: "Your wallet doesn't support the required signing method.",
|
|
3293
|
+
category: "wallet",
|
|
3294
|
+
suggestion: "Try using a different wallet like Phantom or Solflare.",
|
|
3295
|
+
recoverable: true
|
|
3296
|
+
}
|
|
3297
|
+
},
|
|
3298
|
+
// Balance errors
|
|
3299
|
+
{
|
|
3300
|
+
patterns: [
|
|
3301
|
+
"insufficient lamports",
|
|
3302
|
+
"Insufficient lamports",
|
|
3303
|
+
"insufficient balance",
|
|
3304
|
+
"Insufficient balance",
|
|
3305
|
+
"Insufficient SOL",
|
|
3306
|
+
"not enough SOL",
|
|
3307
|
+
"Attempt to debit an account but found no record",
|
|
3308
|
+
/0x1$/
|
|
3309
|
+
],
|
|
3310
|
+
result: {
|
|
3311
|
+
title: "Insufficient Balance",
|
|
3312
|
+
message: "You don't have enough SOL for this transaction.",
|
|
3313
|
+
category: "validation",
|
|
3314
|
+
suggestion: "Add more SOL to your wallet or reduce the amount.",
|
|
3315
|
+
recoverable: true
|
|
3316
|
+
}
|
|
3317
|
+
},
|
|
3318
|
+
{
|
|
3319
|
+
patterns: [
|
|
3320
|
+
"Insufficient funds",
|
|
3321
|
+
"insufficient funds"
|
|
3322
|
+
],
|
|
3323
|
+
result: {
|
|
3324
|
+
title: "Insufficient Funds",
|
|
3325
|
+
message: "Your wallet doesn't have enough funds for this transaction.",
|
|
3326
|
+
category: "validation",
|
|
3327
|
+
suggestion: "Add more funds to your wallet and try again.",
|
|
3328
|
+
recoverable: true
|
|
3329
|
+
}
|
|
3330
|
+
},
|
|
3331
|
+
// Network errors
|
|
3332
|
+
{
|
|
3333
|
+
patterns: [
|
|
3334
|
+
"fetch failed",
|
|
3335
|
+
"Failed to fetch",
|
|
3336
|
+
"Network error",
|
|
3337
|
+
"network error",
|
|
3338
|
+
"ECONNREFUSED",
|
|
3339
|
+
"ETIMEDOUT",
|
|
3340
|
+
"ENOTFOUND",
|
|
3341
|
+
"NetworkError",
|
|
3342
|
+
"net::ERR",
|
|
3343
|
+
"Failed to load"
|
|
3344
|
+
],
|
|
3345
|
+
result: {
|
|
3346
|
+
title: "Connection Error",
|
|
3347
|
+
message: "Unable to connect to the network.",
|
|
3348
|
+
category: "network",
|
|
3349
|
+
suggestion: "Check your internet connection and try again.",
|
|
3350
|
+
recoverable: true
|
|
3351
|
+
}
|
|
3352
|
+
},
|
|
3353
|
+
{
|
|
3354
|
+
patterns: [
|
|
3355
|
+
"timeout",
|
|
3356
|
+
"Timeout",
|
|
3357
|
+
"TIMEOUT",
|
|
3358
|
+
"timed out",
|
|
3359
|
+
"Timed out"
|
|
3360
|
+
],
|
|
3361
|
+
result: {
|
|
3362
|
+
title: "Request Timed Out",
|
|
3363
|
+
message: "The request took too long to complete.",
|
|
3364
|
+
category: "network",
|
|
3365
|
+
suggestion: "The network may be congested. Please try again in a moment.",
|
|
3366
|
+
recoverable: true
|
|
3367
|
+
}
|
|
3368
|
+
},
|
|
3369
|
+
{
|
|
3370
|
+
patterns: [
|
|
3371
|
+
"blockhash not found",
|
|
3372
|
+
"Blockhash not found",
|
|
3373
|
+
"block height exceeded"
|
|
3374
|
+
],
|
|
3375
|
+
result: {
|
|
3376
|
+
title: "Transaction Expired",
|
|
3377
|
+
message: "The transaction took too long and expired.",
|
|
3378
|
+
category: "network",
|
|
3379
|
+
suggestion: "Please try again. If this persists, the network may be congested.",
|
|
3380
|
+
recoverable: true
|
|
3381
|
+
}
|
|
3382
|
+
},
|
|
3383
|
+
// Service errors (Indexer/Relay)
|
|
3384
|
+
{
|
|
3385
|
+
patterns: [
|
|
3386
|
+
"Indexer",
|
|
3387
|
+
"indexer",
|
|
3388
|
+
/indexer.*unavailable/i,
|
|
3389
|
+
/failed.*indexer/i
|
|
3390
|
+
],
|
|
3391
|
+
result: {
|
|
3392
|
+
title: "Service Temporarily Unavailable",
|
|
3393
|
+
message: "The privacy service is temporarily unavailable.",
|
|
3394
|
+
category: "service",
|
|
3395
|
+
suggestion: "Please wait a moment and try again.",
|
|
3396
|
+
recoverable: true
|
|
3397
|
+
}
|
|
3398
|
+
},
|
|
3399
|
+
{
|
|
3400
|
+
patterns: [
|
|
3401
|
+
"Relay",
|
|
3402
|
+
"relay",
|
|
3403
|
+
/relay.*unavailable/i,
|
|
3404
|
+
/failed.*relay/i,
|
|
3405
|
+
"failed to submit",
|
|
3406
|
+
"Failed to submit"
|
|
3407
|
+
],
|
|
3408
|
+
result: {
|
|
3409
|
+
title: "Processing Service Busy",
|
|
3410
|
+
message: "The transaction processing service is busy.",
|
|
3411
|
+
category: "service",
|
|
3412
|
+
suggestion: "Please wait a moment and try again.",
|
|
3413
|
+
recoverable: true
|
|
3414
|
+
}
|
|
3415
|
+
},
|
|
3416
|
+
{
|
|
3417
|
+
patterns: [
|
|
3418
|
+
"429",
|
|
3419
|
+
"Too Many Requests",
|
|
3420
|
+
"rate limit",
|
|
3421
|
+
"Rate limit"
|
|
3422
|
+
],
|
|
3423
|
+
result: {
|
|
3424
|
+
title: "Too Many Requests",
|
|
3425
|
+
message: "You're making requests too quickly.",
|
|
3426
|
+
category: "service",
|
|
3427
|
+
suggestion: "Please wait a moment before trying again.",
|
|
3428
|
+
recoverable: true
|
|
3429
|
+
}
|
|
3430
|
+
},
|
|
3431
|
+
{
|
|
3432
|
+
patterns: [
|
|
3433
|
+
"503",
|
|
3434
|
+
"Service Unavailable",
|
|
3435
|
+
"502",
|
|
3436
|
+
"Bad Gateway"
|
|
3437
|
+
],
|
|
3438
|
+
result: {
|
|
3439
|
+
title: "Service Temporarily Unavailable",
|
|
3440
|
+
message: "Our servers are temporarily unavailable.",
|
|
3441
|
+
category: "service",
|
|
3442
|
+
suggestion: "Please try again in a few minutes.",
|
|
3443
|
+
recoverable: true
|
|
3444
|
+
}
|
|
3445
|
+
},
|
|
3446
|
+
// Proof/ZK errors
|
|
3447
|
+
{
|
|
3448
|
+
patterns: [
|
|
3449
|
+
"Circuits not available",
|
|
3450
|
+
"circuits not available",
|
|
3451
|
+
"Failed to load circuit",
|
|
3452
|
+
"WASM",
|
|
3453
|
+
"wasm",
|
|
3454
|
+
/circuit.*not.*found/i
|
|
3455
|
+
],
|
|
3456
|
+
result: {
|
|
3457
|
+
title: "Loading Error",
|
|
3458
|
+
message: "Failed to load required cryptographic components.",
|
|
3459
|
+
category: "service",
|
|
3460
|
+
suggestion: "Try refreshing the page. If the problem persists, clear your browser cache.",
|
|
3461
|
+
recoverable: true
|
|
3462
|
+
}
|
|
3463
|
+
},
|
|
3464
|
+
{
|
|
3465
|
+
patterns: [
|
|
3466
|
+
"proof generation",
|
|
3467
|
+
"Proof generation",
|
|
3468
|
+
"Failed to generate proof",
|
|
3469
|
+
"proving error"
|
|
3470
|
+
],
|
|
3471
|
+
result: {
|
|
3472
|
+
title: "Proof Generation Failed",
|
|
3473
|
+
message: "Failed to generate the privacy proof.",
|
|
3474
|
+
category: "service",
|
|
3475
|
+
suggestion: "Try again with a smaller amount or refresh the page.",
|
|
3476
|
+
recoverable: true
|
|
3477
|
+
}
|
|
3478
|
+
},
|
|
3479
|
+
// Validation errors
|
|
3480
|
+
{
|
|
3481
|
+
patterns: [
|
|
3482
|
+
"Invalid amount",
|
|
3483
|
+
"invalid amount",
|
|
3484
|
+
"Amount must be",
|
|
3485
|
+
"amount must be",
|
|
3486
|
+
"Amount too small"
|
|
3487
|
+
],
|
|
3488
|
+
result: {
|
|
3489
|
+
title: "Invalid Amount",
|
|
3490
|
+
message: "The amount you entered is not valid.",
|
|
3491
|
+
category: "validation",
|
|
3492
|
+
suggestion: "Enter an amount greater than the minimum required.",
|
|
3493
|
+
recoverable: true
|
|
3494
|
+
}
|
|
3495
|
+
},
|
|
3496
|
+
{
|
|
3497
|
+
patterns: [
|
|
3498
|
+
"Invalid recipient",
|
|
3499
|
+
"invalid recipient",
|
|
3500
|
+
"Invalid address",
|
|
3501
|
+
"invalid address",
|
|
3502
|
+
"Invalid Solana address"
|
|
3503
|
+
],
|
|
3504
|
+
result: {
|
|
3505
|
+
title: "Invalid Address",
|
|
3506
|
+
message: "The recipient address is not valid.",
|
|
3507
|
+
category: "validation",
|
|
3508
|
+
suggestion: "Check the address and make sure it's a valid Solana address.",
|
|
3509
|
+
recoverable: true
|
|
3510
|
+
}
|
|
3511
|
+
},
|
|
3512
|
+
{
|
|
3513
|
+
patterns: [
|
|
3514
|
+
"Invalid token",
|
|
3515
|
+
"invalid token",
|
|
3516
|
+
"Unsupported token"
|
|
3517
|
+
],
|
|
3518
|
+
result: {
|
|
3519
|
+
title: "Unsupported Token",
|
|
3520
|
+
message: "This token is not supported.",
|
|
3521
|
+
category: "validation",
|
|
3522
|
+
suggestion: "Select a supported token and try again.",
|
|
3523
|
+
recoverable: true
|
|
3524
|
+
}
|
|
3525
|
+
},
|
|
3526
|
+
// Swap errors
|
|
3527
|
+
{
|
|
3528
|
+
patterns: [
|
|
3529
|
+
"Failed to get quote",
|
|
3530
|
+
"failed to get quote",
|
|
3531
|
+
"No route found",
|
|
3532
|
+
"no route found",
|
|
3533
|
+
"Insufficient liquidity",
|
|
3534
|
+
"insufficient liquidity"
|
|
3535
|
+
],
|
|
3536
|
+
result: {
|
|
3537
|
+
title: "Swap Quote Unavailable",
|
|
3538
|
+
message: "Unable to get a price quote for this swap.",
|
|
3539
|
+
category: "service",
|
|
3540
|
+
suggestion: "Try a different amount or wait for better liquidity.",
|
|
3541
|
+
recoverable: true
|
|
3542
|
+
}
|
|
3543
|
+
},
|
|
3544
|
+
{
|
|
3545
|
+
patterns: [
|
|
3546
|
+
"Slippage",
|
|
3547
|
+
"slippage",
|
|
3548
|
+
"Price impact",
|
|
3549
|
+
"price impact"
|
|
3550
|
+
],
|
|
3551
|
+
result: {
|
|
3552
|
+
title: "Price Changed",
|
|
3553
|
+
message: "The price changed too much during the transaction.",
|
|
3554
|
+
category: "transaction",
|
|
3555
|
+
suggestion: "Try again or increase your slippage tolerance.",
|
|
3556
|
+
recoverable: true
|
|
3557
|
+
}
|
|
3558
|
+
},
|
|
3559
|
+
// Transaction errors
|
|
3560
|
+
{
|
|
3561
|
+
patterns: [
|
|
3562
|
+
"Double spend",
|
|
3563
|
+
"double spend",
|
|
3564
|
+
"already been spent",
|
|
3565
|
+
"already spent"
|
|
3566
|
+
],
|
|
3567
|
+
result: {
|
|
3568
|
+
title: "Already Spent",
|
|
3569
|
+
message: "This note has already been used.",
|
|
3570
|
+
category: "transaction",
|
|
3571
|
+
suggestion: "This funds have already been withdrawn.",
|
|
3572
|
+
recoverable: false
|
|
3573
|
+
}
|
|
3574
|
+
},
|
|
3575
|
+
{
|
|
3576
|
+
patterns: [
|
|
3577
|
+
"simulation failed",
|
|
3578
|
+
"Simulation failed",
|
|
3579
|
+
"Transaction simulation"
|
|
3580
|
+
],
|
|
3581
|
+
result: {
|
|
3582
|
+
title: "Transaction Failed",
|
|
3583
|
+
message: "The transaction could not be completed.",
|
|
3584
|
+
category: "transaction",
|
|
3585
|
+
suggestion: "Please try again. If the problem persists, check your balance.",
|
|
3586
|
+
recoverable: true
|
|
3587
|
+
}
|
|
3588
|
+
},
|
|
3589
|
+
{
|
|
3590
|
+
patterns: [
|
|
3591
|
+
"confirmation timeout",
|
|
3592
|
+
"Confirmation timeout",
|
|
3593
|
+
"not confirmed"
|
|
3594
|
+
],
|
|
3595
|
+
result: {
|
|
3596
|
+
title: "Confirmation Pending",
|
|
3597
|
+
message: "Transaction confirmation is taking longer than expected.",
|
|
3598
|
+
category: "network",
|
|
3599
|
+
suggestion: "Your transaction may still complete. Check your wallet or try again.",
|
|
3600
|
+
recoverable: true
|
|
3601
|
+
}
|
|
3602
|
+
}
|
|
3603
|
+
];
|
|
3604
|
+
function parseError(error) {
|
|
3605
|
+
let errorMessage = "";
|
|
3606
|
+
let originalError = "";
|
|
3607
|
+
if (error instanceof Error) {
|
|
3608
|
+
errorMessage = error.message;
|
|
3609
|
+
originalError = error.stack || error.message;
|
|
3610
|
+
} else if (typeof error === "string") {
|
|
3611
|
+
errorMessage = error;
|
|
3612
|
+
originalError = error;
|
|
3613
|
+
} else if (error && typeof error === "object") {
|
|
3614
|
+
const err = error;
|
|
3615
|
+
errorMessage = String(err.message || err.error || err.msg || JSON.stringify(error));
|
|
3616
|
+
originalError = JSON.stringify(error);
|
|
3617
|
+
} else {
|
|
3618
|
+
errorMessage = String(error);
|
|
3619
|
+
originalError = String(error);
|
|
3620
|
+
}
|
|
3621
|
+
for (const { patterns, result } of ErrorPatterns) {
|
|
3622
|
+
for (const pattern of patterns) {
|
|
3623
|
+
if (typeof pattern === "string") {
|
|
3624
|
+
if (errorMessage.includes(pattern)) {
|
|
3625
|
+
return { ...result, originalError };
|
|
3626
|
+
}
|
|
3627
|
+
} else if (pattern instanceof RegExp) {
|
|
3628
|
+
if (pattern.test(errorMessage)) {
|
|
3629
|
+
return { ...result, originalError };
|
|
3630
|
+
}
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3633
|
+
}
|
|
3634
|
+
const programError = tryParseProgramError(errorMessage);
|
|
3635
|
+
if (programError) {
|
|
3636
|
+
return { ...programError, originalError };
|
|
3637
|
+
}
|
|
3638
|
+
return {
|
|
3639
|
+
title: "Something Went Wrong",
|
|
3640
|
+
message: "An unexpected error occurred.",
|
|
3641
|
+
category: "unknown",
|
|
3642
|
+
suggestion: "Please try again. If the problem persists, refresh the page.",
|
|
3643
|
+
recoverable: true,
|
|
3644
|
+
originalError
|
|
3645
|
+
};
|
|
3646
|
+
}
|
|
3647
|
+
function tryParseProgramError(message) {
|
|
3648
|
+
const match = message.match(/\{"InstructionError":\[(\d+),\{"Custom":(\d+)\}\]\}/);
|
|
3649
|
+
if (match) {
|
|
3650
|
+
const errorCode = parseInt(match[2]);
|
|
3651
|
+
const friendlyMessage = ShieldPoolErrors[errorCode];
|
|
3652
|
+
if (friendlyMessage) {
|
|
3653
|
+
return {
|
|
3654
|
+
title: "Transaction Failed",
|
|
3655
|
+
message: friendlyMessage,
|
|
3656
|
+
category: "transaction",
|
|
3657
|
+
recoverable: !friendlyMessage.toLowerCase().includes("double spend")
|
|
3658
|
+
};
|
|
3659
|
+
}
|
|
3660
|
+
return {
|
|
3661
|
+
title: "Transaction Failed",
|
|
3662
|
+
message: `Transaction failed with error code ${errorCode}.`,
|
|
3663
|
+
category: "transaction",
|
|
3664
|
+
suggestion: "Please try again or contact support if this persists.",
|
|
3665
|
+
recoverable: true
|
|
3666
|
+
};
|
|
3667
|
+
}
|
|
3668
|
+
return null;
|
|
3669
|
+
}
|
|
2958
3670
|
function parseTransactionError(error) {
|
|
2959
3671
|
if (!error) return "An unknown error occurred";
|
|
2960
3672
|
const errorStr = typeof error === "string" ? error : error.message || error.toString();
|
|
@@ -3062,188 +3774,6 @@ function formatErrorForLogging(error) {
|
|
|
3062
3774
|
return String(error);
|
|
3063
3775
|
}
|
|
3064
3776
|
|
|
3065
|
-
// src/services/ProverService.ts
|
|
3066
|
-
var ProverService = class {
|
|
3067
|
-
/**
|
|
3068
|
-
* Create a new Prover Service client
|
|
3069
|
-
*
|
|
3070
|
-
* @param indexerUrl - Indexer/Prover service base URL
|
|
3071
|
-
* @param timeout - Proof generation timeout in ms (default: 5 minutes)
|
|
3072
|
-
*/
|
|
3073
|
-
constructor(indexerUrl, timeout = 5 * 60 * 1e3) {
|
|
3074
|
-
this.indexerUrl = indexerUrl.replace(/\/$/, "");
|
|
3075
|
-
this.timeout = timeout;
|
|
3076
|
-
}
|
|
3077
|
-
/**
|
|
3078
|
-
* Generate a zero-knowledge proof for withdrawal
|
|
3079
|
-
*
|
|
3080
|
-
* This process typically takes 30-180 seconds depending on the backend.
|
|
3081
|
-
*
|
|
3082
|
-
* @param inputs - Circuit inputs (private + public + outputs)
|
|
3083
|
-
* @param options - Optional progress tracking and callbacks
|
|
3084
|
-
* @returns Proof result with hex-encoded proof and public inputs
|
|
3085
|
-
*
|
|
3086
|
-
* @example
|
|
3087
|
-
* ```typescript
|
|
3088
|
-
* const result = await prover.generateProof(inputs);
|
|
3089
|
-
* if (result.success) {
|
|
3090
|
-
* console.log(`Proof: ${result.proof}`);
|
|
3091
|
-
* }
|
|
3092
|
-
* ```
|
|
3093
|
-
*
|
|
3094
|
-
* @example
|
|
3095
|
-
* ```typescript
|
|
3096
|
-
* // With progress tracking
|
|
3097
|
-
* const result = await prover.generateProof(inputs, {
|
|
3098
|
-
* onProgress: (progress) => console.log(`Progress: ${progress}%`),
|
|
3099
|
-
* onStart: () => console.log("Starting proof generation..."),
|
|
3100
|
-
* onSuccess: (result) => console.log("Proof generated!"),
|
|
3101
|
-
* onError: (error) => console.error("Failed:", error)
|
|
3102
|
-
* });
|
|
3103
|
-
* ```
|
|
3104
|
-
*/
|
|
3105
|
-
async generateProof(inputs, options) {
|
|
3106
|
-
const startTime = Date.now();
|
|
3107
|
-
const actualTimeout = options?.timeout || this.timeout;
|
|
3108
|
-
options?.onStart?.();
|
|
3109
|
-
let progressInterval;
|
|
3110
|
-
try {
|
|
3111
|
-
const requestBody = {
|
|
3112
|
-
private_inputs: JSON.stringify(inputs.privateInputs),
|
|
3113
|
-
public_inputs: JSON.stringify(inputs.publicInputs),
|
|
3114
|
-
outputs: JSON.stringify(inputs.outputs)
|
|
3115
|
-
};
|
|
3116
|
-
if (inputs.swapParams) {
|
|
3117
|
-
requestBody.swap_params = inputs.swapParams;
|
|
3118
|
-
}
|
|
3119
|
-
const controller = new AbortController();
|
|
3120
|
-
const timeoutId = setTimeout(() => controller.abort(), actualTimeout);
|
|
3121
|
-
if (options?.onProgress) {
|
|
3122
|
-
let progress = 0;
|
|
3123
|
-
progressInterval = setInterval(() => {
|
|
3124
|
-
progress = Math.min(90, progress + Math.random() * 10);
|
|
3125
|
-
options.onProgress(Math.floor(progress));
|
|
3126
|
-
}, 2e3);
|
|
3127
|
-
}
|
|
3128
|
-
const response = await fetch(`${this.indexerUrl}/api/v1/prove`, {
|
|
3129
|
-
method: "POST",
|
|
3130
|
-
headers: {
|
|
3131
|
-
"Content-Type": "application/json"
|
|
3132
|
-
},
|
|
3133
|
-
body: JSON.stringify(requestBody),
|
|
3134
|
-
signal: controller.signal
|
|
3135
|
-
});
|
|
3136
|
-
clearTimeout(timeoutId);
|
|
3137
|
-
if (progressInterval) clearInterval(progressInterval);
|
|
3138
|
-
if (!response.ok) {
|
|
3139
|
-
let errorMessage = `${response.status} ${response.statusText}`;
|
|
3140
|
-
try {
|
|
3141
|
-
const errorText = await response.text();
|
|
3142
|
-
try {
|
|
3143
|
-
const errorJson = JSON.parse(errorText);
|
|
3144
|
-
errorMessage = errorJson.error || errorJson.message || errorText;
|
|
3145
|
-
} catch {
|
|
3146
|
-
errorMessage = errorText || errorMessage;
|
|
3147
|
-
}
|
|
3148
|
-
} catch {
|
|
3149
|
-
}
|
|
3150
|
-
options?.onError?.(errorMessage);
|
|
3151
|
-
return {
|
|
3152
|
-
success: false,
|
|
3153
|
-
generationTimeMs: Date.now() - startTime,
|
|
3154
|
-
error: errorMessage
|
|
3155
|
-
};
|
|
3156
|
-
}
|
|
3157
|
-
options?.onProgress?.(100);
|
|
3158
|
-
const rawData = await response.json();
|
|
3159
|
-
const result = {
|
|
3160
|
-
success: rawData.success,
|
|
3161
|
-
proof: rawData.proof,
|
|
3162
|
-
publicInputs: rawData.public_inputs,
|
|
3163
|
-
// Map snake_case
|
|
3164
|
-
generationTimeMs: rawData.generation_time_ms || Date.now() - startTime,
|
|
3165
|
-
error: rawData.error
|
|
3166
|
-
};
|
|
3167
|
-
if (!result.success && rawData.execution_report) {
|
|
3168
|
-
}
|
|
3169
|
-
if (!result.success && result.error) {
|
|
3170
|
-
try {
|
|
3171
|
-
const errorObj = typeof result.error === "string" ? JSON.parse(result.error) : result.error;
|
|
3172
|
-
if (errorObj?.error && typeof errorObj.error === "string") {
|
|
3173
|
-
result.error = errorObj.error;
|
|
3174
|
-
} else if (typeof errorObj === "string") {
|
|
3175
|
-
result.error = errorObj;
|
|
3176
|
-
}
|
|
3177
|
-
if (errorObj?.execution_report && typeof errorObj.execution_report === "string") {
|
|
3178
|
-
result.error += `
|
|
3179
|
-
Execution report: ${errorObj.execution_report}`;
|
|
3180
|
-
}
|
|
3181
|
-
if (errorObj?.total_cycles !== void 0) {
|
|
3182
|
-
result.error += `
|
|
3183
|
-
Total cycles: ${errorObj.total_cycles}`;
|
|
3184
|
-
}
|
|
3185
|
-
if (errorObj?.total_syscalls !== void 0) {
|
|
3186
|
-
result.error += `
|
|
3187
|
-
Total syscalls: ${errorObj.total_syscalls}`;
|
|
3188
|
-
}
|
|
3189
|
-
} catch {
|
|
3190
|
-
}
|
|
3191
|
-
}
|
|
3192
|
-
if (result.success) {
|
|
3193
|
-
options?.onSuccess?.(result);
|
|
3194
|
-
} else if (result.error) {
|
|
3195
|
-
options?.onError?.(result.error);
|
|
3196
|
-
}
|
|
3197
|
-
return result;
|
|
3198
|
-
} catch (error) {
|
|
3199
|
-
const totalTime = Date.now() - startTime;
|
|
3200
|
-
if (progressInterval) clearInterval(progressInterval);
|
|
3201
|
-
let errorMessage;
|
|
3202
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
3203
|
-
errorMessage = `Proof generation timed out after ${actualTimeout}ms`;
|
|
3204
|
-
} else {
|
|
3205
|
-
errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
3206
|
-
}
|
|
3207
|
-
options?.onError?.(errorMessage);
|
|
3208
|
-
return {
|
|
3209
|
-
success: false,
|
|
3210
|
-
generationTimeMs: totalTime,
|
|
3211
|
-
error: errorMessage
|
|
3212
|
-
};
|
|
3213
|
-
}
|
|
3214
|
-
}
|
|
3215
|
-
/**
|
|
3216
|
-
* Check if the prover service is available
|
|
3217
|
-
*
|
|
3218
|
-
* @returns True if service is healthy
|
|
3219
|
-
*/
|
|
3220
|
-
async healthCheck() {
|
|
3221
|
-
try {
|
|
3222
|
-
const response = await fetch(`${this.indexerUrl}/health`, {
|
|
3223
|
-
method: "GET"
|
|
3224
|
-
});
|
|
3225
|
-
return response.ok;
|
|
3226
|
-
} catch {
|
|
3227
|
-
return false;
|
|
3228
|
-
}
|
|
3229
|
-
}
|
|
3230
|
-
/**
|
|
3231
|
-
* Get the configured timeout
|
|
3232
|
-
*/
|
|
3233
|
-
getTimeout() {
|
|
3234
|
-
return this.timeout;
|
|
3235
|
-
}
|
|
3236
|
-
/**
|
|
3237
|
-
* Set a new timeout
|
|
3238
|
-
*/
|
|
3239
|
-
setTimeout(timeout) {
|
|
3240
|
-
if (timeout <= 0) {
|
|
3241
|
-
throw new Error("Timeout must be positive");
|
|
3242
|
-
}
|
|
3243
|
-
this.timeout = timeout;
|
|
3244
|
-
}
|
|
3245
|
-
};
|
|
3246
|
-
|
|
3247
3777
|
// src/helpers/wallet-integration.ts
|
|
3248
3778
|
import {
|
|
3249
3779
|
Keypair as Keypair2
|
|
@@ -3318,6 +3848,173 @@ function keypairToAdapter(keypair) {
|
|
|
3318
3848
|
};
|
|
3319
3849
|
}
|
|
3320
3850
|
|
|
3851
|
+
// src/utils/pending-operations.ts
|
|
3852
|
+
var PENDING_DEPOSITS_KEY = "cloak_pending_deposits";
|
|
3853
|
+
var PENDING_WITHDRAWALS_KEY = "cloak_pending_withdrawals";
|
|
3854
|
+
function getStorage() {
|
|
3855
|
+
if (typeof globalThis !== "undefined" && globalThis.localStorage) {
|
|
3856
|
+
return globalThis.localStorage;
|
|
3857
|
+
}
|
|
3858
|
+
return null;
|
|
3859
|
+
}
|
|
3860
|
+
function savePendingDeposit(deposit) {
|
|
3861
|
+
const storage = getStorage();
|
|
3862
|
+
if (!storage) {
|
|
3863
|
+
console.warn("localStorage not available - pending deposit not persisted");
|
|
3864
|
+
return;
|
|
3865
|
+
}
|
|
3866
|
+
const deposits = loadPendingDeposits();
|
|
3867
|
+
const index = deposits.findIndex((d) => d.note.commitment === deposit.note.commitment);
|
|
3868
|
+
if (index >= 0) {
|
|
3869
|
+
deposits[index] = deposit;
|
|
3870
|
+
} else {
|
|
3871
|
+
deposits.push(deposit);
|
|
3872
|
+
}
|
|
3873
|
+
storage.setItem(PENDING_DEPOSITS_KEY, JSON.stringify(deposits));
|
|
3874
|
+
}
|
|
3875
|
+
function loadPendingDeposits() {
|
|
3876
|
+
const storage = getStorage();
|
|
3877
|
+
if (!storage) return [];
|
|
3878
|
+
const stored = storage.getItem(PENDING_DEPOSITS_KEY);
|
|
3879
|
+
if (!stored) return [];
|
|
3880
|
+
try {
|
|
3881
|
+
return JSON.parse(stored);
|
|
3882
|
+
} catch {
|
|
3883
|
+
return [];
|
|
3884
|
+
}
|
|
3885
|
+
}
|
|
3886
|
+
function updatePendingDeposit(commitment, updates) {
|
|
3887
|
+
const storage = getStorage();
|
|
3888
|
+
if (!storage) return;
|
|
3889
|
+
const deposits = loadPendingDeposits();
|
|
3890
|
+
const index = deposits.findIndex((d) => d.note.commitment === commitment);
|
|
3891
|
+
if (index >= 0) {
|
|
3892
|
+
deposits[index] = { ...deposits[index], ...updates };
|
|
3893
|
+
storage.setItem(PENDING_DEPOSITS_KEY, JSON.stringify(deposits));
|
|
3894
|
+
}
|
|
3895
|
+
}
|
|
3896
|
+
function removePendingDeposit(commitment) {
|
|
3897
|
+
const storage = getStorage();
|
|
3898
|
+
if (!storage) return;
|
|
3899
|
+
const deposits = loadPendingDeposits();
|
|
3900
|
+
const filtered = deposits.filter((d) => d.note.commitment !== commitment);
|
|
3901
|
+
storage.setItem(PENDING_DEPOSITS_KEY, JSON.stringify(filtered));
|
|
3902
|
+
}
|
|
3903
|
+
function clearPendingDeposits() {
|
|
3904
|
+
const storage = getStorage();
|
|
3905
|
+
if (storage) {
|
|
3906
|
+
storage.removeItem(PENDING_DEPOSITS_KEY);
|
|
3907
|
+
}
|
|
3908
|
+
}
|
|
3909
|
+
function savePendingWithdrawal(withdrawal) {
|
|
3910
|
+
const storage = getStorage();
|
|
3911
|
+
if (!storage) {
|
|
3912
|
+
console.warn("localStorage not available - pending withdrawal not persisted");
|
|
3913
|
+
return;
|
|
3914
|
+
}
|
|
3915
|
+
const withdrawals = loadPendingWithdrawals();
|
|
3916
|
+
const index = withdrawals.findIndex((w) => w.requestId === withdrawal.requestId);
|
|
3917
|
+
if (index >= 0) {
|
|
3918
|
+
withdrawals[index] = withdrawal;
|
|
3919
|
+
} else {
|
|
3920
|
+
withdrawals.push(withdrawal);
|
|
3921
|
+
}
|
|
3922
|
+
storage.setItem(PENDING_WITHDRAWALS_KEY, JSON.stringify(withdrawals));
|
|
3923
|
+
}
|
|
3924
|
+
function loadPendingWithdrawals() {
|
|
3925
|
+
const storage = getStorage();
|
|
3926
|
+
if (!storage) return [];
|
|
3927
|
+
const stored = storage.getItem(PENDING_WITHDRAWALS_KEY);
|
|
3928
|
+
if (!stored) return [];
|
|
3929
|
+
try {
|
|
3930
|
+
return JSON.parse(stored);
|
|
3931
|
+
} catch {
|
|
3932
|
+
return [];
|
|
3933
|
+
}
|
|
3934
|
+
}
|
|
3935
|
+
function updatePendingWithdrawal(requestId, updates) {
|
|
3936
|
+
const storage = getStorage();
|
|
3937
|
+
if (!storage) return;
|
|
3938
|
+
const withdrawals = loadPendingWithdrawals();
|
|
3939
|
+
const index = withdrawals.findIndex((w) => w.requestId === requestId);
|
|
3940
|
+
if (index >= 0) {
|
|
3941
|
+
withdrawals[index] = { ...withdrawals[index], ...updates };
|
|
3942
|
+
storage.setItem(PENDING_WITHDRAWALS_KEY, JSON.stringify(withdrawals));
|
|
3943
|
+
}
|
|
3944
|
+
}
|
|
3945
|
+
function removePendingWithdrawal(requestId) {
|
|
3946
|
+
const storage = getStorage();
|
|
3947
|
+
if (!storage) return;
|
|
3948
|
+
const withdrawals = loadPendingWithdrawals();
|
|
3949
|
+
const filtered = withdrawals.filter((w) => w.requestId !== requestId);
|
|
3950
|
+
storage.setItem(PENDING_WITHDRAWALS_KEY, JSON.stringify(filtered));
|
|
3951
|
+
}
|
|
3952
|
+
function clearPendingWithdrawals() {
|
|
3953
|
+
const storage = getStorage();
|
|
3954
|
+
if (storage) {
|
|
3955
|
+
storage.removeItem(PENDING_WITHDRAWALS_KEY);
|
|
3956
|
+
}
|
|
3957
|
+
}
|
|
3958
|
+
function hasPendingOperations() {
|
|
3959
|
+
const deposits = loadPendingDeposits();
|
|
3960
|
+
const withdrawals = loadPendingWithdrawals();
|
|
3961
|
+
const pendingDeposits = deposits.filter(
|
|
3962
|
+
(d) => d.status === "pending" || d.status === "tx_sent"
|
|
3963
|
+
);
|
|
3964
|
+
const pendingWithdrawals = withdrawals.filter(
|
|
3965
|
+
(w) => w.status === "pending" || w.status === "processing"
|
|
3966
|
+
);
|
|
3967
|
+
return pendingDeposits.length > 0 || pendingWithdrawals.length > 0;
|
|
3968
|
+
}
|
|
3969
|
+
function getPendingOperationsSummary() {
|
|
3970
|
+
const deposits = loadPendingDeposits().filter(
|
|
3971
|
+
(d) => d.status === "pending" || d.status === "tx_sent"
|
|
3972
|
+
);
|
|
3973
|
+
const withdrawals = loadPendingWithdrawals().filter(
|
|
3974
|
+
(w) => w.status === "pending" || w.status === "processing"
|
|
3975
|
+
);
|
|
3976
|
+
return {
|
|
3977
|
+
deposits,
|
|
3978
|
+
withdrawals,
|
|
3979
|
+
totalPending: deposits.length + withdrawals.length
|
|
3980
|
+
};
|
|
3981
|
+
}
|
|
3982
|
+
function cleanupStalePendingOperations(maxAgeMs = 24 * 60 * 60 * 1e3) {
|
|
3983
|
+
const now = Date.now();
|
|
3984
|
+
let removedDeposits = 0;
|
|
3985
|
+
let removedWithdrawals = 0;
|
|
3986
|
+
const deposits = loadPendingDeposits();
|
|
3987
|
+
const activeDeposits = deposits.filter((d) => {
|
|
3988
|
+
const age = now - d.startedAt;
|
|
3989
|
+
const isStale = age > maxAgeMs;
|
|
3990
|
+
const isTerminal = d.status === "confirmed" || d.status === "failed";
|
|
3991
|
+
if (isStale || isTerminal) {
|
|
3992
|
+
removedDeposits++;
|
|
3993
|
+
return false;
|
|
3994
|
+
}
|
|
3995
|
+
return true;
|
|
3996
|
+
});
|
|
3997
|
+
const storage = getStorage();
|
|
3998
|
+
if (storage) {
|
|
3999
|
+
storage.setItem(PENDING_DEPOSITS_KEY, JSON.stringify(activeDeposits));
|
|
4000
|
+
}
|
|
4001
|
+
const withdrawals = loadPendingWithdrawals();
|
|
4002
|
+
const activeWithdrawals = withdrawals.filter((w) => {
|
|
4003
|
+
const age = now - w.startedAt;
|
|
4004
|
+
const isStale = age > maxAgeMs;
|
|
4005
|
+
const isTerminal = w.status === "completed" || w.status === "failed";
|
|
4006
|
+
if (isStale || isTerminal) {
|
|
4007
|
+
removedWithdrawals++;
|
|
4008
|
+
return false;
|
|
4009
|
+
}
|
|
4010
|
+
return true;
|
|
4011
|
+
});
|
|
4012
|
+
if (storage) {
|
|
4013
|
+
storage.setItem(PENDING_WITHDRAWALS_KEY, JSON.stringify(activeWithdrawals));
|
|
4014
|
+
}
|
|
4015
|
+
return { removedDeposits, removedWithdrawals };
|
|
4016
|
+
}
|
|
4017
|
+
|
|
3321
4018
|
// src/index.ts
|
|
3322
4019
|
var VERSION = "1.0.0";
|
|
3323
4020
|
export {
|
|
@@ -3325,13 +4022,14 @@ export {
|
|
|
3325
4022
|
CloakError,
|
|
3326
4023
|
CloakSDK,
|
|
3327
4024
|
DepositRecoveryService,
|
|
4025
|
+
EXPECTED_CIRCUIT_HASHES,
|
|
3328
4026
|
FIXED_FEE_LAMPORTS,
|
|
3329
4027
|
IndexerService,
|
|
3330
4028
|
LAMPORTS_PER_SOL,
|
|
3331
4029
|
LocalStorageAdapter,
|
|
3332
4030
|
MemoryStorageAdapter,
|
|
3333
|
-
ProverService,
|
|
3334
4031
|
RelayService,
|
|
4032
|
+
ShieldPoolErrors,
|
|
3335
4033
|
VARIABLE_FEE_RATE,
|
|
3336
4034
|
VERSION,
|
|
3337
4035
|
bigintToBytes32,
|
|
@@ -3339,6 +4037,9 @@ export {
|
|
|
3339
4037
|
bytesToHex,
|
|
3340
4038
|
calculateFee2 as calculateFee,
|
|
3341
4039
|
calculateRelayFee,
|
|
4040
|
+
cleanupStalePendingOperations,
|
|
4041
|
+
clearPendingDeposits,
|
|
4042
|
+
clearPendingWithdrawals,
|
|
3342
4043
|
computeCommitment,
|
|
3343
4044
|
computeMerkleRoot,
|
|
3344
4045
|
computeNullifier,
|
|
@@ -3378,12 +4079,14 @@ export {
|
|
|
3378
4079
|
getAddressExplorerUrl,
|
|
3379
4080
|
getDistributableAmount2 as getDistributableAmount,
|
|
3380
4081
|
getExplorerUrl,
|
|
4082
|
+
getPendingOperationsSummary,
|
|
3381
4083
|
getPublicKey,
|
|
3382
4084
|
getPublicViewKey,
|
|
3383
4085
|
getRecipientAmount,
|
|
3384
4086
|
getRpcUrlForNetwork,
|
|
3385
4087
|
getShieldPoolPDAs,
|
|
3386
4088
|
getViewKey,
|
|
4089
|
+
hasPendingOperations,
|
|
3387
4090
|
hexToBigint,
|
|
3388
4091
|
hexToBytes,
|
|
3389
4092
|
importKeys,
|
|
@@ -3393,7 +4096,10 @@ export {
|
|
|
3393
4096
|
isValidSolanaAddress,
|
|
3394
4097
|
isWithdrawable,
|
|
3395
4098
|
keypairToAdapter,
|
|
4099
|
+
loadPendingDeposits,
|
|
4100
|
+
loadPendingWithdrawals,
|
|
3396
4101
|
parseAmount,
|
|
4102
|
+
parseError,
|
|
3397
4103
|
parseNote,
|
|
3398
4104
|
parseTransactionError,
|
|
3399
4105
|
poseidonHash,
|
|
@@ -3402,6 +4108,10 @@ export {
|
|
|
3402
4108
|
proofToBytes,
|
|
3403
4109
|
pubkeyToLimbs,
|
|
3404
4110
|
randomBytes,
|
|
4111
|
+
removePendingDeposit,
|
|
4112
|
+
removePendingWithdrawal,
|
|
4113
|
+
savePendingDeposit,
|
|
4114
|
+
savePendingWithdrawal,
|
|
3405
4115
|
scanNotesForWallet,
|
|
3406
4116
|
sendTransaction,
|
|
3407
4117
|
serializeNote,
|
|
@@ -3409,10 +4119,14 @@ export {
|
|
|
3409
4119
|
splitTo2Limbs,
|
|
3410
4120
|
tryDecryptNote,
|
|
3411
4121
|
updateNoteWithDeposit,
|
|
4122
|
+
updatePendingDeposit,
|
|
4123
|
+
updatePendingWithdrawal,
|
|
3412
4124
|
validateDepositParams,
|
|
3413
4125
|
validateNote,
|
|
3414
4126
|
validateOutputsSum,
|
|
3415
4127
|
validateTransfers,
|
|
3416
4128
|
validateWalletConnected,
|
|
3417
|
-
validateWithdrawableNote
|
|
4129
|
+
validateWithdrawableNote,
|
|
4130
|
+
verifyAllCircuits,
|
|
4131
|
+
verifyCircuitIntegrity
|
|
3418
4132
|
};
|