@cloak.dev/sdk 0.1.7 → 0.1.8
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 +96 -183
- package/dist/index.cjs +2289 -288
- package/dist/index.d.cts +1502 -433
- package/dist/index.d.ts +1502 -433
- package/dist/index.js +2199 -249
- package/package.json +1 -2
- package/CHANGELOG.md +0 -173
package/dist/index.js
CHANGED
|
@@ -24,7 +24,10 @@ import {
|
|
|
24
24
|
// src/core/CloakSDK.ts
|
|
25
25
|
import {
|
|
26
26
|
Keypair,
|
|
27
|
-
PublicKey as
|
|
27
|
+
PublicKey as PublicKey4,
|
|
28
|
+
Transaction,
|
|
29
|
+
TransactionInstruction as TransactionInstruction2,
|
|
30
|
+
ComputeBudgetProgram
|
|
28
31
|
} from "@solana/web3.js";
|
|
29
32
|
|
|
30
33
|
// src/core/types.ts
|
|
@@ -82,6 +85,79 @@ function hexToBigint2(hex) {
|
|
|
82
85
|
const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
83
86
|
return BigInt("0x" + cleanHex);
|
|
84
87
|
}
|
|
88
|
+
async function computeCommitment2(amount, r, sk_spend) {
|
|
89
|
+
const [sk0, sk1] = splitTo2Limbs(sk_spend);
|
|
90
|
+
const [r0, r1] = splitTo2Limbs(r);
|
|
91
|
+
const pk_spend = await poseidonHash([sk0, sk1]);
|
|
92
|
+
return await poseidonHash([amount, r0, r1, pk_spend]);
|
|
93
|
+
}
|
|
94
|
+
async function generateCommitmentAsync(amountLamports, r, skSpend) {
|
|
95
|
+
const amount = BigInt(amountLamports);
|
|
96
|
+
const rValue = hexToBigint2(bytesToHex(r));
|
|
97
|
+
const skValue = hexToBigint2(bytesToHex(skSpend));
|
|
98
|
+
return await computeCommitment2(amount, rValue, skValue);
|
|
99
|
+
}
|
|
100
|
+
function generateCommitment(_amountLamports, _r, _skSpend) {
|
|
101
|
+
throw new Error("generateCommitment is deprecated. Use generateCommitmentAsync instead.");
|
|
102
|
+
}
|
|
103
|
+
async function computeNullifier2(sk_spend, leafIndex) {
|
|
104
|
+
const [sk0, sk1] = splitTo2Limbs(sk_spend);
|
|
105
|
+
return await poseidonHash([sk0, sk1, leafIndex]);
|
|
106
|
+
}
|
|
107
|
+
async function computeNullifierAsync(skSpend, leafIndex) {
|
|
108
|
+
const skValue = typeof skSpend === "string" ? hexToBigint2(skSpend) : hexToBigint2(bytesToHex(skSpend));
|
|
109
|
+
return await computeNullifier2(skValue, BigInt(leafIndex));
|
|
110
|
+
}
|
|
111
|
+
function computeNullifierSync(_skSpend, _leafIndex) {
|
|
112
|
+
throw new Error("computeNullifierSync is deprecated. Use computeNullifierAsync instead.");
|
|
113
|
+
}
|
|
114
|
+
async function computeOutputsHashAsync(outputs) {
|
|
115
|
+
let hash = 0n;
|
|
116
|
+
for (const output of outputs) {
|
|
117
|
+
const [lo, hi] = pubkeyToLimbs(output.recipient);
|
|
118
|
+
hash = await poseidonHash([hash, lo, hi, BigInt(output.amount)]);
|
|
119
|
+
}
|
|
120
|
+
return hash;
|
|
121
|
+
}
|
|
122
|
+
async function computeOutputsHash(outAddr, outAmount, outFlags) {
|
|
123
|
+
let hash = 0n;
|
|
124
|
+
for (let i = 0; i < 5; i++) {
|
|
125
|
+
if (outFlags[i] === 1) {
|
|
126
|
+
hash = await poseidonHash([hash, outAddr[i][0], outAddr[i][1], outAmount[i]]);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return hash;
|
|
130
|
+
}
|
|
131
|
+
function computeOutputsHashSync(_outputs) {
|
|
132
|
+
throw new Error("computeOutputsHashSync is deprecated. Use computeOutputsHashAsync instead.");
|
|
133
|
+
}
|
|
134
|
+
async function computeSwapOutputsHash(inputMintLimbs, outputMintLimbs, recipientAtaLimbs, minOutputAmount, publicAmount) {
|
|
135
|
+
return await poseidonHash([
|
|
136
|
+
inputMintLimbs[0],
|
|
137
|
+
inputMintLimbs[1],
|
|
138
|
+
outputMintLimbs[0],
|
|
139
|
+
outputMintLimbs[1],
|
|
140
|
+
recipientAtaLimbs[0],
|
|
141
|
+
recipientAtaLimbs[1],
|
|
142
|
+
minOutputAmount,
|
|
143
|
+
publicAmount
|
|
144
|
+
]);
|
|
145
|
+
}
|
|
146
|
+
async function computeSwapOutputsHashAsync(inputMint, outputMint, recipientAta, minOutputAmount, amount) {
|
|
147
|
+
const inputMintLimbs = pubkeyToLimbs(inputMint);
|
|
148
|
+
const outputMintLimbs = pubkeyToLimbs(outputMint);
|
|
149
|
+
const recipientAtaLimbs = pubkeyToLimbs(recipientAta);
|
|
150
|
+
return await computeSwapOutputsHash(
|
|
151
|
+
inputMintLimbs,
|
|
152
|
+
outputMintLimbs,
|
|
153
|
+
recipientAtaLimbs,
|
|
154
|
+
BigInt(minOutputAmount),
|
|
155
|
+
BigInt(amount)
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
function computeSwapOutputsHashSync(_outputMint, _recipientAta, _minOutputAmount, _amount) {
|
|
159
|
+
throw new Error("computeSwapOutputsHashSync is deprecated. Use computeSwapOutputsHashAsync instead.");
|
|
160
|
+
}
|
|
85
161
|
function bigintToBytes322(n) {
|
|
86
162
|
const hex = n.toString(16).padStart(64, "0");
|
|
87
163
|
const bytes = new Uint8Array(32);
|
|
@@ -354,11 +430,223 @@ function importKeys(exported) {
|
|
|
354
430
|
return generateCloakKeys(masterSeed);
|
|
355
431
|
}
|
|
356
432
|
|
|
433
|
+
// src/utils/network.ts
|
|
434
|
+
function detectNetworkFromRpcUrl(rpcUrl) {
|
|
435
|
+
const url = rpcUrl || process.env.NEXT_PUBLIC_SOLANA_RPC_URL || "";
|
|
436
|
+
const lowerUrl = url.toLowerCase();
|
|
437
|
+
if (lowerUrl.includes("mainnet") || lowerUrl.includes("api.mainnet-beta") || lowerUrl.includes("mainnet-beta")) {
|
|
438
|
+
return "mainnet";
|
|
439
|
+
}
|
|
440
|
+
if (lowerUrl.includes("testnet") || lowerUrl.includes("api.testnet")) {
|
|
441
|
+
return "testnet";
|
|
442
|
+
}
|
|
443
|
+
if (lowerUrl.includes("devnet") || lowerUrl.includes("api.devnet")) {
|
|
444
|
+
return "devnet";
|
|
445
|
+
}
|
|
446
|
+
if (lowerUrl.includes("localhost") || lowerUrl.includes("127.0.0.1") || lowerUrl.includes("local")) {
|
|
447
|
+
return "localnet";
|
|
448
|
+
}
|
|
449
|
+
return "mainnet";
|
|
450
|
+
}
|
|
451
|
+
function getRpcUrlForNetwork(network) {
|
|
452
|
+
switch (network) {
|
|
453
|
+
case "mainnet":
|
|
454
|
+
return "https://api.mainnet-beta.solana.com";
|
|
455
|
+
case "devnet":
|
|
456
|
+
return "https://api.devnet.solana.com";
|
|
457
|
+
case "localnet":
|
|
458
|
+
return "http://localhost:8899";
|
|
459
|
+
default:
|
|
460
|
+
return "https://api.mainnet-beta.solana.com";
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
function isValidRpcUrl(url) {
|
|
464
|
+
try {
|
|
465
|
+
const parsed = new URL(url);
|
|
466
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
467
|
+
} catch {
|
|
468
|
+
return false;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
function getExplorerUrl(signature, network = "devnet") {
|
|
472
|
+
const cluster = network === "mainnet" ? "" : `?cluster=${network}`;
|
|
473
|
+
return `https://explorer.solana.com/tx/${signature}${cluster}`;
|
|
474
|
+
}
|
|
475
|
+
function getAddressExplorerUrl(address, network = "devnet") {
|
|
476
|
+
const cluster = network === "mainnet" ? "" : `?cluster=${network}`;
|
|
477
|
+
return `https://explorer.solana.com/address/${address}${cluster}`;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// src/utils/fees.ts
|
|
481
|
+
var LAMPORTS_PER_SOL = 1e9;
|
|
482
|
+
var FIXED_FEE_LAMPORTS = 5e6;
|
|
483
|
+
var VARIABLE_FEE_NUMERATOR = 3;
|
|
484
|
+
var VARIABLE_FEE_DENOMINATOR = 1e3;
|
|
485
|
+
var VARIABLE_FEE_RATE = VARIABLE_FEE_NUMERATOR / VARIABLE_FEE_DENOMINATOR;
|
|
486
|
+
var MIN_DEPOSIT_LAMPORTS = 1e7;
|
|
487
|
+
function calculateFee(amountLamports) {
|
|
488
|
+
const variableFee = Math.floor(amountLamports * VARIABLE_FEE_NUMERATOR / VARIABLE_FEE_DENOMINATOR);
|
|
489
|
+
return FIXED_FEE_LAMPORTS + variableFee;
|
|
490
|
+
}
|
|
491
|
+
function calculateFeeBigint(amountLamports) {
|
|
492
|
+
const fixed = BigInt(FIXED_FEE_LAMPORTS);
|
|
493
|
+
const variable = amountLamports * BigInt(VARIABLE_FEE_NUMERATOR) / BigInt(VARIABLE_FEE_DENOMINATOR);
|
|
494
|
+
return fixed + variable;
|
|
495
|
+
}
|
|
496
|
+
function isWithdrawAmountSufficient(amountLamports) {
|
|
497
|
+
return amountLamports > calculateFeeBigint(amountLamports);
|
|
498
|
+
}
|
|
499
|
+
function getDistributableAmount(amountLamports) {
|
|
500
|
+
return amountLamports - calculateFee(amountLamports);
|
|
501
|
+
}
|
|
502
|
+
function formatAmount(lamports, decimals = 9) {
|
|
503
|
+
return (lamports / LAMPORTS_PER_SOL).toFixed(decimals);
|
|
504
|
+
}
|
|
505
|
+
function parseAmount(sol) {
|
|
506
|
+
const num = parseFloat(sol);
|
|
507
|
+
if (isNaN(num) || num < 0) {
|
|
508
|
+
throw new Error(`Invalid SOL amount: ${sol}`);
|
|
509
|
+
}
|
|
510
|
+
return Math.floor(num * LAMPORTS_PER_SOL);
|
|
511
|
+
}
|
|
512
|
+
function validateOutputsSum(outputs, expectedTotal) {
|
|
513
|
+
const sum = outputs.reduce((acc, out) => acc + out.amount, 0);
|
|
514
|
+
return sum === expectedTotal;
|
|
515
|
+
}
|
|
516
|
+
function calculateRelayFee(amountLamports, feeBps) {
|
|
517
|
+
if (feeBps < 0 || feeBps > 1e4) {
|
|
518
|
+
throw new Error("Fee basis points must be between 0 and 10000");
|
|
519
|
+
}
|
|
520
|
+
return Math.floor(amountLamports * feeBps / 1e4);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// src/core/note-manager.ts
|
|
524
|
+
async function generateNote(amountLamports, network) {
|
|
525
|
+
const actualNetwork = network || detectNetworkFromRpcUrl();
|
|
526
|
+
const skSpend = randomBytes(32);
|
|
527
|
+
const rBytes = randomBytes(32);
|
|
528
|
+
const commitmentBigint = await generateCommitmentAsync(amountLamports, rBytes, skSpend);
|
|
529
|
+
const commitmentHex = commitmentBigint.toString(16).padStart(64, "0");
|
|
530
|
+
const skSpendHex = bytesToHex(skSpend);
|
|
531
|
+
const rHex = bytesToHex(rBytes);
|
|
532
|
+
return {
|
|
533
|
+
version: "1.0",
|
|
534
|
+
amount: amountLamports,
|
|
535
|
+
commitment: commitmentHex,
|
|
536
|
+
sk_spend: skSpendHex,
|
|
537
|
+
r: rHex,
|
|
538
|
+
timestamp: Date.now(),
|
|
539
|
+
network: actualNetwork
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
async function generateNoteFromWallet(amountLamports, keys, network) {
|
|
543
|
+
const actualNetwork = network || detectNetworkFromRpcUrl();
|
|
544
|
+
const rBytes = randomBytes(32);
|
|
545
|
+
const sk_spend = hexToBytes(keys.spend.sk_spend_hex);
|
|
546
|
+
const commitmentBigint = await generateCommitmentAsync(amountLamports, rBytes, sk_spend);
|
|
547
|
+
const commitmentHex = commitmentBigint.toString(16).padStart(64, "0");
|
|
548
|
+
return {
|
|
549
|
+
version: "2.0",
|
|
550
|
+
amount: amountLamports,
|
|
551
|
+
commitment: commitmentHex,
|
|
552
|
+
sk_spend: keys.spend.sk_spend_hex,
|
|
553
|
+
r: bytesToHex(rBytes),
|
|
554
|
+
timestamp: Date.now(),
|
|
555
|
+
network: actualNetwork
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
function parseNote(jsonString) {
|
|
559
|
+
const note = JSON.parse(jsonString);
|
|
560
|
+
if (!note.version || !note.amount || !note.commitment || !note.sk_spend || !note.r) {
|
|
561
|
+
throw new CloakError(
|
|
562
|
+
"Invalid note format: missing required fields",
|
|
563
|
+
"validation",
|
|
564
|
+
false
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
if (!/^[0-9a-f]{64}$/i.test(note.commitment)) {
|
|
568
|
+
throw new CloakError("Invalid commitment format", "validation", false);
|
|
569
|
+
}
|
|
570
|
+
if (!/^[0-9a-f]{64}$/i.test(note.sk_spend)) {
|
|
571
|
+
throw new CloakError("Invalid sk_spend format", "validation", false);
|
|
572
|
+
}
|
|
573
|
+
if (!/^[0-9a-f]{64}$/i.test(note.r)) {
|
|
574
|
+
throw new CloakError("Invalid r format", "validation", false);
|
|
575
|
+
}
|
|
576
|
+
return note;
|
|
577
|
+
}
|
|
578
|
+
function exportNote(note, pretty = false) {
|
|
579
|
+
return pretty ? JSON.stringify(note, null, 2) : JSON.stringify(note);
|
|
580
|
+
}
|
|
581
|
+
function isWithdrawable(note) {
|
|
582
|
+
return !!(note.depositSignature && note.leafIndex !== void 0 && note.root);
|
|
583
|
+
}
|
|
584
|
+
function updateNoteWithDeposit(note, depositInfo) {
|
|
585
|
+
const updated = {
|
|
586
|
+
...note,
|
|
587
|
+
depositSignature: depositInfo.signature,
|
|
588
|
+
depositSlot: depositInfo.slot,
|
|
589
|
+
leafIndex: depositInfo.leafIndex,
|
|
590
|
+
root: depositInfo.root
|
|
591
|
+
};
|
|
592
|
+
if (depositInfo.merkleProof) {
|
|
593
|
+
updated.merkleProof = depositInfo.merkleProof;
|
|
594
|
+
}
|
|
595
|
+
return updated;
|
|
596
|
+
}
|
|
597
|
+
function findNoteByCommitment(notes, commitment) {
|
|
598
|
+
return notes.find((n) => n.commitment === commitment);
|
|
599
|
+
}
|
|
600
|
+
function filterNotesByNetwork(notes, network) {
|
|
601
|
+
return notes.filter((n) => n.network === network);
|
|
602
|
+
}
|
|
603
|
+
function filterWithdrawableNotes(notes) {
|
|
604
|
+
return notes.filter(isWithdrawable);
|
|
605
|
+
}
|
|
606
|
+
function exportWalletKeys(keys) {
|
|
607
|
+
return exportKeys(keys);
|
|
608
|
+
}
|
|
609
|
+
function importWalletKeys(keysJson) {
|
|
610
|
+
return importKeys(keysJson);
|
|
611
|
+
}
|
|
612
|
+
function getPublicViewKey(keys) {
|
|
613
|
+
return keys.view.pvk_hex;
|
|
614
|
+
}
|
|
615
|
+
function getViewKey(keys) {
|
|
616
|
+
return keys.view;
|
|
617
|
+
}
|
|
618
|
+
var calculateFee2 = calculateFee;
|
|
619
|
+
function getDistributableAmount2(amountLamports) {
|
|
620
|
+
return amountLamports - calculateFee2(amountLamports);
|
|
621
|
+
}
|
|
622
|
+
function getRecipientAmount(amountLamports) {
|
|
623
|
+
return getDistributableAmount2(amountLamports);
|
|
624
|
+
}
|
|
625
|
+
|
|
357
626
|
// src/core/storage.ts
|
|
358
627
|
var MemoryStorageAdapter = class {
|
|
359
628
|
constructor() {
|
|
629
|
+
this.notes = /* @__PURE__ */ new Map();
|
|
360
630
|
this.keys = null;
|
|
361
631
|
}
|
|
632
|
+
saveNote(note) {
|
|
633
|
+
this.notes.set(note.commitment, note);
|
|
634
|
+
}
|
|
635
|
+
loadAllNotes() {
|
|
636
|
+
return Array.from(this.notes.values());
|
|
637
|
+
}
|
|
638
|
+
updateNote(commitment, updates) {
|
|
639
|
+
const existing = this.notes.get(commitment);
|
|
640
|
+
if (existing) {
|
|
641
|
+
this.notes.set(commitment, { ...existing, ...updates });
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
deleteNote(commitment) {
|
|
645
|
+
this.notes.delete(commitment);
|
|
646
|
+
}
|
|
647
|
+
clearAllNotes() {
|
|
648
|
+
this.notes.clear();
|
|
649
|
+
}
|
|
362
650
|
saveKeys(keys) {
|
|
363
651
|
this.keys = keys;
|
|
364
652
|
}
|
|
@@ -369,10 +657,10 @@ var MemoryStorageAdapter = class {
|
|
|
369
657
|
this.keys = null;
|
|
370
658
|
}
|
|
371
659
|
};
|
|
372
|
-
var
|
|
373
|
-
constructor(keysKey = "cloak_wallet_keys") {
|
|
660
|
+
var LocalStorageAdapter = class {
|
|
661
|
+
constructor(notesKey = "cloak_notes", keysKey = "cloak_wallet_keys") {
|
|
662
|
+
this.notesKey = notesKey;
|
|
374
663
|
this.keysKey = keysKey;
|
|
375
|
-
_LocalStorageAdapter.runLegacyPurgeOnce();
|
|
376
664
|
}
|
|
377
665
|
getStorage() {
|
|
378
666
|
if (typeof globalThis !== "undefined" && globalThis.localStorage) {
|
|
@@ -380,6 +668,47 @@ var _LocalStorageAdapter = class _LocalStorageAdapter {
|
|
|
380
668
|
}
|
|
381
669
|
return null;
|
|
382
670
|
}
|
|
671
|
+
saveNote(note) {
|
|
672
|
+
const storage = this.getStorage();
|
|
673
|
+
if (!storage) throw new Error("localStorage not available");
|
|
674
|
+
const notes = this.loadAllNotes();
|
|
675
|
+
notes.push(note);
|
|
676
|
+
storage.setItem(this.notesKey, JSON.stringify(notes));
|
|
677
|
+
}
|
|
678
|
+
loadAllNotes() {
|
|
679
|
+
const storage = this.getStorage();
|
|
680
|
+
if (!storage) return [];
|
|
681
|
+
const stored = storage.getItem(this.notesKey);
|
|
682
|
+
if (!stored) return [];
|
|
683
|
+
try {
|
|
684
|
+
return JSON.parse(stored);
|
|
685
|
+
} catch {
|
|
686
|
+
return [];
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
updateNote(commitment, updates) {
|
|
690
|
+
const storage = this.getStorage();
|
|
691
|
+
if (!storage) return;
|
|
692
|
+
const notes = this.loadAllNotes();
|
|
693
|
+
const index = notes.findIndex((n) => n.commitment === commitment);
|
|
694
|
+
if (index !== -1) {
|
|
695
|
+
notes[index] = { ...notes[index], ...updates };
|
|
696
|
+
storage.setItem(this.notesKey, JSON.stringify(notes));
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
deleteNote(commitment) {
|
|
700
|
+
const storage = this.getStorage();
|
|
701
|
+
if (!storage) return;
|
|
702
|
+
const notes = this.loadAllNotes();
|
|
703
|
+
const filtered = notes.filter((n) => n.commitment !== commitment);
|
|
704
|
+
storage.setItem(this.notesKey, JSON.stringify(filtered));
|
|
705
|
+
}
|
|
706
|
+
clearAllNotes() {
|
|
707
|
+
const storage = this.getStorage();
|
|
708
|
+
if (storage) {
|
|
709
|
+
storage.removeItem(this.notesKey);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
383
712
|
saveKeys(keys) {
|
|
384
713
|
const storage = this.getStorage();
|
|
385
714
|
if (!storage) throw new Error("localStorage not available");
|
|
@@ -402,64 +731,126 @@ var _LocalStorageAdapter = class _LocalStorageAdapter {
|
|
|
402
731
|
storage.removeItem(this.keysKey);
|
|
403
732
|
}
|
|
404
733
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
// src/utils/validation.ts
|
|
737
|
+
import { PublicKey } from "@solana/web3.js";
|
|
738
|
+
function isValidSolanaAddress(address) {
|
|
739
|
+
try {
|
|
740
|
+
new PublicKey(address);
|
|
741
|
+
return true;
|
|
742
|
+
} catch {
|
|
743
|
+
return false;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
function validateNote(note) {
|
|
747
|
+
if (!note || typeof note !== "object") {
|
|
748
|
+
throw new Error("Note must be an object");
|
|
749
|
+
}
|
|
750
|
+
const requiredFields = ["version", "amount", "commitment", "sk_spend", "r", "timestamp", "network"];
|
|
751
|
+
for (const field of requiredFields) {
|
|
752
|
+
if (!(field in note)) {
|
|
753
|
+
throw new Error(`Missing required field: ${field}`);
|
|
414
754
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
755
|
+
}
|
|
756
|
+
if (typeof note.version !== "string") {
|
|
757
|
+
throw new Error("Version must be a string");
|
|
758
|
+
}
|
|
759
|
+
if (typeof note.amount !== "number" || note.amount <= 0) {
|
|
760
|
+
throw new Error("Amount must be a positive number");
|
|
761
|
+
}
|
|
762
|
+
if (!isValidHex(note.commitment, 32)) {
|
|
763
|
+
throw new Error("Invalid commitment format (expected 64 hex characters)");
|
|
764
|
+
}
|
|
765
|
+
if (!isValidHex(note.sk_spend, 32)) {
|
|
766
|
+
throw new Error("Invalid sk_spend format (expected 64 hex characters)");
|
|
767
|
+
}
|
|
768
|
+
if (!isValidHex(note.r, 32)) {
|
|
769
|
+
throw new Error("Invalid r format (expected 64 hex characters)");
|
|
770
|
+
}
|
|
771
|
+
if (typeof note.timestamp !== "number" || note.timestamp <= 0) {
|
|
772
|
+
throw new Error("Timestamp must be a positive number");
|
|
773
|
+
}
|
|
774
|
+
if (!["localnet", "devnet", "testnet", "mainnet"].includes(note.network)) {
|
|
775
|
+
throw new Error("Network must be localnet, devnet, testnet, or mainnet");
|
|
776
|
+
}
|
|
777
|
+
if (note.depositSignature !== void 0 && typeof note.depositSignature !== "string") {
|
|
778
|
+
throw new Error("Deposit signature must be a string");
|
|
779
|
+
}
|
|
780
|
+
if (note.depositSlot !== void 0 && typeof note.depositSlot !== "number") {
|
|
781
|
+
throw new Error("Deposit slot must be a number");
|
|
782
|
+
}
|
|
783
|
+
if (note.leafIndex !== void 0) {
|
|
784
|
+
if (typeof note.leafIndex !== "number" || note.leafIndex < 0) {
|
|
785
|
+
throw new Error("Leaf index must be a non-negative number");
|
|
418
786
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
787
|
+
}
|
|
788
|
+
if (note.root !== void 0 && !isValidHex(note.root, 32)) {
|
|
789
|
+
throw new Error("Invalid root format (expected 64 hex characters)");
|
|
790
|
+
}
|
|
791
|
+
if (note.merkleProof !== void 0) {
|
|
792
|
+
if (!Array.isArray(note.merkleProof.pathElements)) {
|
|
793
|
+
throw new Error("Merkle proof pathElements must be an array");
|
|
794
|
+
}
|
|
795
|
+
if (!Array.isArray(note.merkleProof.pathIndices)) {
|
|
796
|
+
throw new Error("Merkle proof pathIndices must be an array");
|
|
797
|
+
}
|
|
798
|
+
if (note.merkleProof.pathElements.length !== note.merkleProof.pathIndices.length) {
|
|
799
|
+
throw new Error("Merkle proof pathElements and pathIndices must have same length");
|
|
423
800
|
}
|
|
424
801
|
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
802
|
+
}
|
|
803
|
+
function parseNote2(jsonString) {
|
|
804
|
+
let parsed;
|
|
805
|
+
try {
|
|
806
|
+
parsed = JSON.parse(jsonString);
|
|
807
|
+
} catch (error) {
|
|
808
|
+
throw new Error("Invalid JSON format");
|
|
809
|
+
}
|
|
810
|
+
validateNote(parsed);
|
|
811
|
+
return parsed;
|
|
812
|
+
}
|
|
813
|
+
function validateWithdrawableNote(note) {
|
|
814
|
+
if (!note.depositSignature) {
|
|
815
|
+
throw new Error("Note must be deposited before withdrawal (missing depositSignature)");
|
|
816
|
+
}
|
|
817
|
+
if (note.leafIndex === void 0) {
|
|
818
|
+
throw new Error("Note must be deposited before withdrawal (missing leafIndex)");
|
|
819
|
+
}
|
|
820
|
+
if (!note.root) {
|
|
821
|
+
throw new Error("Note must have historical root for withdrawal");
|
|
822
|
+
}
|
|
823
|
+
if (!note.merkleProof) {
|
|
824
|
+
throw new Error("Note must have Merkle proof for withdrawal");
|
|
825
|
+
}
|
|
826
|
+
if (note.merkleProof.pathElements.length === 0) {
|
|
827
|
+
throw new Error("Merkle proof is empty");
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
function validateTransfers(recipients, totalAmount) {
|
|
831
|
+
if (recipients.length === 0) {
|
|
832
|
+
throw new Error("At least one recipient is required");
|
|
833
|
+
}
|
|
834
|
+
if (recipients.length > 5) {
|
|
835
|
+
throw new Error("Maximum 5 recipients allowed");
|
|
836
|
+
}
|
|
837
|
+
for (let i = 0; i < recipients.length; i++) {
|
|
838
|
+
const transfer2 = recipients[i];
|
|
839
|
+
const isPublicKeyLike = transfer2.recipient && typeof transfer2.recipient.toBase58 === "function" && typeof transfer2.recipient.toBuffer === "function";
|
|
840
|
+
if (!isPublicKeyLike) {
|
|
841
|
+
throw new Error(`Recipient ${i} must be a PublicKey (got ${typeof transfer2.recipient})`);
|
|
842
|
+
}
|
|
843
|
+
if (typeof transfer2.amount !== "number" || transfer2.amount <= 0) {
|
|
844
|
+
throw new Error(`Recipient ${i} amount must be a positive number`);
|
|
445
845
|
}
|
|
446
|
-
globalThis.localStorage.removeItem(notesKey);
|
|
447
846
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
* Default localStorage key the legacy `<= 0.1.5` SDK used for the
|
|
456
|
-
* plaintext `CloakNote[]` blob.
|
|
457
|
-
*/
|
|
458
|
-
_LocalStorageAdapter.LEGACY_NOTES_KEY = "cloak_notes";
|
|
459
|
-
var LocalStorageAdapter = _LocalStorageAdapter;
|
|
460
|
-
|
|
461
|
-
// src/services/RelayService.ts
|
|
462
|
-
import { sha256 } from "@noble/hashes/sha2";
|
|
847
|
+
const sum = recipients.reduce((acc, t) => acc + t.amount, 0);
|
|
848
|
+
if (sum !== totalAmount) {
|
|
849
|
+
throw new Error(
|
|
850
|
+
`Recipients sum (${sum}) does not match expected total (${totalAmount})`
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
463
854
|
|
|
464
855
|
// src/utils/errors.ts
|
|
465
856
|
var RootNotFoundError = class extends Error {
|
|
@@ -519,7 +910,8 @@ function classifyRelayError(responseText, httpStatus) {
|
|
|
519
910
|
inner = responseText;
|
|
520
911
|
}
|
|
521
912
|
const haystack = inner.toLowerCase();
|
|
522
|
-
if (haystack.includes("0x1020") ||
|
|
913
|
+
if (haystack.includes("0x1020") || // 0x1020 as decimal in a JSON-stringified confirmTransaction err (`"Custom":4128`).
|
|
914
|
+
haystack.includes('"custom":4128') || haystack.includes("doublespend")) {
|
|
523
915
|
return new UtxoAlreadySpentError(
|
|
524
916
|
"This shielded balance was already spent. The UTXO's nullifier is registered on-chain \u2014 typically means you spent it via another session or device. Run verifyUtxos() to reconcile local state."
|
|
525
917
|
);
|
|
@@ -1355,6 +1747,7 @@ function formatErrorForLogging(error) {
|
|
|
1355
1747
|
}
|
|
1356
1748
|
|
|
1357
1749
|
// src/services/RelayService.ts
|
|
1750
|
+
import { sha256 } from "@noble/hashes/sha2";
|
|
1358
1751
|
var RelayService = class {
|
|
1359
1752
|
/**
|
|
1360
1753
|
* Create a new Relay Service client
|
|
@@ -1723,23 +2116,91 @@ var RelayService = class {
|
|
|
1723
2116
|
}
|
|
1724
2117
|
};
|
|
1725
2118
|
|
|
2119
|
+
// src/solana/instructions.ts
|
|
2120
|
+
import {
|
|
2121
|
+
PublicKey as PublicKey2,
|
|
2122
|
+
SystemProgram,
|
|
2123
|
+
TransactionInstruction
|
|
2124
|
+
} from "@solana/web3.js";
|
|
2125
|
+
function createDepositInstruction(params) {
|
|
2126
|
+
if (params.commitment.length !== 32) {
|
|
2127
|
+
throw new Error(
|
|
2128
|
+
`Invalid commitment length: ${params.commitment.length} (expected 32 bytes)`
|
|
2129
|
+
);
|
|
2130
|
+
}
|
|
2131
|
+
if (params.amount <= 0) {
|
|
2132
|
+
throw new Error("Amount must be positive");
|
|
2133
|
+
}
|
|
2134
|
+
const discriminant = new Uint8Array([1]);
|
|
2135
|
+
const amountBytes = new Uint8Array(8);
|
|
2136
|
+
new DataView(amountBytes.buffer).setBigUint64(
|
|
2137
|
+
0,
|
|
2138
|
+
BigInt(params.amount),
|
|
2139
|
+
true
|
|
2140
|
+
// little-endian
|
|
2141
|
+
);
|
|
2142
|
+
const data = new Uint8Array(41);
|
|
2143
|
+
data.set(discriminant, 0);
|
|
2144
|
+
data.set(amountBytes, 1);
|
|
2145
|
+
data.set(params.commitment, 9);
|
|
2146
|
+
return new TransactionInstruction({
|
|
2147
|
+
programId: params.programId,
|
|
2148
|
+
keys: [
|
|
2149
|
+
// Account 0: Payer (signer, writable) - pays for transaction
|
|
2150
|
+
{ pubkey: params.payer, isSigner: true, isWritable: true },
|
|
2151
|
+
// Account 1: Pool (writable) - receives SOL
|
|
2152
|
+
{ pubkey: params.pool, isSigner: false, isWritable: true },
|
|
2153
|
+
// Account 2: System Program (readonly) - for transfers
|
|
2154
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
2155
|
+
// Account 3: Merkle Tree (writable) - stores on-chain Merkle tree
|
|
2156
|
+
{ pubkey: params.merkleTree, isSigner: false, isWritable: true }
|
|
2157
|
+
],
|
|
2158
|
+
data: Buffer.from(data)
|
|
2159
|
+
});
|
|
2160
|
+
}
|
|
2161
|
+
function validateDepositParams(params) {
|
|
2162
|
+
if (!(params.programId instanceof PublicKey2)) {
|
|
2163
|
+
throw new Error("programId must be a PublicKey");
|
|
2164
|
+
}
|
|
2165
|
+
if (!(params.payer instanceof PublicKey2)) {
|
|
2166
|
+
throw new Error("payer must be a PublicKey");
|
|
2167
|
+
}
|
|
2168
|
+
if (!(params.pool instanceof PublicKey2)) {
|
|
2169
|
+
throw new Error("pool must be a PublicKey");
|
|
2170
|
+
}
|
|
2171
|
+
if (!(params.merkleTree instanceof PublicKey2)) {
|
|
2172
|
+
throw new Error("merkleTree must be a PublicKey");
|
|
2173
|
+
}
|
|
2174
|
+
if (typeof params.amount !== "number" || params.amount <= 0) {
|
|
2175
|
+
throw new Error("amount must be a positive number");
|
|
2176
|
+
}
|
|
2177
|
+
if (!(params.commitment instanceof Uint8Array)) {
|
|
2178
|
+
throw new Error("commitment must be a Uint8Array");
|
|
2179
|
+
}
|
|
2180
|
+
if (params.commitment.length !== 32) {
|
|
2181
|
+
throw new Error(
|
|
2182
|
+
`commitment must be 32 bytes (got ${params.commitment.length})`
|
|
2183
|
+
);
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
|
|
1726
2187
|
// src/utils/pda.ts
|
|
1727
|
-
import { PublicKey } from "@solana/web3.js";
|
|
2188
|
+
import { PublicKey as PublicKey3 } from "@solana/web3.js";
|
|
1728
2189
|
function getShieldPoolPDAs(programId, mint = NATIVE_SOL_MINT) {
|
|
1729
2190
|
const pid = programId || CLOAK_PROGRAM_ID;
|
|
1730
|
-
const [pool] =
|
|
2191
|
+
const [pool] = PublicKey3.findProgramAddressSync(
|
|
1731
2192
|
[Buffer.from("pool"), mint.toBuffer()],
|
|
1732
2193
|
pid
|
|
1733
2194
|
);
|
|
1734
|
-
const [merkleTree] =
|
|
2195
|
+
const [merkleTree] = PublicKey3.findProgramAddressSync(
|
|
1735
2196
|
[Buffer.from("merkle_tree"), mint.toBuffer()],
|
|
1736
2197
|
pid
|
|
1737
2198
|
);
|
|
1738
|
-
const [treasury] =
|
|
2199
|
+
const [treasury] = PublicKey3.findProgramAddressSync(
|
|
1739
2200
|
[Buffer.from("treasury"), mint.toBuffer()],
|
|
1740
2201
|
pid
|
|
1741
2202
|
);
|
|
1742
|
-
const [vaultAuthority] =
|
|
2203
|
+
const [vaultAuthority] = PublicKey3.findProgramAddressSync(
|
|
1743
2204
|
[Buffer.from("vault_authority"), mint.toBuffer()],
|
|
1744
2205
|
pid
|
|
1745
2206
|
);
|
|
@@ -1755,7 +2216,7 @@ function getNullifierPDA(poolPubkey, nullifier, programId) {
|
|
|
1755
2216
|
if (nullifier.length !== 32) {
|
|
1756
2217
|
throw new Error(`Nullifier must be 32 bytes, got ${nullifier.length}`);
|
|
1757
2218
|
}
|
|
1758
|
-
return
|
|
2219
|
+
return PublicKey3.findProgramAddressSync(
|
|
1759
2220
|
[Buffer.from("nullifier"), poolPubkey.toBuffer(), Buffer.from(nullifier)],
|
|
1760
2221
|
pid
|
|
1761
2222
|
);
|
|
@@ -1765,29 +2226,273 @@ function getSwapStatePDA(poolPubkey, nullifier, programId) {
|
|
|
1765
2226
|
if (nullifier.length !== 32) {
|
|
1766
2227
|
throw new Error(`Nullifier must be 32 bytes, got ${nullifier.length}`);
|
|
1767
2228
|
}
|
|
1768
|
-
return
|
|
2229
|
+
return PublicKey3.findProgramAddressSync(
|
|
1769
2230
|
[Buffer.from("swap_state"), poolPubkey.toBuffer(), Buffer.from(nullifier)],
|
|
1770
2231
|
pid
|
|
1771
2232
|
);
|
|
1772
2233
|
}
|
|
1773
2234
|
|
|
1774
|
-
// src/utils/
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
2235
|
+
// src/utils/proof-generation.ts
|
|
2236
|
+
import * as snarkjs from "snarkjs";
|
|
2237
|
+
var IS_REACT_NATIVE = typeof navigator !== "undefined" && navigator.product === "ReactNative";
|
|
2238
|
+
var IS_BROWSER = IS_REACT_NATIVE || typeof window !== "undefined" || typeof globalThis !== "undefined" && typeof globalThis.document !== "undefined";
|
|
2239
|
+
var DEFAULT_CIRCUITS_URL = "https://storage.googleapis.com/cloak-circuits/circuits/0.1.0";
|
|
2240
|
+
var SKIP_CIRCUIT_VERIFY_ENV = typeof process !== "undefined" && process.env && process.env.CLOAK_SKIP_CIRCUIT_INTEGRITY_CHECK === "1";
|
|
2241
|
+
function isUrl(path) {
|
|
2242
|
+
return path.startsWith("http://") || path.startsWith("https://");
|
|
2243
|
+
}
|
|
2244
|
+
function resolveCircuitsUrl(circuitsPath2) {
|
|
2245
|
+
if (circuitsPath2.trim() !== "" && circuitsPath2.trim() !== DEFAULT_CIRCUITS_URL) {
|
|
2246
|
+
return DEFAULT_CIRCUITS_URL;
|
|
1779
2247
|
}
|
|
1780
|
-
return
|
|
2248
|
+
return DEFAULT_CIRCUITS_URL;
|
|
1781
2249
|
}
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
2250
|
+
async function fetchFileAsBuffer(url) {
|
|
2251
|
+
const response = await fetch(url);
|
|
2252
|
+
if (!response.ok) {
|
|
2253
|
+
throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
|
|
2254
|
+
}
|
|
2255
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
2256
|
+
return new Uint8Array(arrayBuffer);
|
|
1785
2257
|
}
|
|
1786
|
-
function
|
|
1787
|
-
|
|
2258
|
+
async function sha256Hex(data) {
|
|
2259
|
+
if (IS_BROWSER) {
|
|
2260
|
+
const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
2261
|
+
const buffer = bytes.buffer.slice(
|
|
2262
|
+
bytes.byteOffset,
|
|
2263
|
+
bytes.byteOffset + bytes.byteLength
|
|
2264
|
+
);
|
|
2265
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", buffer);
|
|
2266
|
+
return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2267
|
+
}
|
|
2268
|
+
const nodeCrypto = await getNodeCrypto();
|
|
2269
|
+
return nodeCrypto.createHash("sha256").update(data).digest("hex");
|
|
1788
2270
|
}
|
|
1789
|
-
var
|
|
1790
|
-
|
|
2271
|
+
var _nodeCrypto = null;
|
|
2272
|
+
function nodeRequire(moduleName) {
|
|
2273
|
+
if (IS_BROWSER) {
|
|
2274
|
+
throw new Error(`Node.js ${moduleName} module not available in browser/React Native`);
|
|
2275
|
+
}
|
|
2276
|
+
try {
|
|
2277
|
+
const requireFunc = new Function("moduleName", "return require(moduleName)");
|
|
2278
|
+
return requireFunc(moduleName);
|
|
2279
|
+
} catch {
|
|
2280
|
+
throw new Error(`Cannot load Node.js module ${moduleName} synchronously in ESM.`);
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
async function getNodeCrypto() {
|
|
2284
|
+
if (_nodeCrypto) return _nodeCrypto;
|
|
2285
|
+
_nodeCrypto = nodeRequire("crypto");
|
|
2286
|
+
return _nodeCrypto;
|
|
2287
|
+
}
|
|
2288
|
+
async function generateWithdrawRegularProof(inputs, circuitsPath2) {
|
|
2289
|
+
const circuitsUrl = resolveCircuitsUrl(circuitsPath2);
|
|
2290
|
+
let wasmInput;
|
|
2291
|
+
let zkeyInput;
|
|
2292
|
+
if (IS_BROWSER) {
|
|
2293
|
+
wasmInput = `${circuitsUrl}/withdraw_regular_js/withdraw_regular.wasm`;
|
|
2294
|
+
zkeyInput = `${circuitsUrl}/withdraw_regular_final.zkey`;
|
|
2295
|
+
} else {
|
|
2296
|
+
const wasmUrl = `${circuitsUrl}/withdraw_regular_js/withdraw_regular.wasm`;
|
|
2297
|
+
const zkeyUrl = `${circuitsUrl}/withdraw_regular_final.zkey`;
|
|
2298
|
+
const [wasmData, zkeyData] = await Promise.all([
|
|
2299
|
+
fetchFileAsBuffer(wasmUrl),
|
|
2300
|
+
fetchFileAsBuffer(zkeyUrl)
|
|
2301
|
+
]);
|
|
2302
|
+
wasmInput = wasmData;
|
|
2303
|
+
zkeyInput = zkeyData;
|
|
2304
|
+
}
|
|
2305
|
+
const verification = await verifyCircuitIntegrity(circuitsUrl, "withdraw_regular");
|
|
2306
|
+
if (!verification.valid) {
|
|
2307
|
+
throw new Error(
|
|
2308
|
+
`withdraw_regular circuit integrity verification failed: ${verification.error ?? "hash mismatch"}`
|
|
2309
|
+
);
|
|
2310
|
+
}
|
|
2311
|
+
const circuitInputs = {
|
|
2312
|
+
// Public signals
|
|
2313
|
+
root: inputs.root.toString(),
|
|
2314
|
+
nullifier: inputs.nullifier.toString(),
|
|
2315
|
+
outputs_hash: inputs.outputs_hash.toString(),
|
|
2316
|
+
public_amount: inputs.public_amount.toString(),
|
|
2317
|
+
// Private common inputs
|
|
2318
|
+
amount: inputs.amount.toString(),
|
|
2319
|
+
leaf_index: inputs.leaf_index.toString(),
|
|
2320
|
+
sk: inputs.sk.map((x) => x.toString()),
|
|
2321
|
+
r: inputs.r.map((x) => x.toString()),
|
|
2322
|
+
pathElements: inputs.pathElements.map((x) => x.toString()),
|
|
2323
|
+
pathIndices: inputs.pathIndices,
|
|
2324
|
+
// Outputs
|
|
2325
|
+
num_outputs: inputs.num_outputs,
|
|
2326
|
+
out_addr: inputs.out_addr.map((addr) => addr.map((x) => x.toString())),
|
|
2327
|
+
out_amount: inputs.out_amount.map((x) => x.toString()),
|
|
2328
|
+
out_flags: inputs.out_flags,
|
|
2329
|
+
// Fee calculation
|
|
2330
|
+
var_fee: inputs.var_fee.toString(),
|
|
2331
|
+
rem: inputs.rem.toString()
|
|
2332
|
+
};
|
|
2333
|
+
const { proof, publicSignals } = await snarkjs.groth16.fullProve(circuitInputs, wasmInput, zkeyInput);
|
|
2334
|
+
const proofBytes = proofToBytes(proof);
|
|
2335
|
+
return {
|
|
2336
|
+
proof,
|
|
2337
|
+
publicSignals,
|
|
2338
|
+
proofBytes,
|
|
2339
|
+
publicInputsBytes: new Uint8Array(0)
|
|
2340
|
+
// Not used, kept for compatibility
|
|
2341
|
+
};
|
|
2342
|
+
}
|
|
2343
|
+
async function generateWithdrawSwapProof(inputs, circuitsPath2) {
|
|
2344
|
+
const circuitsUrl = resolveCircuitsUrl(circuitsPath2);
|
|
2345
|
+
let wasmInput;
|
|
2346
|
+
let zkeyInput;
|
|
2347
|
+
if (IS_BROWSER) {
|
|
2348
|
+
wasmInput = `${circuitsUrl}/withdraw_swap_js/withdraw_swap.wasm`;
|
|
2349
|
+
zkeyInput = `${circuitsUrl}/withdraw_swap_final.zkey`;
|
|
2350
|
+
} else {
|
|
2351
|
+
const wasmUrl = `${circuitsUrl}/withdraw_swap_js/withdraw_swap.wasm`;
|
|
2352
|
+
const zkeyUrl = `${circuitsUrl}/withdraw_swap_final.zkey`;
|
|
2353
|
+
const [wasmData, zkeyData] = await Promise.all([
|
|
2354
|
+
fetchFileAsBuffer(wasmUrl),
|
|
2355
|
+
fetchFileAsBuffer(zkeyUrl)
|
|
2356
|
+
]);
|
|
2357
|
+
wasmInput = wasmData;
|
|
2358
|
+
zkeyInput = zkeyData;
|
|
2359
|
+
}
|
|
2360
|
+
const verification = await verifyCircuitIntegrity(circuitsUrl, "withdraw_swap");
|
|
2361
|
+
if (!verification.valid) {
|
|
2362
|
+
throw new Error(
|
|
2363
|
+
`withdraw_swap circuit integrity verification failed: ${verification.error ?? "hash mismatch"}`
|
|
2364
|
+
);
|
|
2365
|
+
}
|
|
2366
|
+
const sk = splitTo2Limbs(inputs.sk_spend);
|
|
2367
|
+
const r = splitTo2Limbs(inputs.r);
|
|
2368
|
+
const circuitInputs = {
|
|
2369
|
+
// Public signals (must be provided for witness generation)
|
|
2370
|
+
root: inputs.root.toString(),
|
|
2371
|
+
nullifier: inputs.nullifier.toString(),
|
|
2372
|
+
outputs_hash: inputs.outputs_hash.toString(),
|
|
2373
|
+
public_amount: inputs.public_amount.toString(),
|
|
2374
|
+
// Private inputs
|
|
2375
|
+
sk: sk.map((x) => x.toString()),
|
|
2376
|
+
r: r.map((x) => x.toString()),
|
|
2377
|
+
amount: inputs.amount.toString(),
|
|
2378
|
+
leaf_index: inputs.leaf_index.toString(),
|
|
2379
|
+
pathElements: inputs.path_elements.map((x) => x.toString()),
|
|
2380
|
+
pathIndices: inputs.path_indices,
|
|
2381
|
+
input_mint: inputs.input_mint.map((x) => x.toString()),
|
|
2382
|
+
output_mint: inputs.output_mint.map((x) => x.toString()),
|
|
2383
|
+
recipient_ata: inputs.recipient_ata.map((x) => x.toString()),
|
|
2384
|
+
min_output_amount: inputs.min_output_amount.toString(),
|
|
2385
|
+
// Note: fee is computed off-chain and validated on-chain:
|
|
2386
|
+
// - fixed_fee is 5000000 (0.005 SOL)
|
|
2387
|
+
// - var_fee and rem are computed from amount * 3 / 1000 (0.3%)
|
|
2388
|
+
var_fee: inputs.var_fee.toString(),
|
|
2389
|
+
rem: inputs.rem.toString()
|
|
2390
|
+
};
|
|
2391
|
+
const { proof, publicSignals } = await snarkjs.groth16.fullProve(
|
|
2392
|
+
circuitInputs,
|
|
2393
|
+
wasmInput,
|
|
2394
|
+
zkeyInput
|
|
2395
|
+
);
|
|
2396
|
+
const proofBytes = proofToBytes(proof);
|
|
2397
|
+
return {
|
|
2398
|
+
proof,
|
|
2399
|
+
publicSignals,
|
|
2400
|
+
proofBytes,
|
|
2401
|
+
publicInputsBytes: new Uint8Array(0)
|
|
2402
|
+
// Not used, kept for compatibility
|
|
2403
|
+
};
|
|
2404
|
+
}
|
|
2405
|
+
async function areCircuitsAvailable(circuitsPath2) {
|
|
2406
|
+
if (!circuitsPath2 || circuitsPath2 === "") {
|
|
2407
|
+
return false;
|
|
2408
|
+
}
|
|
2409
|
+
return isUrl(resolveCircuitsUrl(circuitsPath2));
|
|
2410
|
+
}
|
|
2411
|
+
async function getDefaultCircuitsPath() {
|
|
2412
|
+
return DEFAULT_CIRCUITS_URL;
|
|
2413
|
+
}
|
|
2414
|
+
var EXPECTED_CIRCUIT_HASHES = {
|
|
2415
|
+
withdraw_regular_wasm: "b80c364c926332111945ef437b07a71720fae774e08206cb11b6171f2a2420a3",
|
|
2416
|
+
withdraw_regular_zkey: "6dbca2612f7cc257b897d93384ae9dd09c1617fd1668f8287efb1b124488d144",
|
|
2417
|
+
withdraw_swap_wasm: "a296badc0047c211a4724c96a4e64a77cd75fbde7cfa4cf23af3640a723e5686",
|
|
2418
|
+
withdraw_swap_zkey: "95251954feaac80396651d12a0df3288630fa75db9410aebecb8aaee17a4f6ea"
|
|
2419
|
+
};
|
|
2420
|
+
async function verifyCircuitIntegrity(circuitsPath2, circuit) {
|
|
2421
|
+
const circuitsUrl = resolveCircuitsUrl(circuitsPath2);
|
|
2422
|
+
if (SKIP_CIRCUIT_VERIFY_ENV) {
|
|
2423
|
+
return {
|
|
2424
|
+
valid: true,
|
|
2425
|
+
circuit,
|
|
2426
|
+
error: "Verification skipped (CLOAK_SKIP_CIRCUIT_INTEGRITY_CHECK=1)"
|
|
2427
|
+
};
|
|
2428
|
+
}
|
|
2429
|
+
const expected = circuit === "withdraw_regular" ? {
|
|
2430
|
+
wasm: EXPECTED_CIRCUIT_HASHES.withdraw_regular_wasm,
|
|
2431
|
+
zkey: EXPECTED_CIRCUIT_HASHES.withdraw_regular_zkey
|
|
2432
|
+
} : {
|
|
2433
|
+
wasm: EXPECTED_CIRCUIT_HASHES.withdraw_swap_wasm,
|
|
2434
|
+
zkey: EXPECTED_CIRCUIT_HASHES.withdraw_swap_zkey
|
|
2435
|
+
};
|
|
2436
|
+
const wasmPath = `${circuitsUrl}/${circuit}_js/${circuit}.wasm`;
|
|
2437
|
+
const zkeyPath = `${circuitsUrl}/${circuit}_final.zkey`;
|
|
2438
|
+
try {
|
|
2439
|
+
const [wasmBytes, zkeyBytes] = await Promise.all([
|
|
2440
|
+
fetchFileAsBuffer(wasmPath),
|
|
2441
|
+
fetchFileAsBuffer(zkeyPath)
|
|
2442
|
+
]);
|
|
2443
|
+
const computed = {
|
|
2444
|
+
wasm: await sha256Hex(wasmBytes),
|
|
2445
|
+
zkey: await sha256Hex(zkeyBytes)
|
|
2446
|
+
};
|
|
2447
|
+
if (computed.wasm === expected.wasm && computed.zkey === expected.zkey) {
|
|
2448
|
+
return {
|
|
2449
|
+
valid: true,
|
|
2450
|
+
circuit,
|
|
2451
|
+
computed,
|
|
2452
|
+
expected
|
|
2453
|
+
};
|
|
2454
|
+
}
|
|
2455
|
+
return {
|
|
2456
|
+
valid: false,
|
|
2457
|
+
circuit,
|
|
2458
|
+
error: `Circuit artifact hash mismatch for ${circuit}`,
|
|
2459
|
+
computed,
|
|
2460
|
+
expected
|
|
2461
|
+
};
|
|
2462
|
+
} catch (error) {
|
|
2463
|
+
return {
|
|
2464
|
+
valid: false,
|
|
2465
|
+
circuit,
|
|
2466
|
+
error: `Circuit verification failed: ${error instanceof Error ? error.message : String(error)}`
|
|
2467
|
+
};
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
async function verifyAllCircuits(circuitsPath2) {
|
|
2471
|
+
const circuitsUrl = resolveCircuitsUrl(circuitsPath2);
|
|
2472
|
+
const results = await Promise.all([
|
|
2473
|
+
verifyCircuitIntegrity(circuitsUrl, "withdraw_regular"),
|
|
2474
|
+
verifyCircuitIntegrity(circuitsUrl, "withdraw_swap")
|
|
2475
|
+
]);
|
|
2476
|
+
return results;
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
// src/utils/logger.ts
|
|
2480
|
+
var globalDebugEnabled = false;
|
|
2481
|
+
function checkEnvDebug() {
|
|
2482
|
+
if (typeof process !== "undefined" && process.env) {
|
|
2483
|
+
return !!(process.env.CLOAK_DEBUG === "1" || process.env.CLOAK_DEBUG === "true" || process.env.DEBUG?.includes("cloak"));
|
|
2484
|
+
}
|
|
2485
|
+
return false;
|
|
2486
|
+
}
|
|
2487
|
+
globalDebugEnabled = checkEnvDebug();
|
|
2488
|
+
function setDebugMode(enabled) {
|
|
2489
|
+
globalDebugEnabled = enabled;
|
|
2490
|
+
}
|
|
2491
|
+
function isDebugEnabled() {
|
|
2492
|
+
return globalDebugEnabled;
|
|
2493
|
+
}
|
|
2494
|
+
var LOG_COLORS = {
|
|
2495
|
+
DEBUG: "\x1B[90m",
|
|
1791
2496
|
// gray
|
|
1792
2497
|
INFO: "\x1B[32m",
|
|
1793
2498
|
// green
|
|
@@ -1987,16 +2692,44 @@ async function computeProofInternal(leafIndex, _nextIndex, subtrees) {
|
|
|
1987
2692
|
}
|
|
1988
2693
|
|
|
1989
2694
|
// src/core/CloakSDK.ts
|
|
1990
|
-
var
|
|
2695
|
+
var COMPUTE_BUDGET_PROGRAM_ID = new PublicKey4("ComputeBudget111111111111111111111111111111");
|
|
2696
|
+
function createSetLoadedAccountsDataSizeLimitInstruction(bytes) {
|
|
2697
|
+
const data = Buffer.alloc(5);
|
|
2698
|
+
data.writeUInt8(4, 0);
|
|
2699
|
+
data.writeUInt32LE(bytes, 1);
|
|
2700
|
+
return new TransactionInstruction2({
|
|
2701
|
+
keys: [],
|
|
2702
|
+
programId: COMPUTE_BUDGET_PROGRAM_ID,
|
|
2703
|
+
data
|
|
2704
|
+
});
|
|
2705
|
+
}
|
|
2706
|
+
var CLOAK_PROGRAM_ID = new PublicKey4("zh1eLd6rSphLejbFfJEneUwzHRfMKxgzrgkfwA6qRkW");
|
|
1991
2707
|
var CLOAK_API_URL = "https://api.cloak.ag";
|
|
1992
2708
|
var CloakSDK = class {
|
|
2709
|
+
/**
|
|
2710
|
+
* Create a new Cloak SDK client
|
|
2711
|
+
*
|
|
2712
|
+
* @param config - Client configuration
|
|
2713
|
+
*
|
|
2714
|
+
* @example Node.js mode (with keypair)
|
|
2715
|
+
* ```typescript
|
|
2716
|
+
* const sdk = new CloakSDK({
|
|
2717
|
+
* keypairBytes: keypair.secretKey,
|
|
2718
|
+
* network: "devnet"
|
|
2719
|
+
* });
|
|
2720
|
+
* ```
|
|
2721
|
+
*
|
|
2722
|
+
* @example Browser mode (with wallet adapter)
|
|
2723
|
+
* ```typescript
|
|
2724
|
+
* const sdk = new CloakSDK({
|
|
2725
|
+
* wallet: walletAdapter,
|
|
2726
|
+
* network: "devnet"
|
|
2727
|
+
* });
|
|
2728
|
+
* ```
|
|
2729
|
+
*/
|
|
1993
2730
|
constructor(config) {
|
|
1994
2731
|
if (!config.keypairBytes && !config.wallet) {
|
|
1995
|
-
throw new
|
|
1996
|
-
"Must provide either keypairBytes (Node.js) or wallet (Browser)",
|
|
1997
|
-
"validation",
|
|
1998
|
-
false
|
|
1999
|
-
);
|
|
2732
|
+
throw new Error("Must provide either keypairBytes (Node.js) or wallet (Browser)");
|
|
2000
2733
|
}
|
|
2001
2734
|
if (config.debug) {
|
|
2002
2735
|
setDebugMode(true);
|
|
@@ -2016,9 +2749,13 @@ var CloakSDK = class {
|
|
|
2016
2749
|
}
|
|
2017
2750
|
}
|
|
2018
2751
|
const programId = config.programId || CLOAK_PROGRAM_ID;
|
|
2019
|
-
const { pool, merkleTree, treasury } = getShieldPoolPDAs(
|
|
2752
|
+
const { pool, merkleTree, treasury } = getShieldPoolPDAs(
|
|
2753
|
+
programId,
|
|
2754
|
+
NATIVE_SOL_MINT
|
|
2755
|
+
);
|
|
2020
2756
|
this.config = {
|
|
2021
2757
|
network: config.network || "mainnet",
|
|
2758
|
+
keypairBytes: config.keypairBytes,
|
|
2022
2759
|
cloakKeys: config.cloakKeys,
|
|
2023
2760
|
programId,
|
|
2024
2761
|
poolAddress: pool,
|
|
@@ -2027,7 +2764,9 @@ var CloakSDK = class {
|
|
|
2027
2764
|
debug: config.debug
|
|
2028
2765
|
};
|
|
2029
2766
|
}
|
|
2030
|
-
/**
|
|
2767
|
+
/**
|
|
2768
|
+
* Get the public key for deposits (from keypair or wallet)
|
|
2769
|
+
*/
|
|
2031
2770
|
getPublicKey() {
|
|
2032
2771
|
if (this.keypair) {
|
|
2033
2772
|
return this.keypair.publicKey;
|
|
@@ -2035,23 +2774,877 @@ var CloakSDK = class {
|
|
|
2035
2774
|
if (this.wallet?.publicKey) {
|
|
2036
2775
|
return this.wallet.publicKey;
|
|
2037
2776
|
}
|
|
2038
|
-
throw new
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2777
|
+
throw new Error("No public key available - wallet not connected or keypair not provided");
|
|
2778
|
+
}
|
|
2779
|
+
/**
|
|
2780
|
+
* Check if the SDK is using a wallet adapter
|
|
2781
|
+
*/
|
|
2782
|
+
isWalletMode() {
|
|
2783
|
+
return !!this.wallet && !this.keypair;
|
|
2784
|
+
}
|
|
2785
|
+
/**
|
|
2786
|
+
* Deposit SOL into the Cloak protocol
|
|
2787
|
+
*
|
|
2788
|
+
* Creates a new note (or uses a provided one), submits a deposit transaction,
|
|
2789
|
+
* and registers with the indexer.
|
|
2790
|
+
*
|
|
2791
|
+
* @param connection - Solana connection
|
|
2792
|
+
* @param payer - Payer wallet with sendTransaction method
|
|
2793
|
+
* @param amountOrNote - Amount in lamports OR an existing note to deposit
|
|
2794
|
+
* @param options - Optional configuration
|
|
2795
|
+
* @returns Deposit result with note and transaction info
|
|
2796
|
+
*
|
|
2797
|
+
* @example
|
|
2798
|
+
* ```typescript
|
|
2799
|
+
* // Generate and deposit in one step
|
|
2800
|
+
* const result = await client.deposit(
|
|
2801
|
+
* connection,
|
|
2802
|
+
* wallet,
|
|
2803
|
+
* 1_000_000_000,
|
|
2804
|
+
* {
|
|
2805
|
+
* onProgress: (status) => console.log(status)
|
|
2806
|
+
* }
|
|
2807
|
+
* );
|
|
2808
|
+
*
|
|
2809
|
+
* // Or deposit a pre-generated note
|
|
2810
|
+
* const note = client.generateNote(1_000_000_000);
|
|
2811
|
+
* const result = await client.deposit(connection, wallet, note);
|
|
2812
|
+
* ```
|
|
2813
|
+
*/
|
|
2814
|
+
async deposit(connection, amountOrNote, options) {
|
|
2815
|
+
try {
|
|
2816
|
+
let note;
|
|
2817
|
+
let isNewNote = false;
|
|
2818
|
+
if (typeof amountOrNote === "number") {
|
|
2819
|
+
options?.onProgress?.("generating_note", { message: "Generating note with secrets..." });
|
|
2820
|
+
note = await generateNote(amountOrNote, this.config.network);
|
|
2821
|
+
isNewNote = true;
|
|
2822
|
+
} else {
|
|
2823
|
+
note = amountOrNote;
|
|
2824
|
+
if (note.depositSignature) {
|
|
2825
|
+
throw new Error("Note has already been deposited");
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
2828
|
+
if (isNewNote) {
|
|
2829
|
+
await this.storage.saveNote(note);
|
|
2830
|
+
if (options?.onNoteGenerated) {
|
|
2831
|
+
options?.onProgress?.("awaiting_note_acknowledgment", {
|
|
2832
|
+
message: "Waiting for note to be saved before proceeding with deposit..."
|
|
2833
|
+
});
|
|
2834
|
+
try {
|
|
2835
|
+
await options.onNoteGenerated(note);
|
|
2836
|
+
} catch (error) {
|
|
2837
|
+
throw new Error(
|
|
2838
|
+
`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.`
|
|
2839
|
+
);
|
|
2840
|
+
}
|
|
2841
|
+
}
|
|
2842
|
+
options?.onProgress?.("note_saved", {
|
|
2843
|
+
message: "Note saved. Proceeding with on-chain deposit..."
|
|
2844
|
+
});
|
|
2845
|
+
}
|
|
2846
|
+
const payerPubkey = this.getPublicKey();
|
|
2847
|
+
const balance = await connection.getBalance(payerPubkey);
|
|
2848
|
+
const requiredAmount = note.amount + 5e3;
|
|
2849
|
+
if (balance < requiredAmount) {
|
|
2850
|
+
throw new Error(
|
|
2851
|
+
`Insufficient balance. Required: ${requiredAmount} lamports (${note.amount} + fees), Available: ${balance} lamports`
|
|
2852
|
+
);
|
|
2853
|
+
}
|
|
2854
|
+
const commitmentBytes = hexToBytes(note.commitment);
|
|
2855
|
+
const programId = this.config.programId || CLOAK_PROGRAM_ID;
|
|
2856
|
+
const depositIx = createDepositInstruction({
|
|
2857
|
+
programId,
|
|
2858
|
+
payer: payerPubkey,
|
|
2859
|
+
pool: this.config.poolAddress,
|
|
2860
|
+
merkleTree: this.config.merkleTreeAddress,
|
|
2861
|
+
amount: note.amount,
|
|
2862
|
+
commitment: commitmentBytes
|
|
2863
|
+
});
|
|
2864
|
+
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
|
|
2865
|
+
const priorityFee = options?.priorityFee ?? 1e4;
|
|
2866
|
+
const cuPriceIx = ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priorityFee });
|
|
2867
|
+
const dataSizeLimit = options?.loadedAccountsDataSizeLimit ?? 256 * 1024;
|
|
2868
|
+
const dataSizeLimitIx = dataSizeLimit > 0 ? createSetLoadedAccountsDataSizeLimitInstruction(dataSizeLimit) : null;
|
|
2869
|
+
let computeUnits;
|
|
2870
|
+
if (options?.optimizeCU && this.keypair) {
|
|
2871
|
+
options?.onProgress?.("simulating", { message: "Simulating transaction for optimal CU..." });
|
|
2872
|
+
const simCuLimitIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 2e5 });
|
|
2873
|
+
const simTransaction = new Transaction({
|
|
2874
|
+
feePayer: payerPubkey,
|
|
2875
|
+
recentBlockhash: blockhash
|
|
2876
|
+
}).add(simCuLimitIx).add(cuPriceIx);
|
|
2877
|
+
if (dataSizeLimitIx) {
|
|
2878
|
+
simTransaction.add(dataSizeLimitIx);
|
|
2879
|
+
}
|
|
2880
|
+
simTransaction.add(depositIx);
|
|
2881
|
+
try {
|
|
2882
|
+
const simulation = await connection.simulateTransaction(simTransaction, [this.keypair]);
|
|
2883
|
+
if (simulation.value.err) {
|
|
2884
|
+
console.warn("Simulation failed, using default CU:", simulation.value.err);
|
|
2885
|
+
computeUnits = 4e4;
|
|
2886
|
+
} else {
|
|
2887
|
+
const simulatedCU = simulation.value.unitsConsumed ?? 3e4;
|
|
2888
|
+
computeUnits = Math.ceil(simulatedCU * 1.2);
|
|
2889
|
+
console.log(`CU optimization: ${simulatedCU} simulated \u2192 ${computeUnits} limit`);
|
|
2890
|
+
}
|
|
2891
|
+
} catch (simError) {
|
|
2892
|
+
console.warn("Simulation RPC error, using default CU:", simError);
|
|
2893
|
+
computeUnits = 4e4;
|
|
2894
|
+
}
|
|
2895
|
+
} else if (options?.optimizeCU && this.wallet) {
|
|
2896
|
+
console.warn("CU optimization via simulation not available in wallet mode (would require double signing). Using default.");
|
|
2897
|
+
computeUnits = options?.computeUnits ?? 4e4;
|
|
2898
|
+
} else {
|
|
2899
|
+
computeUnits = options?.computeUnits ?? 4e4;
|
|
2900
|
+
}
|
|
2901
|
+
const cuLimitIx = ComputeBudgetProgram.setComputeUnitLimit({ units: computeUnits });
|
|
2902
|
+
const transaction = new Transaction({
|
|
2903
|
+
feePayer: payerPubkey,
|
|
2904
|
+
recentBlockhash: blockhash
|
|
2905
|
+
}).add(cuLimitIx).add(cuPriceIx);
|
|
2906
|
+
if (dataSizeLimitIx) {
|
|
2907
|
+
transaction.add(dataSizeLimitIx);
|
|
2908
|
+
}
|
|
2909
|
+
transaction.add(depositIx);
|
|
2910
|
+
if (!transaction.feePayer) {
|
|
2911
|
+
throw new Error("Transaction feePayer is not set");
|
|
2912
|
+
}
|
|
2913
|
+
if (!transaction.recentBlockhash) {
|
|
2914
|
+
throw new Error("Transaction recentBlockhash is not set");
|
|
2915
|
+
}
|
|
2916
|
+
let signature;
|
|
2917
|
+
if (this.wallet) {
|
|
2918
|
+
if (!this.wallet.publicKey) {
|
|
2919
|
+
throw new Error("Wallet not connected - publicKey is null");
|
|
2920
|
+
}
|
|
2921
|
+
if (this.wallet.signTransaction) {
|
|
2922
|
+
try {
|
|
2923
|
+
const signedTransaction = await this.wallet.signTransaction(transaction);
|
|
2924
|
+
const rawTransaction = signedTransaction.serialize();
|
|
2925
|
+
signature = await connection.sendRawTransaction(rawTransaction, {
|
|
2926
|
+
skipPreflight: options?.skipPreflight || false,
|
|
2927
|
+
preflightCommitment: "confirmed",
|
|
2928
|
+
maxRetries: 3
|
|
2929
|
+
});
|
|
2930
|
+
} catch (signError) {
|
|
2931
|
+
if (this.wallet.sendTransaction) {
|
|
2932
|
+
signature = await this.wallet.sendTransaction(transaction, connection, {
|
|
2933
|
+
skipPreflight: options?.skipPreflight || false,
|
|
2934
|
+
preflightCommitment: "confirmed",
|
|
2935
|
+
maxRetries: 3
|
|
2936
|
+
});
|
|
2937
|
+
} else {
|
|
2938
|
+
throw signError;
|
|
2939
|
+
}
|
|
2940
|
+
}
|
|
2941
|
+
} else if (this.wallet.sendTransaction) {
|
|
2942
|
+
signature = await this.wallet.sendTransaction(transaction, connection, {
|
|
2943
|
+
skipPreflight: options?.skipPreflight || false,
|
|
2944
|
+
preflightCommitment: "confirmed",
|
|
2945
|
+
maxRetries: 3
|
|
2946
|
+
});
|
|
2947
|
+
} else {
|
|
2948
|
+
throw new Error("Wallet adapter must provide either sendTransaction or signTransaction");
|
|
2949
|
+
}
|
|
2950
|
+
} else if (this.keypair) {
|
|
2951
|
+
signature = await connection.sendTransaction(transaction, [this.keypair], {
|
|
2952
|
+
skipPreflight: options?.skipPreflight || false,
|
|
2953
|
+
preflightCommitment: "confirmed",
|
|
2954
|
+
maxRetries: 3
|
|
2955
|
+
});
|
|
2956
|
+
} else {
|
|
2957
|
+
throw new Error("No signing method available - provide keypair or wallet");
|
|
2958
|
+
}
|
|
2959
|
+
const confirmation = await connection.confirmTransaction({
|
|
2960
|
+
signature,
|
|
2961
|
+
blockhash,
|
|
2962
|
+
lastValidBlockHeight
|
|
2963
|
+
});
|
|
2964
|
+
if (confirmation.value.err) {
|
|
2965
|
+
throw new Error(
|
|
2966
|
+
`Transaction failed: ${JSON.stringify(confirmation.value.err)}`
|
|
2967
|
+
);
|
|
2968
|
+
}
|
|
2969
|
+
const txDetails = await connection.getTransaction(signature, {
|
|
2970
|
+
commitment: "confirmed",
|
|
2971
|
+
maxSupportedTransactionVersion: 0
|
|
2972
|
+
});
|
|
2973
|
+
const depositSlot = txDetails?.slot ?? 0;
|
|
2974
|
+
let leafIndex = null;
|
|
2975
|
+
let root = null;
|
|
2976
|
+
try {
|
|
2977
|
+
if (txDetails?.meta?.logMessages) {
|
|
2978
|
+
for (const log of txDetails.meta.logMessages) {
|
|
2979
|
+
if (log.includes("Program data:")) {
|
|
2980
|
+
try {
|
|
2981
|
+
const parts = log.split("Program data:");
|
|
2982
|
+
if (parts.length > 1) {
|
|
2983
|
+
const base64Data = parts[1].trim();
|
|
2984
|
+
const eventData = Buffer.from(base64Data, "base64");
|
|
2985
|
+
if (eventData.length >= 81 && eventData[0] === 1) {
|
|
2986
|
+
const parsedLeafIndex = Number(eventData.readBigUInt64LE(1));
|
|
2987
|
+
const loggedCommitment = eventData.slice(9, 41);
|
|
2988
|
+
if (Buffer.compare(loggedCommitment, Buffer.from(commitmentBytes)) === 0) {
|
|
2989
|
+
leafIndex = parsedLeafIndex;
|
|
2990
|
+
const loggedRoot = eventData.slice(49, 81);
|
|
2991
|
+
root = Buffer.from(loggedRoot).toString("hex");
|
|
2992
|
+
break;
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
} catch (e) {
|
|
2997
|
+
continue;
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
} catch (e) {
|
|
3003
|
+
}
|
|
3004
|
+
if (leafIndex === null) {
|
|
3005
|
+
const programId2 = this.config.programId || CLOAK_PROGRAM_ID;
|
|
3006
|
+
const mintForSOL = NATIVE_SOL_MINT;
|
|
3007
|
+
const { merkleTree } = getShieldPoolPDAs(programId2, mintForSOL);
|
|
3008
|
+
const merkleTreeAccount = await connection.getAccountInfo(merkleTree);
|
|
3009
|
+
if (!merkleTreeAccount) {
|
|
3010
|
+
throw new Error("Failed to fetch merkle tree account");
|
|
3011
|
+
}
|
|
3012
|
+
const nextIndex = Number(merkleTreeAccount.data.readBigUInt64LE(32));
|
|
3013
|
+
leafIndex = nextIndex - 1;
|
|
3014
|
+
const rootBytes = merkleTreeAccount.data.slice(1064, 1096);
|
|
3015
|
+
root = Buffer.from(rootBytes).toString("hex");
|
|
3016
|
+
}
|
|
3017
|
+
if (leafIndex === null || root === null) {
|
|
3018
|
+
throw new Error("Failed to get leaf index and root from transaction logs or on-chain state");
|
|
3019
|
+
}
|
|
3020
|
+
let merkleProof;
|
|
3021
|
+
try {
|
|
3022
|
+
const programId2 = this.config.programId || CLOAK_PROGRAM_ID;
|
|
3023
|
+
const mintForSOL = NATIVE_SOL_MINT;
|
|
3024
|
+
const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId2, mintForSOL);
|
|
3025
|
+
const chainProof = await computeProofFromChain(connection, merkleTreePDA, leafIndex);
|
|
3026
|
+
merkleProof = {
|
|
3027
|
+
pathElements: chainProof.pathElements,
|
|
3028
|
+
pathIndices: chainProof.pathIndices
|
|
3029
|
+
};
|
|
3030
|
+
root = chainProof.root;
|
|
3031
|
+
} catch (proofError) {
|
|
3032
|
+
console.warn("[SDK] Could not compute proof from chain, will fetch at withdrawal:", proofError);
|
|
3033
|
+
}
|
|
3034
|
+
const updatedNote = updateNoteWithDeposit(note, {
|
|
3035
|
+
signature,
|
|
3036
|
+
slot: depositSlot,
|
|
3037
|
+
leafIndex,
|
|
3038
|
+
root,
|
|
3039
|
+
merkleProof
|
|
3040
|
+
});
|
|
3041
|
+
await this.storage.updateNote(note.commitment, {
|
|
3042
|
+
depositSignature: signature,
|
|
3043
|
+
depositSlot,
|
|
3044
|
+
leafIndex,
|
|
3045
|
+
root,
|
|
3046
|
+
merkleProof
|
|
3047
|
+
});
|
|
3048
|
+
return {
|
|
3049
|
+
note: updatedNote,
|
|
3050
|
+
signature,
|
|
3051
|
+
leafIndex,
|
|
3052
|
+
root
|
|
3053
|
+
};
|
|
3054
|
+
} catch (error) {
|
|
3055
|
+
throw this.wrapError(error, "Deposit failed");
|
|
3056
|
+
}
|
|
3057
|
+
}
|
|
3058
|
+
/**
|
|
3059
|
+
* Private transfer with up to 5 recipients
|
|
3060
|
+
*
|
|
3061
|
+
* Handles the complete private transfer flow:
|
|
3062
|
+
* 1. If note is not deposited, deposits it first and waits for confirmation
|
|
3063
|
+
* 2. Generates a zero-knowledge proof
|
|
3064
|
+
* 3. Submits the withdrawal via relay service to recipients
|
|
3065
|
+
*
|
|
3066
|
+
* This is the main method for performing private transfers - it handles everything!
|
|
3067
|
+
*
|
|
3068
|
+
* @param connection - Solana connection (required for deposit if not already deposited)
|
|
3069
|
+
* @param payer - Payer wallet (required for deposit if not already deposited)
|
|
3070
|
+
* @param note - Note to spend (can be deposited or not)
|
|
3071
|
+
* @param recipients - Array of 1-5 recipients with amounts
|
|
3072
|
+
* @param options - Optional configuration
|
|
3073
|
+
* @returns Transfer result with signature and outputs
|
|
3074
|
+
*
|
|
3075
|
+
* @example
|
|
3076
|
+
* ```typescript
|
|
3077
|
+
* // Create a note (not deposited yet)
|
|
3078
|
+
* const note = client.generateNote(1_000_000_000);
|
|
3079
|
+
*
|
|
3080
|
+
* // privateTransfer handles the full flow: deposit + withdraw
|
|
3081
|
+
* const result = await client.privateTransfer(
|
|
3082
|
+
* connection,
|
|
3083
|
+
* wallet,
|
|
3084
|
+
* note,
|
|
3085
|
+
* [
|
|
3086
|
+
* { recipient: new PublicKey("..."), amount: 500_000_000 },
|
|
3087
|
+
* { recipient: new PublicKey("..."), amount: 492_500_000 }
|
|
3088
|
+
* ],
|
|
3089
|
+
* {
|
|
3090
|
+
* relayFeeBps: 50, // 0.5%
|
|
3091
|
+
* onProgress: (status) => console.log(status),
|
|
3092
|
+
* onProofProgress: (pct) => console.log(`Proof: ${pct}%`)
|
|
3093
|
+
* }
|
|
3094
|
+
* );
|
|
3095
|
+
* console.log(`Success! TX: ${result.signature}`);
|
|
3096
|
+
* ```
|
|
3097
|
+
*/
|
|
3098
|
+
async privateTransfer(connection, note, recipients, options) {
|
|
3099
|
+
if (!isWithdrawable(note)) {
|
|
3100
|
+
const depositResult = await this.deposit(connection, note, {
|
|
3101
|
+
skipPreflight: false
|
|
3102
|
+
});
|
|
3103
|
+
note = depositResult.note;
|
|
3104
|
+
}
|
|
3105
|
+
const protocolFee = note.amount - getDistributableAmount(note.amount);
|
|
3106
|
+
const feeBps = Math.ceil(protocolFee * 1e4 / note.amount);
|
|
3107
|
+
const distributableAmount = getDistributableAmount(note.amount);
|
|
3108
|
+
validateTransfers(recipients, distributableAmount);
|
|
3109
|
+
if (!note.leafIndex && note.leafIndex !== 0) {
|
|
3110
|
+
throw new Error("Note must have a leaf index (note must be deposited)");
|
|
3111
|
+
}
|
|
3112
|
+
if (!isValidHex(note.r, 32)) {
|
|
3113
|
+
throw new Error("Note r must be 64 hex characters (32 bytes)");
|
|
3114
|
+
}
|
|
3115
|
+
if (!isValidHex(note.sk_spend, 32)) {
|
|
3116
|
+
throw new Error("Note sk_spend must be 64 hex characters (32 bytes)");
|
|
3117
|
+
}
|
|
3118
|
+
const nullifier = await computeNullifierAsync(note.sk_spend, note.leafIndex);
|
|
3119
|
+
const nullifierHex = nullifier.toString(16).padStart(64, "0");
|
|
3120
|
+
const outputsHash = await computeOutputsHashAsync(recipients);
|
|
3121
|
+
const outputsHashHex = outputsHash.toString(16).padStart(64, "0");
|
|
3122
|
+
const circuitsPath2 = await getDefaultCircuitsPath();
|
|
3123
|
+
const useDirectProof = await areCircuitsAvailable(circuitsPath2);
|
|
3124
|
+
if (!useDirectProof) {
|
|
3125
|
+
throw new Error(
|
|
3126
|
+
`Circuits not available at ${circuitsPath2}. Make sure circuits are built and accessible. Circuit artifacts are pinned to S3 and must be reachable from this environment.`
|
|
3127
|
+
);
|
|
3128
|
+
}
|
|
3129
|
+
const MAX_PROOF_RETRIES = 3;
|
|
3130
|
+
let lastError = null;
|
|
3131
|
+
for (let attempt = 0; attempt < MAX_PROOF_RETRIES; attempt++) {
|
|
3132
|
+
try {
|
|
3133
|
+
if (attempt > 0) {
|
|
3134
|
+
options?.onProgress?.(`proof_refresh_${attempt + 1}`);
|
|
3135
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
3136
|
+
}
|
|
3137
|
+
let merkleProof;
|
|
3138
|
+
let merkleRoot;
|
|
3139
|
+
const hasStoredProof = note.merkleProof && note.root && note.merkleProof.pathElements && note.merkleProof.pathElements.length > 0;
|
|
3140
|
+
if (attempt === 0 && hasStoredProof) {
|
|
3141
|
+
merkleProof = note.merkleProof;
|
|
3142
|
+
merkleRoot = note.root;
|
|
3143
|
+
} else {
|
|
3144
|
+
const programId = this.config.programId || CLOAK_PROGRAM_ID;
|
|
3145
|
+
const mintForSOL = NATIVE_SOL_MINT;
|
|
3146
|
+
const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId, mintForSOL);
|
|
3147
|
+
const chainProof = await computeProofFromChain(
|
|
3148
|
+
connection,
|
|
3149
|
+
merkleTreePDA,
|
|
3150
|
+
note.leafIndex
|
|
3151
|
+
);
|
|
3152
|
+
merkleProof = {
|
|
3153
|
+
pathElements: chainProof.pathElements,
|
|
3154
|
+
pathIndices: chainProof.pathIndices,
|
|
3155
|
+
root: chainProof.root
|
|
3156
|
+
};
|
|
3157
|
+
merkleRoot = chainProof.root;
|
|
3158
|
+
note.merkleProof = merkleProof;
|
|
3159
|
+
note.root = merkleRoot;
|
|
3160
|
+
}
|
|
3161
|
+
if (!merkleProof || !merkleRoot) {
|
|
3162
|
+
throw new Error("Failed to get Merkle proof from chain");
|
|
3163
|
+
}
|
|
3164
|
+
if (!merkleProof.pathElements || merkleProof.pathElements.length === 0) {
|
|
3165
|
+
throw new Error("Merkle proof is invalid: missing path elements");
|
|
3166
|
+
}
|
|
3167
|
+
if (merkleProof.pathElements.length !== merkleProof.pathIndices.length) {
|
|
3168
|
+
throw new Error("Merkle proof is invalid: path elements and indices length mismatch");
|
|
3169
|
+
}
|
|
3170
|
+
for (let i = 0; i < merkleProof.pathIndices.length; i++) {
|
|
3171
|
+
const idx = merkleProof.pathIndices[i];
|
|
3172
|
+
if (idx !== 0 && idx !== 1) {
|
|
3173
|
+
throw new Error(`Merkle proof path index at position ${i} must be 0 or 1, got ${idx}`);
|
|
3174
|
+
}
|
|
3175
|
+
}
|
|
3176
|
+
if (!isValidHex(merkleRoot, 32)) {
|
|
3177
|
+
throw new Error("Merkle root must be 64 hex characters (32 bytes)");
|
|
3178
|
+
}
|
|
3179
|
+
for (let i = 0; i < merkleProof.pathElements.length; i++) {
|
|
3180
|
+
const element = merkleProof.pathElements[i];
|
|
3181
|
+
if (typeof element !== "string" || !isValidHex(element, 32)) {
|
|
3182
|
+
throw new Error(`Merkle proof path element at position ${i} must be 64 hex characters (32 bytes)`);
|
|
3183
|
+
}
|
|
3184
|
+
}
|
|
3185
|
+
const sk_spend_bigint = BigInt("0x" + note.sk_spend);
|
|
3186
|
+
const r_bigint = BigInt("0x" + note.r);
|
|
3187
|
+
const root_bigint = BigInt("0x" + merkleRoot);
|
|
3188
|
+
const nullifier_bigint = BigInt("0x" + nullifierHex);
|
|
3189
|
+
const outputs_hash_bigint = BigInt("0x" + outputsHashHex);
|
|
3190
|
+
const amount_bigint = BigInt(note.amount);
|
|
3191
|
+
const pathElements = merkleProof.pathElements.map((p) => BigInt("0x" + p));
|
|
3192
|
+
const outAddr = [];
|
|
3193
|
+
for (let i = 0; i < 5; i++) {
|
|
3194
|
+
if (i < recipients.length) {
|
|
3195
|
+
const [lo, hi] = pubkeyToLimbs(recipients[i].recipient.toBytes());
|
|
3196
|
+
outAddr.push([lo, hi]);
|
|
3197
|
+
} else {
|
|
3198
|
+
outAddr.push([0n, 0n]);
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
const t = amount_bigint * 3n;
|
|
3202
|
+
const varFee = t / 1000n;
|
|
3203
|
+
const rem = t % 1000n;
|
|
3204
|
+
const outAmount = [];
|
|
3205
|
+
const outFlags = [];
|
|
3206
|
+
for (let i = 0; i < 5; i++) {
|
|
3207
|
+
if (i < recipients.length) {
|
|
3208
|
+
outAmount.push(BigInt(recipients[i].amount));
|
|
3209
|
+
outFlags.push(1);
|
|
3210
|
+
} else {
|
|
3211
|
+
outAmount.push(0n);
|
|
3212
|
+
outFlags.push(0);
|
|
3213
|
+
}
|
|
3214
|
+
}
|
|
3215
|
+
const sk = splitTo2Limbs(sk_spend_bigint);
|
|
3216
|
+
const r = splitTo2Limbs(r_bigint);
|
|
3217
|
+
if (attempt === 0) {
|
|
3218
|
+
const computedCommitment = await computeCommitment2(amount_bigint, r_bigint, sk_spend_bigint);
|
|
3219
|
+
const noteCommitment = BigInt("0x" + note.commitment);
|
|
3220
|
+
if (computedCommitment !== noteCommitment) {
|
|
3221
|
+
throw new Error(
|
|
3222
|
+
`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.`
|
|
3223
|
+
);
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
const proofInputs = {
|
|
3227
|
+
root: root_bigint,
|
|
3228
|
+
nullifier: nullifier_bigint,
|
|
3229
|
+
outputs_hash: outputs_hash_bigint,
|
|
3230
|
+
public_amount: amount_bigint,
|
|
3231
|
+
amount: amount_bigint,
|
|
3232
|
+
leaf_index: BigInt(note.leafIndex),
|
|
3233
|
+
sk,
|
|
3234
|
+
r,
|
|
3235
|
+
pathElements,
|
|
3236
|
+
pathIndices: merkleProof.pathIndices,
|
|
3237
|
+
num_outputs: recipients.length,
|
|
3238
|
+
out_addr: outAddr,
|
|
3239
|
+
out_amount: outAmount,
|
|
3240
|
+
out_flags: outFlags,
|
|
3241
|
+
var_fee: varFee,
|
|
3242
|
+
rem
|
|
3243
|
+
};
|
|
3244
|
+
options?.onProgress?.("proof_generating");
|
|
3245
|
+
const proofResult = await generateWithdrawRegularProof(proofInputs, circuitsPath2);
|
|
3246
|
+
options?.onProgress?.("proof_complete");
|
|
3247
|
+
const proofHex = Buffer.from(proofResult.proofBytes).toString("hex");
|
|
3248
|
+
const finalPublicInputs = {
|
|
3249
|
+
root: merkleRoot,
|
|
3250
|
+
nf: nullifierHex,
|
|
3251
|
+
outputs_hash: outputsHashHex,
|
|
3252
|
+
amount: note.amount
|
|
3253
|
+
};
|
|
3254
|
+
const metadataBundle = options?.metadataBundle;
|
|
3255
|
+
const signature = await this.relay.submitWithdraw(
|
|
3256
|
+
{
|
|
3257
|
+
proof: proofHex,
|
|
3258
|
+
publicInputs: finalPublicInputs,
|
|
3259
|
+
outputs: recipients.map((r2) => ({
|
|
3260
|
+
recipient: r2.recipient.toBase58(),
|
|
3261
|
+
amount: r2.amount
|
|
3262
|
+
})),
|
|
3263
|
+
feeBps,
|
|
3264
|
+
// Use calculated protocol fee BPS
|
|
3265
|
+
metadataBundle
|
|
3266
|
+
},
|
|
3267
|
+
options?.onProgress
|
|
3268
|
+
);
|
|
3269
|
+
return {
|
|
3270
|
+
signature,
|
|
3271
|
+
outputs: recipients.map((r2) => ({
|
|
3272
|
+
recipient: r2.recipient.toBase58(),
|
|
3273
|
+
amount: r2.amount
|
|
3274
|
+
})),
|
|
3275
|
+
nullifier: nullifierHex,
|
|
3276
|
+
root: merkleRoot
|
|
3277
|
+
};
|
|
3278
|
+
} catch (error) {
|
|
3279
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
3280
|
+
if (error instanceof RootNotFoundError) {
|
|
3281
|
+
console.warn(
|
|
3282
|
+
`[Cloak SDK] Merkle root expired (attempt ${attempt + 1}/${MAX_PROOF_RETRIES}). Regenerating proof with fresh root...`
|
|
3283
|
+
);
|
|
3284
|
+
continue;
|
|
3285
|
+
}
|
|
3286
|
+
throw error;
|
|
3287
|
+
}
|
|
3288
|
+
}
|
|
3289
|
+
throw new Error(
|
|
3290
|
+
`Withdrawal failed after ${MAX_PROOF_RETRIES} proof refresh attempts. The Merkle tree is updating too rapidly. Last error: ${lastError?.message}`
|
|
2042
3291
|
);
|
|
2043
3292
|
}
|
|
2044
|
-
/**
|
|
2045
|
-
|
|
2046
|
-
|
|
3293
|
+
/**
|
|
3294
|
+
* Withdraw to a single recipient
|
|
3295
|
+
*
|
|
3296
|
+
* Convenience method for withdrawing to one address.
|
|
3297
|
+
* Handles the complete flow: deposits if needed, then withdraws.
|
|
3298
|
+
*
|
|
3299
|
+
* @param connection - Solana connection
|
|
3300
|
+
* @param payer - Payer wallet
|
|
3301
|
+
* @param note - Note to spend
|
|
3302
|
+
* @param recipient - Recipient address
|
|
3303
|
+
* @param options - Optional configuration
|
|
3304
|
+
* @returns Transfer result
|
|
3305
|
+
*
|
|
3306
|
+
* @example
|
|
3307
|
+
* ```typescript
|
|
3308
|
+
* const note = client.generateNote(1_000_000_000);
|
|
3309
|
+
* const result = await client.withdraw(
|
|
3310
|
+
* connection,
|
|
3311
|
+
* wallet,
|
|
3312
|
+
* note,
|
|
3313
|
+
* new PublicKey("..."),
|
|
3314
|
+
* { withdrawAll: true }
|
|
3315
|
+
* );
|
|
3316
|
+
* ```
|
|
3317
|
+
*/
|
|
3318
|
+
async withdraw(connection, note, recipient, options) {
|
|
3319
|
+
const withdrawAll = options?.withdrawAll ?? true;
|
|
3320
|
+
const amount = withdrawAll ? getDistributableAmount(note.amount) : options?.amount || note.amount;
|
|
3321
|
+
if (!withdrawAll && !options?.amount) {
|
|
3322
|
+
throw new Error("Must specify amount or set withdrawAll: true");
|
|
3323
|
+
}
|
|
3324
|
+
return this.privateTransfer(
|
|
3325
|
+
connection,
|
|
3326
|
+
note,
|
|
3327
|
+
[{ recipient, amount }],
|
|
3328
|
+
options
|
|
3329
|
+
);
|
|
3330
|
+
}
|
|
3331
|
+
/**
|
|
3332
|
+
* Send SOL privately to multiple recipients
|
|
3333
|
+
*
|
|
3334
|
+
* Convenience method that wraps privateTransfer with a simpler API.
|
|
3335
|
+
* Handles the complete flow: deposits if needed, then sends to recipients.
|
|
3336
|
+
*
|
|
3337
|
+
* @param connection - Solana connection
|
|
3338
|
+
* @param note - Note to spend
|
|
3339
|
+
* @param recipients - Array of 1-5 recipients with amounts
|
|
3340
|
+
* @param options - Optional configuration
|
|
3341
|
+
* @returns Transfer result
|
|
3342
|
+
*
|
|
3343
|
+
* @example
|
|
3344
|
+
* ```typescript
|
|
3345
|
+
* const note = client.generateNote(1_000_000_000);
|
|
3346
|
+
* const result = await client.send(
|
|
3347
|
+
* connection,
|
|
3348
|
+
* note,
|
|
3349
|
+
* [
|
|
3350
|
+
* { recipient: new PublicKey("..."), amount: 500_000_000 },
|
|
3351
|
+
* { recipient: new PublicKey("..."), amount: 492_500_000 }
|
|
3352
|
+
* ]
|
|
3353
|
+
* );
|
|
3354
|
+
* ```
|
|
3355
|
+
*/
|
|
3356
|
+
async send(connection, note, recipients, options) {
|
|
3357
|
+
return this.privateTransfer(connection, note, recipients, options);
|
|
3358
|
+
}
|
|
3359
|
+
/**
|
|
3360
|
+
* Swap SOL for tokens privately
|
|
3361
|
+
*
|
|
3362
|
+
* Withdraws SOL from a note and swaps it for tokens via the relay service.
|
|
3363
|
+
* Handles the complete flow: deposits if needed, generates proof, and submits swap.
|
|
3364
|
+
*
|
|
3365
|
+
* @param connection - Solana connection
|
|
3366
|
+
* @param note - Note to spend
|
|
3367
|
+
* @param recipient - Recipient address (will receive tokens)
|
|
3368
|
+
* @param options - Swap configuration
|
|
3369
|
+
* @returns Swap result with transaction signature
|
|
3370
|
+
*
|
|
3371
|
+
* @example
|
|
3372
|
+
* ```typescript
|
|
3373
|
+
* const note = client.generateNote(1_000_000_000);
|
|
3374
|
+
* const result = await client.swap(
|
|
3375
|
+
* connection,
|
|
3376
|
+
* note,
|
|
3377
|
+
* new PublicKey("..."), // recipient
|
|
3378
|
+
* {
|
|
3379
|
+
* outputMint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC
|
|
3380
|
+
* slippageBps: 100, // 1%
|
|
3381
|
+
* getQuote: async (amount, mint, slippage) => {
|
|
3382
|
+
* // Fetch quote from your swap API
|
|
3383
|
+
* const quote = await fetchSwapQuote(amount, mint, slippage);
|
|
3384
|
+
* return {
|
|
3385
|
+
* outAmount: quote.outAmount,
|
|
3386
|
+
* minOutputAmount: quote.minOutputAmount
|
|
3387
|
+
* };
|
|
3388
|
+
* }
|
|
3389
|
+
* }
|
|
3390
|
+
* );
|
|
3391
|
+
* ```
|
|
3392
|
+
*/
|
|
3393
|
+
async swap(connection, note, recipient, options) {
|
|
3394
|
+
try {
|
|
3395
|
+
if (!isWithdrawable(note)) {
|
|
3396
|
+
const depositResult = await this.deposit(connection, note, {
|
|
3397
|
+
skipPreflight: false
|
|
3398
|
+
});
|
|
3399
|
+
note = depositResult.note;
|
|
3400
|
+
}
|
|
3401
|
+
const variableFee = Math.floor(note.amount * 3 / 1e3);
|
|
3402
|
+
const feeBps = note.amount === 0 ? 0 : Math.min(Math.floor((variableFee * 1e4 + note.amount - 1) / note.amount), 65535);
|
|
3403
|
+
const withdrawAmountLamports = getDistributableAmount(note.amount);
|
|
3404
|
+
if (withdrawAmountLamports <= 0) {
|
|
3405
|
+
throw new Error("Amount too small after fees");
|
|
3406
|
+
}
|
|
3407
|
+
let minOutputAmount;
|
|
3408
|
+
if (options.minOutputAmount !== void 0) {
|
|
3409
|
+
minOutputAmount = options.minOutputAmount;
|
|
3410
|
+
} else if (options.getQuote) {
|
|
3411
|
+
const quote = await options.getQuote(
|
|
3412
|
+
withdrawAmountLamports,
|
|
3413
|
+
options.outputMint,
|
|
3414
|
+
options.slippageBps || 100
|
|
3415
|
+
);
|
|
3416
|
+
minOutputAmount = quote.minOutputAmount;
|
|
3417
|
+
} else {
|
|
3418
|
+
throw new Error(
|
|
3419
|
+
"Must provide either minOutputAmount or getQuote function"
|
|
3420
|
+
);
|
|
3421
|
+
}
|
|
3422
|
+
let recipientAta;
|
|
3423
|
+
if (options.recipientAta) {
|
|
3424
|
+
recipientAta = new PublicKey4(options.recipientAta);
|
|
3425
|
+
} else {
|
|
3426
|
+
try {
|
|
3427
|
+
const splTokenModule = await import("@solana/spl-token");
|
|
3428
|
+
const getAssociatedTokenAddress = splTokenModule.getAssociatedTokenAddress;
|
|
3429
|
+
if (!getAssociatedTokenAddress) {
|
|
3430
|
+
throw new Error("getAssociatedTokenAddress not found");
|
|
3431
|
+
}
|
|
3432
|
+
const outputMint2 = new PublicKey4(options.outputMint);
|
|
3433
|
+
recipientAta = await getAssociatedTokenAddress(outputMint2, recipient);
|
|
3434
|
+
} catch (error) {
|
|
3435
|
+
throw new Error(
|
|
3436
|
+
`Failed to get associated token account: ${error instanceof Error ? error.message : String(error)}. Please install @solana/spl-token or provide recipientAta in options.`
|
|
3437
|
+
);
|
|
3438
|
+
}
|
|
3439
|
+
}
|
|
3440
|
+
if (!note.leafIndex && note.leafIndex !== 0) {
|
|
3441
|
+
throw new Error("Note must have a leaf index (note must be deposited)");
|
|
3442
|
+
}
|
|
3443
|
+
const nullifier = await computeNullifierAsync(note.sk_spend, note.leafIndex);
|
|
3444
|
+
const nullifierHex = nullifier.toString(16).padStart(64, "0");
|
|
3445
|
+
const inputMint = new PublicKey4("11111111111111111111111111111111");
|
|
3446
|
+
const outputMint = new PublicKey4(options.outputMint);
|
|
3447
|
+
const outputsHash = await computeSwapOutputsHashAsync(
|
|
3448
|
+
inputMint,
|
|
3449
|
+
outputMint,
|
|
3450
|
+
recipientAta,
|
|
3451
|
+
minOutputAmount,
|
|
3452
|
+
note.amount
|
|
3453
|
+
);
|
|
3454
|
+
const outputsHashHex = outputsHash.toString(16).padStart(64, "0");
|
|
3455
|
+
const circuitsPath2 = await getDefaultCircuitsPath();
|
|
3456
|
+
const useDirectProof = await areCircuitsAvailable(circuitsPath2);
|
|
3457
|
+
if (!useDirectProof) {
|
|
3458
|
+
throw new Error(
|
|
3459
|
+
`Circuits not available at ${circuitsPath2}. Make sure circuits are built and accessible. Circuit artifacts are pinned to S3 and must be reachable from this environment.`
|
|
3460
|
+
);
|
|
3461
|
+
}
|
|
3462
|
+
const MAX_PROOF_RETRIES = 3;
|
|
3463
|
+
let lastError = null;
|
|
3464
|
+
for (let attempt = 0; attempt < MAX_PROOF_RETRIES; attempt++) {
|
|
3465
|
+
try {
|
|
3466
|
+
if (attempt > 0) {
|
|
3467
|
+
options?.onProgress?.(`proof_refresh_${attempt + 1}`);
|
|
3468
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
3469
|
+
}
|
|
3470
|
+
const programId = this.config.programId || CLOAK_PROGRAM_ID;
|
|
3471
|
+
const mintForSOL = NATIVE_SOL_MINT;
|
|
3472
|
+
const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId, mintForSOL);
|
|
3473
|
+
const chainProof = await computeProofFromChain(connection, merkleTreePDA, note.leafIndex);
|
|
3474
|
+
const merkleProof = {
|
|
3475
|
+
pathElements: chainProof.pathElements,
|
|
3476
|
+
pathIndices: chainProof.pathIndices
|
|
3477
|
+
};
|
|
3478
|
+
const merkleRoot = chainProof.root;
|
|
3479
|
+
if (!merkleRoot) {
|
|
3480
|
+
throw new Error("Failed to get Merkle root from chain");
|
|
3481
|
+
}
|
|
3482
|
+
if (!merkleProof.pathElements || merkleProof.pathElements.length === 0) {
|
|
3483
|
+
throw new Error("Merkle proof is invalid: missing path elements");
|
|
3484
|
+
}
|
|
3485
|
+
const sk_spend_bigint = BigInt("0x" + note.sk_spend);
|
|
3486
|
+
const r_bigint = BigInt("0x" + note.r);
|
|
3487
|
+
const root_bigint = BigInt("0x" + merkleRoot);
|
|
3488
|
+
const nullifier_bigint = BigInt("0x" + nullifierHex);
|
|
3489
|
+
const outputs_hash_bigint = BigInt("0x" + outputsHashHex);
|
|
3490
|
+
const amount_bigint = BigInt(note.amount);
|
|
3491
|
+
const pathElements = merkleProof.pathElements.map((p) => BigInt("0x" + p));
|
|
3492
|
+
const inputMintLimbs = pubkeyToLimbs(inputMint.toBytes());
|
|
3493
|
+
const outputMintLimbs = pubkeyToLimbs(outputMint.toBytes());
|
|
3494
|
+
const recipientAtaLimbs = pubkeyToLimbs(recipientAta.toBytes());
|
|
3495
|
+
if (attempt === 0) {
|
|
3496
|
+
const computedCommitment = await computeCommitment2(amount_bigint, r_bigint, sk_spend_bigint);
|
|
3497
|
+
const noteCommitment = BigInt("0x" + note.commitment);
|
|
3498
|
+
if (computedCommitment !== noteCommitment) {
|
|
3499
|
+
throw new Error(
|
|
3500
|
+
`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.`
|
|
3501
|
+
);
|
|
3502
|
+
}
|
|
3503
|
+
}
|
|
3504
|
+
const t = amount_bigint * 3n;
|
|
3505
|
+
const varFee = t / 1000n;
|
|
3506
|
+
const rem = t % 1000n;
|
|
3507
|
+
const proofInputs = {
|
|
3508
|
+
sk_spend: sk_spend_bigint,
|
|
3509
|
+
r: r_bigint,
|
|
3510
|
+
amount: amount_bigint,
|
|
3511
|
+
leaf_index: BigInt(note.leafIndex),
|
|
3512
|
+
path_elements: pathElements,
|
|
3513
|
+
path_indices: merkleProof.pathIndices,
|
|
3514
|
+
root: root_bigint,
|
|
3515
|
+
nullifier: nullifier_bigint,
|
|
3516
|
+
outputs_hash: outputs_hash_bigint,
|
|
3517
|
+
public_amount: amount_bigint,
|
|
3518
|
+
input_mint: inputMintLimbs,
|
|
3519
|
+
output_mint: outputMintLimbs,
|
|
3520
|
+
recipient_ata: recipientAtaLimbs,
|
|
3521
|
+
min_output_amount: BigInt(minOutputAmount),
|
|
3522
|
+
var_fee: varFee,
|
|
3523
|
+
rem
|
|
3524
|
+
};
|
|
3525
|
+
options?.onProgress?.("proof_generating");
|
|
3526
|
+
const proofResult = await generateWithdrawSwapProof(proofInputs, circuitsPath2);
|
|
3527
|
+
options?.onProgress?.("proof_complete");
|
|
3528
|
+
const proofHex = Buffer.from(proofResult.proofBytes).toString("hex");
|
|
3529
|
+
const finalPublicInputs = {
|
|
3530
|
+
root: merkleRoot,
|
|
3531
|
+
nf: nullifierHex,
|
|
3532
|
+
outputs_hash: outputsHashHex,
|
|
3533
|
+
amount: note.amount
|
|
3534
|
+
};
|
|
3535
|
+
const metadataBundle = options?.metadataBundle;
|
|
3536
|
+
const signature = await this.relay.submitSwap(
|
|
3537
|
+
{
|
|
3538
|
+
proof: proofHex,
|
|
3539
|
+
publicInputs: finalPublicInputs,
|
|
3540
|
+
outputs: [
|
|
3541
|
+
{
|
|
3542
|
+
recipient: recipient.toBase58(),
|
|
3543
|
+
amount: withdrawAmountLamports
|
|
3544
|
+
}
|
|
3545
|
+
],
|
|
3546
|
+
feeBps,
|
|
3547
|
+
swap: {
|
|
3548
|
+
output_mint: options.outputMint,
|
|
3549
|
+
slippage_bps: options.slippageBps || 100,
|
|
3550
|
+
min_output_amount: minOutputAmount
|
|
3551
|
+
},
|
|
3552
|
+
metadataBundle
|
|
3553
|
+
},
|
|
3554
|
+
options.onProgress
|
|
3555
|
+
);
|
|
3556
|
+
return {
|
|
3557
|
+
signature,
|
|
3558
|
+
outputs: [
|
|
3559
|
+
{
|
|
3560
|
+
recipient: recipient.toBase58(),
|
|
3561
|
+
amount: withdrawAmountLamports
|
|
3562
|
+
}
|
|
3563
|
+
],
|
|
3564
|
+
nullifier: nullifierHex,
|
|
3565
|
+
root: merkleRoot,
|
|
3566
|
+
outputMint: options.outputMint,
|
|
3567
|
+
minOutputAmount
|
|
3568
|
+
};
|
|
3569
|
+
} catch (error) {
|
|
3570
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
3571
|
+
if (error instanceof RootNotFoundError) {
|
|
3572
|
+
console.warn(
|
|
3573
|
+
`[Cloak SDK] Merkle root expired (attempt ${attempt + 1}/${MAX_PROOF_RETRIES}). Regenerating swap proof with fresh root...`
|
|
3574
|
+
);
|
|
3575
|
+
continue;
|
|
3576
|
+
}
|
|
3577
|
+
throw error;
|
|
3578
|
+
}
|
|
3579
|
+
}
|
|
3580
|
+
throw new Error(
|
|
3581
|
+
`Swap failed after ${MAX_PROOF_RETRIES} proof refresh attempts. The Merkle tree is updating too rapidly. Last error: ${lastError?.message}`
|
|
3582
|
+
);
|
|
3583
|
+
} catch (error) {
|
|
3584
|
+
throw this.wrapError(error, "Swap failed");
|
|
3585
|
+
}
|
|
3586
|
+
}
|
|
3587
|
+
/**
|
|
3588
|
+
* Generate a new note without depositing
|
|
3589
|
+
*
|
|
3590
|
+
* @param amountLamports - Amount for the note
|
|
3591
|
+
* @param useWalletKeys - Whether to use wallet keys (v2.0 recommended)
|
|
3592
|
+
* @returns New note (not yet deposited)
|
|
3593
|
+
*/
|
|
3594
|
+
async generateNote(amountLamports, useWalletKeys = false) {
|
|
3595
|
+
if (useWalletKeys && this.cloakKeys) {
|
|
3596
|
+
return await generateNoteFromWallet(amountLamports, this.cloakKeys, this.config.network);
|
|
3597
|
+
} else if (useWalletKeys) {
|
|
3598
|
+
const keys = generateCloakKeys();
|
|
3599
|
+
this.cloakKeys = keys;
|
|
3600
|
+
const result = this.storage.saveKeys(keys);
|
|
3601
|
+
if (result instanceof Promise) {
|
|
3602
|
+
result.catch(() => {
|
|
3603
|
+
});
|
|
3604
|
+
}
|
|
3605
|
+
return await generateNoteFromWallet(amountLamports, keys, this.config.network);
|
|
3606
|
+
}
|
|
3607
|
+
return await generateNote(amountLamports, this.config.network);
|
|
3608
|
+
}
|
|
3609
|
+
/**
|
|
3610
|
+
* Parse a note from JSON string
|
|
3611
|
+
*
|
|
3612
|
+
* @param jsonString - JSON representation
|
|
3613
|
+
* @returns Parsed note
|
|
3614
|
+
*/
|
|
3615
|
+
parseNote(jsonString) {
|
|
3616
|
+
return parseNote2(jsonString);
|
|
3617
|
+
}
|
|
3618
|
+
/**
|
|
3619
|
+
* Export a note to JSON string
|
|
3620
|
+
*
|
|
3621
|
+
* @param note - Note to export
|
|
3622
|
+
* @param pretty - Format with indentation
|
|
3623
|
+
* @returns JSON string
|
|
3624
|
+
*/
|
|
3625
|
+
exportNote(note, pretty = false) {
|
|
3626
|
+
return pretty ? JSON.stringify(note, null, 2) : JSON.stringify(note);
|
|
3627
|
+
}
|
|
3628
|
+
/**
|
|
3629
|
+
* Check if a note is ready for withdrawal
|
|
3630
|
+
*
|
|
3631
|
+
* @param note - Note to check
|
|
3632
|
+
* @returns True if withdrawable
|
|
3633
|
+
*/
|
|
3634
|
+
isWithdrawable(note) {
|
|
3635
|
+
return isWithdrawable(note);
|
|
2047
3636
|
}
|
|
2048
3637
|
/**
|
|
2049
|
-
*
|
|
2050
|
-
*
|
|
3638
|
+
* Get Merkle proof for a leaf index directly from on-chain state
|
|
3639
|
+
*
|
|
3640
|
+
* @param connection - Solana connection
|
|
3641
|
+
* @param leafIndex - Leaf index in tree
|
|
3642
|
+
* @returns Merkle proof computed from on-chain data
|
|
2051
3643
|
*/
|
|
2052
3644
|
async getMerkleProof(connection, leafIndex) {
|
|
2053
3645
|
const programId = this.config.programId || CLOAK_PROGRAM_ID;
|
|
2054
|
-
const
|
|
3646
|
+
const mintForSOL = NATIVE_SOL_MINT;
|
|
3647
|
+
const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId, mintForSOL);
|
|
2055
3648
|
const chainProof = await computeProofFromChain(connection, merkleTreePDA, leafIndex);
|
|
2056
3649
|
return {
|
|
2057
3650
|
pathElements: chainProof.pathElements,
|
|
@@ -2059,20 +3652,72 @@ var CloakSDK = class {
|
|
|
2059
3652
|
root: chainProof.root
|
|
2060
3653
|
};
|
|
2061
3654
|
}
|
|
2062
|
-
/**
|
|
3655
|
+
/**
|
|
3656
|
+
* Get current Merkle root directly from on-chain state
|
|
3657
|
+
*
|
|
3658
|
+
* @param connection - Solana connection
|
|
3659
|
+
* @returns Current root hash from on-chain tree
|
|
3660
|
+
*/
|
|
2063
3661
|
async getCurrentRoot(connection) {
|
|
2064
3662
|
const programId = this.config.programId || CLOAK_PROGRAM_ID;
|
|
2065
|
-
const
|
|
3663
|
+
const mintForSOL = NATIVE_SOL_MINT;
|
|
3664
|
+
const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId, mintForSOL);
|
|
2066
3665
|
const state = await readMerkleTreeState(connection, merkleTreePDA);
|
|
2067
3666
|
return state.root;
|
|
2068
3667
|
}
|
|
2069
|
-
/**
|
|
3668
|
+
/**
|
|
3669
|
+
* Get transaction status from relay service
|
|
3670
|
+
*
|
|
3671
|
+
* @param requestId - Request ID from previous submission
|
|
3672
|
+
* @returns Current status
|
|
3673
|
+
*/
|
|
2070
3674
|
async getTransactionStatus(requestId) {
|
|
2071
3675
|
return this.relay.getStatus(requestId);
|
|
2072
3676
|
}
|
|
2073
|
-
/**
|
|
3677
|
+
/**
|
|
3678
|
+
* Fetch and decrypt this user's transaction metadata history.
|
|
3679
|
+
* DEPRECATED: This functionality is no longer supported
|
|
3680
|
+
*/
|
|
3681
|
+
async getTransactionMetadata(_options) {
|
|
3682
|
+
throw new Error("getTransactionMetadata is deprecated - use compliance export API instead");
|
|
3683
|
+
}
|
|
3684
|
+
/**
|
|
3685
|
+
* Load all notes from storage
|
|
3686
|
+
*
|
|
3687
|
+
* @returns Array of saved notes
|
|
3688
|
+
*/
|
|
3689
|
+
async loadNotes() {
|
|
3690
|
+
const notes = this.storage.loadAllNotes();
|
|
3691
|
+
return Array.isArray(notes) ? notes : await notes;
|
|
3692
|
+
}
|
|
3693
|
+
/**
|
|
3694
|
+
* Save a note to storage
|
|
3695
|
+
*
|
|
3696
|
+
* @param note - Note to save
|
|
3697
|
+
*/
|
|
3698
|
+
async saveNote(note) {
|
|
3699
|
+
const result = this.storage.saveNote(note);
|
|
3700
|
+
if (result instanceof Promise) {
|
|
3701
|
+
await result;
|
|
3702
|
+
}
|
|
3703
|
+
}
|
|
3704
|
+
/**
|
|
3705
|
+
* Find a note by its commitment
|
|
3706
|
+
*
|
|
3707
|
+
* @param commitment - Commitment hash
|
|
3708
|
+
* @returns Note if found
|
|
3709
|
+
*/
|
|
3710
|
+
async findNote(commitment) {
|
|
3711
|
+
const notes = await this.loadNotes();
|
|
3712
|
+
return findNoteByCommitment(notes, commitment);
|
|
3713
|
+
}
|
|
3714
|
+
/**
|
|
3715
|
+
* Import wallet keys from JSON
|
|
3716
|
+
*
|
|
3717
|
+
* @param keysJson - JSON string containing keys
|
|
3718
|
+
*/
|
|
2074
3719
|
async importWalletKeys(keysJson) {
|
|
2075
|
-
const keys =
|
|
3720
|
+
const keys = importWalletKeys(keysJson);
|
|
2076
3721
|
this.cloakKeys = keys;
|
|
2077
3722
|
const result = this.storage.saveKeys(keys);
|
|
2078
3723
|
if (result instanceof Promise) {
|
|
@@ -2080,32 +3725,134 @@ var CloakSDK = class {
|
|
|
2080
3725
|
}
|
|
2081
3726
|
}
|
|
2082
3727
|
/**
|
|
2083
|
-
* Export wallet keys to JSON
|
|
2084
|
-
*
|
|
2085
|
-
* WARNING:
|
|
3728
|
+
* Export wallet keys to JSON
|
|
3729
|
+
*
|
|
3730
|
+
* WARNING: This exports secret keys! Store securely.
|
|
3731
|
+
*
|
|
3732
|
+
* @returns JSON string with keys
|
|
2086
3733
|
*/
|
|
2087
3734
|
exportWalletKeys() {
|
|
2088
3735
|
if (!this.cloakKeys) {
|
|
2089
3736
|
throw new CloakError("No wallet keys available", "wallet", false);
|
|
2090
3737
|
}
|
|
2091
|
-
return
|
|
3738
|
+
return exportWalletKeys(this.cloakKeys);
|
|
2092
3739
|
}
|
|
2093
3740
|
/**
|
|
2094
|
-
*
|
|
2095
|
-
*
|
|
2096
|
-
* The return type ({@link CloakConfig}) is structurally narrower than the
|
|
2097
|
-
* constructor input ({@link CloakSDKOptions}): `keypairBytes`, `wallet`,
|
|
2098
|
-
* and `storage` are intentionally absent from the snapshot. The secrets
|
|
2099
|
-
* are consumed into private SDK state at construction time and are not
|
|
2100
|
-
* re-exposable through this method — the type system enforces it. To
|
|
2101
|
-
* sign, pass a `Keypair` / `WalletAdapter` directly to the standalone
|
|
2102
|
-
* `transact` / `partialWithdraw` / `fullWithdraw` / `swapUtxo` helpers.
|
|
3741
|
+
* Get the configuration
|
|
2103
3742
|
*/
|
|
2104
3743
|
getConfig() {
|
|
2105
3744
|
return { ...this.config };
|
|
2106
3745
|
}
|
|
3746
|
+
// Note: scanNotes was removed - it required the indexer to store encrypted outputs.
|
|
3747
|
+
// Users should maintain their own note storage. This is more privacy-preserving.
|
|
3748
|
+
/**
|
|
3749
|
+
* Wrap errors with better categorization and user-friendly messages
|
|
3750
|
+
*
|
|
3751
|
+
* @private
|
|
3752
|
+
*/
|
|
3753
|
+
wrapError(error, context) {
|
|
3754
|
+
if (error instanceof CloakError) {
|
|
3755
|
+
return error;
|
|
3756
|
+
}
|
|
3757
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3758
|
+
if (errorMessage.includes("duplicate key") || errorMessage.includes("already deposited")) {
|
|
3759
|
+
return new CloakError(
|
|
3760
|
+
"This note was already deposited. Generate a new note to continue.",
|
|
3761
|
+
"validation",
|
|
3762
|
+
false,
|
|
3763
|
+
error instanceof Error ? error : void 0
|
|
3764
|
+
);
|
|
3765
|
+
}
|
|
3766
|
+
if (errorMessage.includes("insufficient funds") || errorMessage.includes("insufficient lamports")) {
|
|
3767
|
+
return new CloakError(
|
|
3768
|
+
"Insufficient funds for this transaction. Please check your wallet balance.",
|
|
3769
|
+
"wallet",
|
|
3770
|
+
false,
|
|
3771
|
+
error instanceof Error ? error : void 0
|
|
3772
|
+
);
|
|
3773
|
+
}
|
|
3774
|
+
if (errorMessage.includes("Merkle tree") || errorMessage.includes("merkle")) {
|
|
3775
|
+
return new CloakError(
|
|
3776
|
+
"Failed to read on-chain Merkle tree state. Please try again.",
|
|
3777
|
+
"network",
|
|
3778
|
+
true,
|
|
3779
|
+
error instanceof Error ? error : void 0
|
|
3780
|
+
);
|
|
3781
|
+
}
|
|
3782
|
+
if (errorMessage.includes("timeout") || errorMessage.includes("timed out")) {
|
|
3783
|
+
return new CloakError(
|
|
3784
|
+
"Network timeout. Please check your connection and try again.",
|
|
3785
|
+
"network",
|
|
3786
|
+
true,
|
|
3787
|
+
error instanceof Error ? error : void 0
|
|
3788
|
+
);
|
|
3789
|
+
}
|
|
3790
|
+
if (errorMessage.includes("not connected") || errorMessage.includes("wallet")) {
|
|
3791
|
+
return new CloakError(
|
|
3792
|
+
"Wallet not connected. Please connect your wallet first.",
|
|
3793
|
+
"wallet",
|
|
3794
|
+
false,
|
|
3795
|
+
error instanceof Error ? error : void 0
|
|
3796
|
+
);
|
|
3797
|
+
}
|
|
3798
|
+
if (errorMessage.includes("proof") && (errorMessage.includes("failed") || errorMessage.includes("error"))) {
|
|
3799
|
+
return new CloakError(
|
|
3800
|
+
"Zero-knowledge proof generation failed. This is usually temporary - please try again.",
|
|
3801
|
+
"prover",
|
|
3802
|
+
true,
|
|
3803
|
+
error instanceof Error ? error : void 0
|
|
3804
|
+
);
|
|
3805
|
+
}
|
|
3806
|
+
if (errorMessage.includes("relay") || errorMessage.includes("withdraw")) {
|
|
3807
|
+
return new CloakError(
|
|
3808
|
+
"Relay service error. Please try again later.",
|
|
3809
|
+
"relay",
|
|
3810
|
+
true,
|
|
3811
|
+
error instanceof Error ? error : void 0
|
|
3812
|
+
);
|
|
3813
|
+
}
|
|
3814
|
+
return new CloakError(
|
|
3815
|
+
`${context}: ${errorMessage}`,
|
|
3816
|
+
"network",
|
|
3817
|
+
false,
|
|
3818
|
+
error instanceof Error ? error : void 0
|
|
3819
|
+
);
|
|
3820
|
+
}
|
|
2107
3821
|
};
|
|
2108
3822
|
|
|
3823
|
+
// src/core/note.ts
|
|
3824
|
+
function serializeNote(note, pretty = false) {
|
|
3825
|
+
validateNote(note);
|
|
3826
|
+
return pretty ? JSON.stringify(note, null, 2) : JSON.stringify(note);
|
|
3827
|
+
}
|
|
3828
|
+
function downloadNote(note, filename) {
|
|
3829
|
+
const g = globalThis;
|
|
3830
|
+
const doc = g?.document;
|
|
3831
|
+
const URL_ = g?.URL;
|
|
3832
|
+
const Blob_ = g?.Blob;
|
|
3833
|
+
if (!doc || !URL_ || !Blob_) {
|
|
3834
|
+
throw new Error("downloadNote is only available in browser environments");
|
|
3835
|
+
}
|
|
3836
|
+
const json = serializeNote(note, true);
|
|
3837
|
+
const blob = new Blob_([json], { type: "application/json" });
|
|
3838
|
+
const url = URL_.createObjectURL(blob);
|
|
3839
|
+
const defaultFilename = `cloak-note-${note.commitment.slice(0, 8)}.json`;
|
|
3840
|
+
const link = doc.createElement("a");
|
|
3841
|
+
link.href = url;
|
|
3842
|
+
link.download = filename || defaultFilename;
|
|
3843
|
+
link.click();
|
|
3844
|
+
URL_.revokeObjectURL(url);
|
|
3845
|
+
}
|
|
3846
|
+
async function copyNoteToClipboard(note) {
|
|
3847
|
+
const g = globalThis;
|
|
3848
|
+
const nav = g?.navigator;
|
|
3849
|
+
if (!nav || !nav.clipboard) {
|
|
3850
|
+
throw new Error("Clipboard API not available");
|
|
3851
|
+
}
|
|
3852
|
+
const json = serializeNote(note, true);
|
|
3853
|
+
await nav.clipboard.writeText(json);
|
|
3854
|
+
}
|
|
3855
|
+
|
|
2109
3856
|
// src/core/compliance-keys.ts
|
|
2110
3857
|
import nacl2 from "tweetnacl";
|
|
2111
3858
|
import { blake3 as blake32 } from "@noble/hashes/blake3";
|
|
@@ -2492,107 +4239,6 @@ async function decryptComplianceMetadataWithMasterKey(encrypted, masterComplianc
|
|
|
2492
4239
|
return decryptWithSharedSecret(payload, sharedSecret);
|
|
2493
4240
|
}
|
|
2494
4241
|
|
|
2495
|
-
// src/utils/fees.ts
|
|
2496
|
-
var LAMPORTS_PER_SOL = 1e9;
|
|
2497
|
-
var FIXED_FEE_LAMPORTS = 5e6;
|
|
2498
|
-
var VARIABLE_FEE_NUMERATOR = 3;
|
|
2499
|
-
var VARIABLE_FEE_DENOMINATOR = 1e3;
|
|
2500
|
-
var VARIABLE_FEE_RATE = VARIABLE_FEE_NUMERATOR / VARIABLE_FEE_DENOMINATOR;
|
|
2501
|
-
var MIN_DEPOSIT_LAMPORTS = 1e7;
|
|
2502
|
-
function calculateFee(amountLamports) {
|
|
2503
|
-
const variableFee = Math.floor(amountLamports * VARIABLE_FEE_NUMERATOR / VARIABLE_FEE_DENOMINATOR);
|
|
2504
|
-
return FIXED_FEE_LAMPORTS + variableFee;
|
|
2505
|
-
}
|
|
2506
|
-
function calculateFeeBigint(amountLamports) {
|
|
2507
|
-
const fixed = BigInt(FIXED_FEE_LAMPORTS);
|
|
2508
|
-
const variable = amountLamports * BigInt(VARIABLE_FEE_NUMERATOR) / BigInt(VARIABLE_FEE_DENOMINATOR);
|
|
2509
|
-
return fixed + variable;
|
|
2510
|
-
}
|
|
2511
|
-
function isWithdrawAmountSufficient(amountLamports) {
|
|
2512
|
-
return amountLamports > calculateFeeBigint(amountLamports);
|
|
2513
|
-
}
|
|
2514
|
-
function getDistributableAmount(amountLamports) {
|
|
2515
|
-
return amountLamports - calculateFee(amountLamports);
|
|
2516
|
-
}
|
|
2517
|
-
function formatAmount(lamports, decimals = 9) {
|
|
2518
|
-
return (lamports / LAMPORTS_PER_SOL).toFixed(decimals);
|
|
2519
|
-
}
|
|
2520
|
-
function parseAmount(sol) {
|
|
2521
|
-
const num = parseFloat(sol);
|
|
2522
|
-
if (isNaN(num) || num < 0) {
|
|
2523
|
-
throw new Error(`Invalid SOL amount: ${sol}`);
|
|
2524
|
-
}
|
|
2525
|
-
return Math.floor(num * LAMPORTS_PER_SOL);
|
|
2526
|
-
}
|
|
2527
|
-
function validateOutputsSum(outputs, expectedTotal) {
|
|
2528
|
-
const sum = outputs.reduce((acc, out) => acc + out.amount, 0);
|
|
2529
|
-
return sum === expectedTotal;
|
|
2530
|
-
}
|
|
2531
|
-
function calculateRelayFee(amountLamports, feeBps) {
|
|
2532
|
-
if (feeBps < 0 || feeBps > 1e4) {
|
|
2533
|
-
throw new Error("Fee basis points must be between 0 and 10000");
|
|
2534
|
-
}
|
|
2535
|
-
return Math.floor(amountLamports * feeBps / 1e4);
|
|
2536
|
-
}
|
|
2537
|
-
|
|
2538
|
-
// src/utils/validation.ts
|
|
2539
|
-
import { PublicKey as PublicKey3 } from "@solana/web3.js";
|
|
2540
|
-
function isValidSolanaAddress(address) {
|
|
2541
|
-
try {
|
|
2542
|
-
new PublicKey3(address);
|
|
2543
|
-
return true;
|
|
2544
|
-
} catch {
|
|
2545
|
-
return false;
|
|
2546
|
-
}
|
|
2547
|
-
}
|
|
2548
|
-
|
|
2549
|
-
// src/utils/network.ts
|
|
2550
|
-
function detectNetworkFromRpcUrl(rpcUrl) {
|
|
2551
|
-
const url = rpcUrl || process.env.NEXT_PUBLIC_SOLANA_RPC_URL || "";
|
|
2552
|
-
const lowerUrl = url.toLowerCase();
|
|
2553
|
-
if (lowerUrl.includes("mainnet") || lowerUrl.includes("api.mainnet-beta") || lowerUrl.includes("mainnet-beta")) {
|
|
2554
|
-
return "mainnet";
|
|
2555
|
-
}
|
|
2556
|
-
if (lowerUrl.includes("testnet") || lowerUrl.includes("api.testnet")) {
|
|
2557
|
-
return "testnet";
|
|
2558
|
-
}
|
|
2559
|
-
if (lowerUrl.includes("devnet") || lowerUrl.includes("api.devnet")) {
|
|
2560
|
-
return "devnet";
|
|
2561
|
-
}
|
|
2562
|
-
if (lowerUrl.includes("localhost") || lowerUrl.includes("127.0.0.1") || lowerUrl.includes("local")) {
|
|
2563
|
-
return "localnet";
|
|
2564
|
-
}
|
|
2565
|
-
return "mainnet";
|
|
2566
|
-
}
|
|
2567
|
-
function getRpcUrlForNetwork(network) {
|
|
2568
|
-
switch (network) {
|
|
2569
|
-
case "mainnet":
|
|
2570
|
-
return "https://api.mainnet-beta.solana.com";
|
|
2571
|
-
case "devnet":
|
|
2572
|
-
return "https://api.devnet.solana.com";
|
|
2573
|
-
case "localnet":
|
|
2574
|
-
return "http://localhost:8899";
|
|
2575
|
-
default:
|
|
2576
|
-
return "https://api.mainnet-beta.solana.com";
|
|
2577
|
-
}
|
|
2578
|
-
}
|
|
2579
|
-
function isValidRpcUrl(url) {
|
|
2580
|
-
try {
|
|
2581
|
-
const parsed = new URL(url);
|
|
2582
|
-
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
2583
|
-
} catch {
|
|
2584
|
-
return false;
|
|
2585
|
-
}
|
|
2586
|
-
}
|
|
2587
|
-
function getExplorerUrl(signature, network = "devnet") {
|
|
2588
|
-
const cluster = network === "mainnet" ? "" : `?cluster=${network}`;
|
|
2589
|
-
return `https://explorer.solana.com/tx/${signature}${cluster}`;
|
|
2590
|
-
}
|
|
2591
|
-
function getAddressExplorerUrl(address, network = "devnet") {
|
|
2592
|
-
const cluster = network === "mainnet" ? "" : `?cluster=${network}`;
|
|
2593
|
-
return `https://explorer.solana.com/address/${address}${cluster}`;
|
|
2594
|
-
}
|
|
2595
|
-
|
|
2596
4242
|
// src/utils/verify-utxos.ts
|
|
2597
4243
|
async function verifyUtxos(utxos, connection, programId, commitment = "confirmed") {
|
|
2598
4244
|
const checkable = [];
|
|
@@ -2721,6 +4367,45 @@ async function fetchRiskQuoteIx(connection, wallet, rangeApiKey) {
|
|
|
2721
4367
|
};
|
|
2722
4368
|
}
|
|
2723
4369
|
|
|
4370
|
+
// src/helpers/encrypted-output.ts
|
|
4371
|
+
function prepareEncryptedOutput(note, cloakKeys) {
|
|
4372
|
+
const noteData = {
|
|
4373
|
+
amount: note.amount,
|
|
4374
|
+
r: note.r,
|
|
4375
|
+
sk_spend: note.sk_spend,
|
|
4376
|
+
commitment: note.commitment
|
|
4377
|
+
};
|
|
4378
|
+
const encrypted = encryptNoteForRecipient(noteData, cloakKeys.view.pvk);
|
|
4379
|
+
return btoa(JSON.stringify(encrypted));
|
|
4380
|
+
}
|
|
4381
|
+
function prepareEncryptedOutputForRecipient(note, recipientPvkHex) {
|
|
4382
|
+
const noteData = {
|
|
4383
|
+
amount: note.amount,
|
|
4384
|
+
r: note.r,
|
|
4385
|
+
sk_spend: note.sk_spend,
|
|
4386
|
+
commitment: note.commitment
|
|
4387
|
+
};
|
|
4388
|
+
const recipientPvk = hexToBytes(recipientPvkHex);
|
|
4389
|
+
const encrypted = encryptNoteForRecipient(noteData, recipientPvk);
|
|
4390
|
+
return btoa(JSON.stringify(encrypted));
|
|
4391
|
+
}
|
|
4392
|
+
function encodeNoteSimple(note) {
|
|
4393
|
+
const data = {
|
|
4394
|
+
amount: note.amount,
|
|
4395
|
+
r: note.r,
|
|
4396
|
+
sk_spend: note.sk_spend,
|
|
4397
|
+
commitment: note.commitment
|
|
4398
|
+
};
|
|
4399
|
+
const json = JSON.stringify(data);
|
|
4400
|
+
if (typeof Buffer !== "undefined") {
|
|
4401
|
+
return Buffer.from(json).toString("base64");
|
|
4402
|
+
} else if (typeof btoa !== "undefined") {
|
|
4403
|
+
return btoa(json);
|
|
4404
|
+
} else {
|
|
4405
|
+
throw new Error("No base64 encoding method available");
|
|
4406
|
+
}
|
|
4407
|
+
}
|
|
4408
|
+
|
|
2724
4409
|
// src/helpers/wallet-integration.ts
|
|
2725
4410
|
import {
|
|
2726
4411
|
Keypair as Keypair2
|
|
@@ -3232,14 +4917,181 @@ async function preflightCheck(relayUrl, rootHex) {
|
|
|
3232
4917
|
};
|
|
3233
4918
|
}
|
|
3234
4919
|
|
|
4920
|
+
// src/utils/pending-operations.ts
|
|
4921
|
+
var PENDING_DEPOSITS_KEY = "cloak_pending_deposits";
|
|
4922
|
+
var PENDING_WITHDRAWALS_KEY = "cloak_pending_withdrawals";
|
|
4923
|
+
function getStorage() {
|
|
4924
|
+
if (typeof globalThis !== "undefined" && globalThis.localStorage) {
|
|
4925
|
+
return globalThis.localStorage;
|
|
4926
|
+
}
|
|
4927
|
+
return null;
|
|
4928
|
+
}
|
|
4929
|
+
function savePendingDeposit(deposit) {
|
|
4930
|
+
const storage = getStorage();
|
|
4931
|
+
if (!storage) {
|
|
4932
|
+
console.warn("localStorage not available - pending deposit not persisted");
|
|
4933
|
+
return;
|
|
4934
|
+
}
|
|
4935
|
+
const deposits = loadPendingDeposits();
|
|
4936
|
+
const index = deposits.findIndex((d) => d.note.commitment === deposit.note.commitment);
|
|
4937
|
+
if (index >= 0) {
|
|
4938
|
+
deposits[index] = deposit;
|
|
4939
|
+
} else {
|
|
4940
|
+
deposits.push(deposit);
|
|
4941
|
+
}
|
|
4942
|
+
storage.setItem(PENDING_DEPOSITS_KEY, JSON.stringify(deposits));
|
|
4943
|
+
}
|
|
4944
|
+
function loadPendingDeposits() {
|
|
4945
|
+
const storage = getStorage();
|
|
4946
|
+
if (!storage) return [];
|
|
4947
|
+
const stored = storage.getItem(PENDING_DEPOSITS_KEY);
|
|
4948
|
+
if (!stored) return [];
|
|
4949
|
+
try {
|
|
4950
|
+
return JSON.parse(stored);
|
|
4951
|
+
} catch {
|
|
4952
|
+
return [];
|
|
4953
|
+
}
|
|
4954
|
+
}
|
|
4955
|
+
function updatePendingDeposit(commitment, updates) {
|
|
4956
|
+
const storage = getStorage();
|
|
4957
|
+
if (!storage) return;
|
|
4958
|
+
const deposits = loadPendingDeposits();
|
|
4959
|
+
const index = deposits.findIndex((d) => d.note.commitment === commitment);
|
|
4960
|
+
if (index >= 0) {
|
|
4961
|
+
deposits[index] = { ...deposits[index], ...updates };
|
|
4962
|
+
storage.setItem(PENDING_DEPOSITS_KEY, JSON.stringify(deposits));
|
|
4963
|
+
}
|
|
4964
|
+
}
|
|
4965
|
+
function removePendingDeposit(commitment) {
|
|
4966
|
+
const storage = getStorage();
|
|
4967
|
+
if (!storage) return;
|
|
4968
|
+
const deposits = loadPendingDeposits();
|
|
4969
|
+
const filtered = deposits.filter((d) => d.note.commitment !== commitment);
|
|
4970
|
+
storage.setItem(PENDING_DEPOSITS_KEY, JSON.stringify(filtered));
|
|
4971
|
+
}
|
|
4972
|
+
function clearPendingDeposits() {
|
|
4973
|
+
const storage = getStorage();
|
|
4974
|
+
if (storage) {
|
|
4975
|
+
storage.removeItem(PENDING_DEPOSITS_KEY);
|
|
4976
|
+
}
|
|
4977
|
+
}
|
|
4978
|
+
function savePendingWithdrawal(withdrawal) {
|
|
4979
|
+
const storage = getStorage();
|
|
4980
|
+
if (!storage) {
|
|
4981
|
+
console.warn("localStorage not available - pending withdrawal not persisted");
|
|
4982
|
+
return;
|
|
4983
|
+
}
|
|
4984
|
+
const withdrawals = loadPendingWithdrawals();
|
|
4985
|
+
const index = withdrawals.findIndex((w) => w.requestId === withdrawal.requestId);
|
|
4986
|
+
if (index >= 0) {
|
|
4987
|
+
withdrawals[index] = withdrawal;
|
|
4988
|
+
} else {
|
|
4989
|
+
withdrawals.push(withdrawal);
|
|
4990
|
+
}
|
|
4991
|
+
storage.setItem(PENDING_WITHDRAWALS_KEY, JSON.stringify(withdrawals));
|
|
4992
|
+
}
|
|
4993
|
+
function loadPendingWithdrawals() {
|
|
4994
|
+
const storage = getStorage();
|
|
4995
|
+
if (!storage) return [];
|
|
4996
|
+
const stored = storage.getItem(PENDING_WITHDRAWALS_KEY);
|
|
4997
|
+
if (!stored) return [];
|
|
4998
|
+
try {
|
|
4999
|
+
return JSON.parse(stored);
|
|
5000
|
+
} catch {
|
|
5001
|
+
return [];
|
|
5002
|
+
}
|
|
5003
|
+
}
|
|
5004
|
+
function updatePendingWithdrawal(requestId, updates) {
|
|
5005
|
+
const storage = getStorage();
|
|
5006
|
+
if (!storage) return;
|
|
5007
|
+
const withdrawals = loadPendingWithdrawals();
|
|
5008
|
+
const index = withdrawals.findIndex((w) => w.requestId === requestId);
|
|
5009
|
+
if (index >= 0) {
|
|
5010
|
+
withdrawals[index] = { ...withdrawals[index], ...updates };
|
|
5011
|
+
storage.setItem(PENDING_WITHDRAWALS_KEY, JSON.stringify(withdrawals));
|
|
5012
|
+
}
|
|
5013
|
+
}
|
|
5014
|
+
function removePendingWithdrawal(requestId) {
|
|
5015
|
+
const storage = getStorage();
|
|
5016
|
+
if (!storage) return;
|
|
5017
|
+
const withdrawals = loadPendingWithdrawals();
|
|
5018
|
+
const filtered = withdrawals.filter((w) => w.requestId !== requestId);
|
|
5019
|
+
storage.setItem(PENDING_WITHDRAWALS_KEY, JSON.stringify(filtered));
|
|
5020
|
+
}
|
|
5021
|
+
function clearPendingWithdrawals() {
|
|
5022
|
+
const storage = getStorage();
|
|
5023
|
+
if (storage) {
|
|
5024
|
+
storage.removeItem(PENDING_WITHDRAWALS_KEY);
|
|
5025
|
+
}
|
|
5026
|
+
}
|
|
5027
|
+
function hasPendingOperations() {
|
|
5028
|
+
const deposits = loadPendingDeposits();
|
|
5029
|
+
const withdrawals = loadPendingWithdrawals();
|
|
5030
|
+
const pendingDeposits = deposits.filter(
|
|
5031
|
+
(d) => d.status === "pending" || d.status === "tx_sent"
|
|
5032
|
+
);
|
|
5033
|
+
const pendingWithdrawals = withdrawals.filter(
|
|
5034
|
+
(w) => w.status === "pending" || w.status === "processing"
|
|
5035
|
+
);
|
|
5036
|
+
return pendingDeposits.length > 0 || pendingWithdrawals.length > 0;
|
|
5037
|
+
}
|
|
5038
|
+
function getPendingOperationsSummary() {
|
|
5039
|
+
const deposits = loadPendingDeposits().filter(
|
|
5040
|
+
(d) => d.status === "pending" || d.status === "tx_sent"
|
|
5041
|
+
);
|
|
5042
|
+
const withdrawals = loadPendingWithdrawals().filter(
|
|
5043
|
+
(w) => w.status === "pending" || w.status === "processing"
|
|
5044
|
+
);
|
|
5045
|
+
return {
|
|
5046
|
+
deposits,
|
|
5047
|
+
withdrawals,
|
|
5048
|
+
totalPending: deposits.length + withdrawals.length
|
|
5049
|
+
};
|
|
5050
|
+
}
|
|
5051
|
+
function cleanupStalePendingOperations(maxAgeMs = 24 * 60 * 60 * 1e3) {
|
|
5052
|
+
const now = Date.now();
|
|
5053
|
+
let removedDeposits = 0;
|
|
5054
|
+
let removedWithdrawals = 0;
|
|
5055
|
+
const deposits = loadPendingDeposits();
|
|
5056
|
+
const activeDeposits = deposits.filter((d) => {
|
|
5057
|
+
const age = now - d.startedAt;
|
|
5058
|
+
const isStale = age > maxAgeMs;
|
|
5059
|
+
const isTerminal = d.status === "confirmed" || d.status === "failed";
|
|
5060
|
+
if (isStale || isTerminal) {
|
|
5061
|
+
removedDeposits++;
|
|
5062
|
+
return false;
|
|
5063
|
+
}
|
|
5064
|
+
return true;
|
|
5065
|
+
});
|
|
5066
|
+
const storage = getStorage();
|
|
5067
|
+
if (storage) {
|
|
5068
|
+
storage.setItem(PENDING_DEPOSITS_KEY, JSON.stringify(activeDeposits));
|
|
5069
|
+
}
|
|
5070
|
+
const withdrawals = loadPendingWithdrawals();
|
|
5071
|
+
const activeWithdrawals = withdrawals.filter((w) => {
|
|
5072
|
+
const age = now - w.startedAt;
|
|
5073
|
+
const isStale = age > maxAgeMs;
|
|
5074
|
+
const isTerminal = w.status === "completed" || w.status === "failed";
|
|
5075
|
+
if (isStale || isTerminal) {
|
|
5076
|
+
removedWithdrawals++;
|
|
5077
|
+
return false;
|
|
5078
|
+
}
|
|
5079
|
+
return true;
|
|
5080
|
+
});
|
|
5081
|
+
if (storage) {
|
|
5082
|
+
storage.setItem(PENDING_WITHDRAWALS_KEY, JSON.stringify(activeWithdrawals));
|
|
5083
|
+
}
|
|
5084
|
+
return { removedDeposits, removedWithdrawals };
|
|
5085
|
+
}
|
|
5086
|
+
|
|
3235
5087
|
// src/core/transact.ts
|
|
3236
5088
|
import {
|
|
3237
|
-
PublicKey as
|
|
3238
|
-
Transaction as
|
|
3239
|
-
TransactionInstruction,
|
|
5089
|
+
PublicKey as PublicKey6,
|
|
5090
|
+
Transaction as Transaction3,
|
|
5091
|
+
TransactionInstruction as TransactionInstruction3,
|
|
3240
5092
|
sendAndConfirmTransaction,
|
|
3241
|
-
SystemProgram,
|
|
3242
|
-
ComputeBudgetProgram,
|
|
5093
|
+
SystemProgram as SystemProgram2,
|
|
5094
|
+
ComputeBudgetProgram as ComputeBudgetProgram2,
|
|
3243
5095
|
SYSVAR_INSTRUCTIONS_PUBKEY,
|
|
3244
5096
|
SYSVAR_SLOT_HASHES_PUBKEY,
|
|
3245
5097
|
VersionedTransaction,
|
|
@@ -3252,7 +5104,7 @@ import {
|
|
|
3252
5104
|
getAssociatedTokenAddressSync,
|
|
3253
5105
|
TOKEN_PROGRAM_ID
|
|
3254
5106
|
} from "@solana/spl-token";
|
|
3255
|
-
import * as
|
|
5107
|
+
import * as snarkjs2 from "snarkjs";
|
|
3256
5108
|
|
|
3257
5109
|
// src/core/chain-note.ts
|
|
3258
5110
|
var NOTE_VERSION = 2;
|
|
@@ -3402,7 +5254,7 @@ function chainNoteFromBase64(base64) {
|
|
|
3402
5254
|
// src/core/transact.ts
|
|
3403
5255
|
import { buildPoseidon as buildPoseidon3 } from "circomlibjs";
|
|
3404
5256
|
import nacl4 from "tweetnacl";
|
|
3405
|
-
var
|
|
5257
|
+
var IS_BROWSER2 = typeof window !== "undefined" || typeof globalThis.document !== "undefined";
|
|
3406
5258
|
var DEFAULT_TRANSACTION_CIRCUITS_URL = "https://storage.googleapis.com/cloak-circuits/circuits/0.1.0";
|
|
3407
5259
|
var DEFAULT_CLOAK_RELAY_URL = "https://api.cloak.ag";
|
|
3408
5260
|
function resolveDefaultTransactionCircuitsPath() {
|
|
@@ -3415,7 +5267,7 @@ async function resolveTransactionCircuitFiles() {
|
|
|
3415
5267
|
const baseNorm = base.endsWith("/") ? base : `${base}/`;
|
|
3416
5268
|
const wasmUrl = `${baseNorm}transaction_js/transaction.wasm`;
|
|
3417
5269
|
const zkeyUrl = `${baseNorm}transaction_final.zkey`;
|
|
3418
|
-
if (
|
|
5270
|
+
if (IS_BROWSER2) {
|
|
3419
5271
|
return { wasmPath: wasmUrl, zkeyPath: zkeyUrl };
|
|
3420
5272
|
}
|
|
3421
5273
|
if (_remoteTxCircuitsCache?.base === baseNorm) {
|
|
@@ -3552,7 +5404,7 @@ async function resolveAddressLookupTableAccounts(connection, relayUrl, altAddres
|
|
|
3552
5404
|
const fetched = await Promise.all(
|
|
3553
5405
|
altAddresses.map(async (addr) => {
|
|
3554
5406
|
try {
|
|
3555
|
-
const pubkey = new
|
|
5407
|
+
const pubkey = new PublicKey6(addr);
|
|
3556
5408
|
const result = await connection.getAddressLookupTable(pubkey);
|
|
3557
5409
|
return result.value ?? null;
|
|
3558
5410
|
} catch {
|
|
@@ -3714,7 +5566,7 @@ async function generateTransactionProof(inputs, onProgress) {
|
|
|
3714
5566
|
onProgress?.(10);
|
|
3715
5567
|
const { wasmPath: wasmInput, zkeyPath: zkeyInput } = await resolveTransactionCircuitFiles();
|
|
3716
5568
|
onProgress?.(30);
|
|
3717
|
-
const { proof, publicSignals } = await
|
|
5569
|
+
const { proof, publicSignals } = await snarkjs2.groth16.fullProve(
|
|
3718
5570
|
inputs,
|
|
3719
5571
|
wasmInput,
|
|
3720
5572
|
zkeyInput
|
|
@@ -3794,7 +5646,7 @@ function buildPublicInputsBytesFromSignals(publicSignals) {
|
|
|
3794
5646
|
}
|
|
3795
5647
|
var TRANSACT_DISCRIMINATOR = 0;
|
|
3796
5648
|
function deriveNullifierPDA(programId, poolPDA, nullifier) {
|
|
3797
|
-
return
|
|
5649
|
+
return PublicKey6.findProgramAddressSync(
|
|
3798
5650
|
[Buffer.from("nullifier"), poolPDA.toBuffer(), Buffer.from(nullifier)],
|
|
3799
5651
|
programId
|
|
3800
5652
|
);
|
|
@@ -3837,7 +5689,7 @@ function buildTransactInstruction(programId, payer, poolPDA, treasuryPDA, merkle
|
|
|
3837
5689
|
// 4. nullifier PDA 0 (writable)
|
|
3838
5690
|
{ pubkey: nullifierPDA1, isSigner: false, isWritable: true },
|
|
3839
5691
|
// 5. nullifier PDA 1 (writable)
|
|
3840
|
-
{ pubkey:
|
|
5692
|
+
{ pubkey: SystemProgram2.programId, isSigner: false, isWritable: false }
|
|
3841
5693
|
// 6. system_program
|
|
3842
5694
|
];
|
|
3843
5695
|
if (splAccounts) {
|
|
@@ -3862,7 +5714,7 @@ function buildTransactInstruction(programId, payer, poolPDA, treasuryPDA, merkle
|
|
|
3862
5714
|
accounts.push({ pubkey: SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
|
|
3863
5715
|
}
|
|
3864
5716
|
}
|
|
3865
|
-
return new
|
|
5717
|
+
return new TransactionInstruction3({
|
|
3866
5718
|
programId,
|
|
3867
5719
|
keys: accounts,
|
|
3868
5720
|
data
|
|
@@ -3870,10 +5722,10 @@ function buildTransactInstruction(programId, payer, poolPDA, treasuryPDA, merkle
|
|
|
3870
5722
|
}
|
|
3871
5723
|
function getCommonALTAddresses() {
|
|
3872
5724
|
return [
|
|
3873
|
-
|
|
5725
|
+
SystemProgram2.programId,
|
|
3874
5726
|
SYSVAR_SLOT_HASHES_PUBKEY,
|
|
3875
5727
|
SYSVAR_INSTRUCTIONS_PUBKEY,
|
|
3876
|
-
|
|
5728
|
+
ComputeBudgetProgram2.programId
|
|
3877
5729
|
];
|
|
3878
5730
|
}
|
|
3879
5731
|
function dedupePubkeys(addresses) {
|
|
@@ -3934,7 +5786,7 @@ async function createEphemeralALT(connection, depositor, onProgress, additionalA
|
|
|
3934
5786
|
lookupTable: altAddress,
|
|
3935
5787
|
addresses: addressesForCreate
|
|
3936
5788
|
});
|
|
3937
|
-
const tx = new
|
|
5789
|
+
const tx = new Transaction3().add(createIx).add(extendIx);
|
|
3938
5790
|
const { blockhash } = await connection.getLatestBlockhash();
|
|
3939
5791
|
tx.recentBlockhash = blockhash;
|
|
3940
5792
|
tx.feePayer = depositor.publicKey;
|
|
@@ -4100,8 +5952,8 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
|
|
|
4100
5952
|
if (riskQuoteIx) {
|
|
4101
5953
|
baseInstructions.unshift(riskQuoteIx);
|
|
4102
5954
|
}
|
|
4103
|
-
const cuLimitIx =
|
|
4104
|
-
const cuPriceIx =
|
|
5955
|
+
const cuLimitIx = ComputeBudgetProgram2.setComputeUnitLimit({ units: 4e5 });
|
|
5956
|
+
const cuPriceIx = ComputeBudgetProgram2.setComputeUnitPrice({ microLamports: 1e5 });
|
|
4105
5957
|
const fullInstructions = [...baseInstructions, cuLimitIx, cuPriceIx, transactIx];
|
|
4106
5958
|
const compactInstructions = [...baseInstructions, cuLimitIx, transactIx];
|
|
4107
5959
|
const minimalInstructions = [...baseInstructions, transactIx];
|
|
@@ -4163,6 +6015,38 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
|
|
|
4163
6015
|
const sendV0 = async (ixs, options) => {
|
|
4164
6016
|
const maxBlockhashRetries = 2;
|
|
4165
6017
|
const maxTransportRetries = 3;
|
|
6018
|
+
const alreadyLanded = async (sig) => {
|
|
6019
|
+
if (!sig) return false;
|
|
6020
|
+
for (let i = 0; i < 6; i++) {
|
|
6021
|
+
try {
|
|
6022
|
+
const { value } = await connection.getSignatureStatus(sig, {
|
|
6023
|
+
searchTransactionHistory: true
|
|
6024
|
+
});
|
|
6025
|
+
if (value) {
|
|
6026
|
+
if (value.err) return false;
|
|
6027
|
+
if (value.confirmationStatus === "confirmed" || value.confirmationStatus === "finalized") {
|
|
6028
|
+
return true;
|
|
6029
|
+
}
|
|
6030
|
+
}
|
|
6031
|
+
} catch {
|
|
6032
|
+
}
|
|
6033
|
+
await new Promise((r) => setTimeout(r, 400));
|
|
6034
|
+
}
|
|
6035
|
+
return false;
|
|
6036
|
+
};
|
|
6037
|
+
const looksLikeDoubleSpend = (error) => {
|
|
6038
|
+
const m = error instanceof Error ? error.message : String(error);
|
|
6039
|
+
return m.includes("0x1020") || // confirmTransaction().value.err is JSON-stringified, so 0x1020 appears as
|
|
6040
|
+
// its decimal form ("Custom":4128) rather than the hex "0x1020".
|
|
6041
|
+
m.includes('"Custom":4128') || m.includes("already been processed") || m.toLowerCase().includes("doublespend");
|
|
6042
|
+
};
|
|
6043
|
+
const sentSigs = [];
|
|
6044
|
+
const anyLanded = async () => {
|
|
6045
|
+
for (const s of sentSigs) {
|
|
6046
|
+
if (await alreadyLanded(s)) return s;
|
|
6047
|
+
}
|
|
6048
|
+
return void 0;
|
|
6049
|
+
};
|
|
4166
6050
|
for (let blockhashRetry = 0; blockhashRetry <= maxBlockhashRetries; blockhashRetry++) {
|
|
4167
6051
|
if (blockhashRetry > 0) {
|
|
4168
6052
|
({ blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash());
|
|
@@ -4185,6 +6069,7 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
|
|
|
4185
6069
|
signature = await connection.sendRawTransaction(versionedTx.serialize(), {
|
|
4186
6070
|
skipPreflight: options?.skipPreflight ?? false
|
|
4187
6071
|
});
|
|
6072
|
+
sentSigs.push(signature);
|
|
4188
6073
|
onProgress?.("Confirming transaction...");
|
|
4189
6074
|
const confirmation = await connection.confirmTransaction({
|
|
4190
6075
|
signature,
|
|
@@ -4201,6 +6086,7 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
|
|
|
4201
6086
|
signature = await connection.sendRawTransaction(signedTx.serialize(), {
|
|
4202
6087
|
skipPreflight: options?.skipPreflight ?? false
|
|
4203
6088
|
});
|
|
6089
|
+
sentSigs.push(signature);
|
|
4204
6090
|
onProgress?.("Confirming transaction...");
|
|
4205
6091
|
const confirmation = await connection.confirmTransaction({
|
|
4206
6092
|
signature,
|
|
@@ -4215,8 +6101,10 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
|
|
|
4215
6101
|
}
|
|
4216
6102
|
return signature;
|
|
4217
6103
|
} catch (error) {
|
|
4218
|
-
if (isBlockhashExpiryError(error)
|
|
4219
|
-
|
|
6104
|
+
if (isBlockhashExpiryError(error)) {
|
|
6105
|
+
const landed = await anyLanded();
|
|
6106
|
+
if (landed) return landed;
|
|
6107
|
+
if (blockhashRetry < maxBlockhashRetries) break;
|
|
4220
6108
|
}
|
|
4221
6109
|
if (isTransientTransportError(error) && transportAttempt < maxTransportRetries) {
|
|
4222
6110
|
transportAttempt++;
|
|
@@ -4228,6 +6116,10 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
|
|
|
4228
6116
|
({ blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash());
|
|
4229
6117
|
continue;
|
|
4230
6118
|
}
|
|
6119
|
+
if (looksLikeDoubleSpend(error)) {
|
|
6120
|
+
const landed = await anyLanded();
|
|
6121
|
+
if (landed) return landed;
|
|
6122
|
+
}
|
|
4231
6123
|
throw error;
|
|
4232
6124
|
}
|
|
4233
6125
|
}
|
|
@@ -4310,7 +6202,7 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
|
|
|
4310
6202
|
let legacyTransportAttempt = 0;
|
|
4311
6203
|
while (true) {
|
|
4312
6204
|
try {
|
|
4313
|
-
const tx = new
|
|
6205
|
+
const tx = new Transaction3();
|
|
4314
6206
|
for (const ix of instructions) {
|
|
4315
6207
|
tx.add(ix);
|
|
4316
6208
|
}
|
|
@@ -4492,7 +6384,7 @@ async function fetchRiskQuoteInstruction(riskQuoteUrl, wallet, options) {
|
|
|
4492
6384
|
const signerB58 = signerKey;
|
|
4493
6385
|
const signatureBuf = Buffer.from(signatureB64, "base64");
|
|
4494
6386
|
const messageArr = hexToBytes2(messageHex);
|
|
4495
|
-
const publicKeyBytes = new
|
|
6387
|
+
const publicKeyBytes = new PublicKey6(signerB58).toBytes();
|
|
4496
6388
|
if (signatureBuf.length !== 64 || messageArr.length !== 33 && messageArr.length !== 41 && messageArr.length !== 50 || publicKeyBytes.length !== 32) {
|
|
4497
6389
|
throw new Error(
|
|
4498
6390
|
`Invalid risk quote response: invalid lengths (sig=${signatureBuf.length}, msg=${messageArr.length}, pk=${publicKeyBytes.length})`
|
|
@@ -4514,10 +6406,10 @@ async function fetchRiskQuoteInstruction(riskQuoteUrl, wallet, options) {
|
|
|
4514
6406
|
`Invalid risk quote response: expected relay format { signature, message, signer_pubkey } or { instruction }. Received keys: ${keys}`
|
|
4515
6407
|
);
|
|
4516
6408
|
}
|
|
4517
|
-
return new
|
|
4518
|
-
programId: new
|
|
6409
|
+
return new TransactionInstruction3({
|
|
6410
|
+
programId: new PublicKey6(raw.programId),
|
|
4519
6411
|
keys: raw.keys.map((k) => ({
|
|
4520
|
-
pubkey: new
|
|
6412
|
+
pubkey: new PublicKey6(k.pubkey),
|
|
4521
6413
|
isSigner: k.isSigner,
|
|
4522
6414
|
isWritable: k.isWritable
|
|
4523
6415
|
})),
|
|
@@ -5401,12 +7293,12 @@ async function transact(params, options) {
|
|
|
5401
7293
|
if (lastError) {
|
|
5402
7294
|
const classified = classifyRelayError(lastError.message);
|
|
5403
7295
|
const isMerkleClass = !(classified instanceof UtxoAlreadySpentError) && !(classified instanceof SanctionsQuoteError) && !isRootNotFoundError2(lastError);
|
|
5404
|
-
if (!
|
|
7296
|
+
if (!IS_BROWSER2 && isMerkleClass && relayUrl) {
|
|
5405
7297
|
onProgress?.(
|
|
5406
7298
|
"All relay-tree attempts failed. Rebuilding merkle tree from chain as last resort..."
|
|
5407
7299
|
);
|
|
5408
7300
|
try {
|
|
5409
|
-
const [merkleTreePda] =
|
|
7301
|
+
const [merkleTreePda] = PublicKey6.findProgramAddressSync(
|
|
5410
7302
|
[Buffer.from("merkle_tree"), mint.toBuffer()],
|
|
5411
7303
|
programId
|
|
5412
7304
|
);
|
|
@@ -6475,7 +8367,7 @@ async function swapWithChange(inputUtxos, swapAmount, outputMint, recipientAta,
|
|
|
6475
8367
|
// src/core/scanner.ts
|
|
6476
8368
|
import _bs582 from "bs58";
|
|
6477
8369
|
import {
|
|
6478
|
-
PublicKey as
|
|
8370
|
+
PublicKey as PublicKey7
|
|
6479
8371
|
} from "@solana/web3.js";
|
|
6480
8372
|
import { getAssociatedTokenAddressSync as getAssociatedTokenAddressSync2 } from "@solana/spl-token";
|
|
6481
8373
|
var bs582 = _bs582.default || _bs582;
|
|
@@ -6563,7 +8455,7 @@ function parseSwapOutputMint(data) {
|
|
|
6563
8455
|
const mintBytes = data.slice(SWAP_OUTPUT_MINT_OFFSET, end);
|
|
6564
8456
|
if (mintBytes.every((byte) => byte === 0)) return void 0;
|
|
6565
8457
|
try {
|
|
6566
|
-
return new
|
|
8458
|
+
return new PublicKey7(mintBytes).toBase58();
|
|
6567
8459
|
} catch {
|
|
6568
8460
|
return void 0;
|
|
6569
8461
|
}
|
|
@@ -6653,7 +8545,7 @@ function parseSwapRecipientAta(data) {
|
|
|
6653
8545
|
const recipientAtaBytes = data.slice(recipientAtaStart, recipientAtaEnd);
|
|
6654
8546
|
if (recipientAtaBytes.every((byte) => byte === 0)) return void 0;
|
|
6655
8547
|
try {
|
|
6656
|
-
return new
|
|
8548
|
+
return new PublicKey7(recipientAtaBytes).toBase58();
|
|
6657
8549
|
} catch {
|
|
6658
8550
|
return void 0;
|
|
6659
8551
|
}
|
|
@@ -6968,8 +8860,8 @@ async function scanTransactions(opts) {
|
|
|
6968
8860
|
if (onChainAta) {
|
|
6969
8861
|
try {
|
|
6970
8862
|
const expectedAta = getAssociatedTokenAddressSync2(
|
|
6971
|
-
new
|
|
6972
|
-
new
|
|
8863
|
+
new PublicKey7(asset.mint),
|
|
8864
|
+
new PublicKey7(walletPublicKey)
|
|
6973
8865
|
).toBase58();
|
|
6974
8866
|
if (onChainAta === expectedAta) {
|
|
6975
8867
|
isOurs = true;
|
|
@@ -7272,7 +9164,7 @@ function formatComplianceCsv(report) {
|
|
|
7272
9164
|
}
|
|
7273
9165
|
|
|
7274
9166
|
// src/core/wallet.ts
|
|
7275
|
-
import { PublicKey as
|
|
9167
|
+
import { PublicKey as PublicKey8 } from "@solana/web3.js";
|
|
7276
9168
|
var UtxoWallet = class _UtxoWallet {
|
|
7277
9169
|
constructor(viewingKey) {
|
|
7278
9170
|
this.wallets = /* @__PURE__ */ new Map();
|
|
@@ -7484,7 +9376,7 @@ var UtxoWallet = class _UtxoWallet {
|
|
|
7484
9376
|
data.viewingKey ? new Uint8Array(data.viewingKey) : void 0
|
|
7485
9377
|
);
|
|
7486
9378
|
for (const w of data.wallets) {
|
|
7487
|
-
const mint = new
|
|
9379
|
+
const mint = new PublicKey8(w.mint);
|
|
7488
9380
|
for (const u of w.utxos) {
|
|
7489
9381
|
wallet.addUtxo({
|
|
7490
9382
|
amount: BigInt(u.amount),
|
|
@@ -7567,13 +9459,15 @@ var SimpleWallet = class {
|
|
|
7567
9459
|
};
|
|
7568
9460
|
|
|
7569
9461
|
// src/index.ts
|
|
7570
|
-
var VERSION = "0.1.
|
|
9462
|
+
var VERSION = "0.1.5";
|
|
7571
9463
|
var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
7572
9464
|
export {
|
|
7573
9465
|
CLOAK_PROGRAM_ID,
|
|
7574
9466
|
CloakError,
|
|
7575
9467
|
CloakSDK,
|
|
9468
|
+
DEFAULT_CIRCUITS_URL,
|
|
7576
9469
|
DEFAULT_TRANSACTION_CIRCUITS_URL,
|
|
9470
|
+
EXPECTED_CIRCUIT_HASHES,
|
|
7577
9471
|
FIXED_FEE_LAMPORTS,
|
|
7578
9472
|
LAMPORTS_PER_SOL,
|
|
7579
9473
|
LocalStorageAdapter,
|
|
@@ -7596,6 +9490,7 @@ export {
|
|
|
7596
9490
|
VARIABLE_FEE_NUMERATOR,
|
|
7597
9491
|
VARIABLE_FEE_RATE,
|
|
7598
9492
|
VERSION,
|
|
9493
|
+
areCircuitsAvailable,
|
|
7599
9494
|
bigintToBytes322 as bigintToBytes32,
|
|
7600
9495
|
bigintToHex,
|
|
7601
9496
|
buildMerkleTree,
|
|
@@ -7609,15 +9504,30 @@ export {
|
|
|
7609
9504
|
chainNoteFromBase64,
|
|
7610
9505
|
chainNoteToBase64,
|
|
7611
9506
|
classifyRelayError,
|
|
9507
|
+
cleanupStalePendingOperations,
|
|
9508
|
+
clearPendingDeposits,
|
|
9509
|
+
clearPendingWithdrawals,
|
|
7612
9510
|
computeChainNoteHash,
|
|
9511
|
+
computeCommitment2 as computeCommitment,
|
|
7613
9512
|
computeExtDataHash,
|
|
7614
9513
|
computeMerkleRoot,
|
|
9514
|
+
computeNullifier2 as computeNullifier,
|
|
9515
|
+
computeNullifierAsync,
|
|
9516
|
+
computeNullifierSync,
|
|
9517
|
+
computeOutputsHash,
|
|
9518
|
+
computeOutputsHashAsync,
|
|
9519
|
+
computeOutputsHashSync,
|
|
7615
9520
|
computeProofForLatestDeposit,
|
|
7616
9521
|
computeProofFromChain,
|
|
7617
9522
|
computeSignature,
|
|
9523
|
+
computeSwapOutputsHash,
|
|
9524
|
+
computeSwapOutputsHashAsync,
|
|
9525
|
+
computeSwapOutputsHashSync,
|
|
7618
9526
|
computeCommitment as computeUtxoCommitment,
|
|
7619
9527
|
computeNullifier as computeUtxoNullifier,
|
|
9528
|
+
copyNoteToClipboard,
|
|
7620
9529
|
createCloakError,
|
|
9530
|
+
createDepositInstruction,
|
|
7621
9531
|
createLogger,
|
|
7622
9532
|
createUtxo,
|
|
7623
9533
|
createZeroUtxo,
|
|
@@ -7637,52 +9547,78 @@ export {
|
|
|
7637
9547
|
deriveViewingKeyFromUtxoPrivateKey,
|
|
7638
9548
|
deserializeUtxo,
|
|
7639
9549
|
detectNetworkFromRpcUrl,
|
|
9550
|
+
downloadNote,
|
|
9551
|
+
encodeNoteSimple,
|
|
7640
9552
|
encryptCompactChainNote,
|
|
7641
9553
|
encryptNoteForRecipient,
|
|
7642
9554
|
encryptTransactionMetadata,
|
|
7643
9555
|
encryptTransactionMetadataBundle,
|
|
7644
9556
|
expandSpendKey,
|
|
7645
9557
|
exportKeys,
|
|
9558
|
+
exportNote,
|
|
9559
|
+
exportWalletKeys,
|
|
7646
9560
|
fetchCommitments,
|
|
7647
9561
|
fetchRiskQuoteInstruction,
|
|
7648
9562
|
fetchRiskQuoteIx,
|
|
9563
|
+
filterNotesByNetwork,
|
|
9564
|
+
filterWithdrawableNotes,
|
|
9565
|
+
findNoteByCommitment,
|
|
7649
9566
|
formatAmount,
|
|
7650
9567
|
formatComplianceCsv,
|
|
7651
9568
|
formatErrorForLogging,
|
|
7652
9569
|
formatSol,
|
|
7653
9570
|
fullWithdraw,
|
|
7654
9571
|
generateCloakKeys,
|
|
9572
|
+
generateCommitment,
|
|
9573
|
+
generateCommitmentAsync,
|
|
7655
9574
|
generateMasterSeed,
|
|
9575
|
+
generateNote,
|
|
9576
|
+
generateNoteFromWallet,
|
|
7656
9577
|
generateUtxoKeypair,
|
|
7657
9578
|
generateViewingKeyPair,
|
|
9579
|
+
generateWithdrawRegularProof,
|
|
9580
|
+
generateWithdrawSwapProof,
|
|
7658
9581
|
getAddressExplorerUrl,
|
|
7659
9582
|
getCircuitsPath,
|
|
9583
|
+
getDefaultCircuitsPath,
|
|
7660
9584
|
getDistributableAmount,
|
|
7661
9585
|
getExplorerUrl,
|
|
7662
9586
|
getNkFromUtxoPrivateKey,
|
|
7663
9587
|
getNullifierPDA,
|
|
9588
|
+
getPendingOperationsSummary,
|
|
7664
9589
|
getPublicKey,
|
|
9590
|
+
getPublicViewKey,
|
|
9591
|
+
getRecipientAmount,
|
|
7665
9592
|
getRpcUrlForNetwork,
|
|
7666
9593
|
getShieldPoolPDAs,
|
|
7667
9594
|
getSwapStatePDA,
|
|
9595
|
+
getViewKey,
|
|
9596
|
+
hasPendingOperations,
|
|
7668
9597
|
hexToBigint2 as hexToBigint,
|
|
7669
9598
|
hexToBytes,
|
|
7670
9599
|
importKeys,
|
|
9600
|
+
importWalletKeys,
|
|
7671
9601
|
isDebugEnabled,
|
|
7672
9602
|
isRootNotFoundError,
|
|
7673
9603
|
isValidHex,
|
|
7674
9604
|
isValidRpcUrl,
|
|
7675
9605
|
isValidSolanaAddress,
|
|
7676
9606
|
isWithdrawAmountSufficient,
|
|
9607
|
+
isWithdrawable,
|
|
7677
9608
|
keypairToAdapter,
|
|
9609
|
+
loadPendingDeposits,
|
|
9610
|
+
loadPendingWithdrawals,
|
|
7678
9611
|
parseAmount,
|
|
7679
9612
|
parseError,
|
|
9613
|
+
parseNote,
|
|
7680
9614
|
parseRelayErrorResponse,
|
|
7681
9615
|
parseTransactionError,
|
|
7682
9616
|
partialWithdraw,
|
|
7683
9617
|
poseidonHash,
|
|
7684
9618
|
preflightCheck,
|
|
7685
9619
|
preflightNullifiers,
|
|
9620
|
+
prepareEncryptedOutput,
|
|
9621
|
+
prepareEncryptedOutputForRecipient,
|
|
7686
9622
|
proofToBytes,
|
|
7687
9623
|
pubkeyToFieldElement,
|
|
7688
9624
|
pubkeyToLimbs,
|
|
@@ -7690,11 +9626,16 @@ export {
|
|
|
7690
9626
|
randomFieldElement,
|
|
7691
9627
|
readMerkleTreeState,
|
|
7692
9628
|
registerViewingKey,
|
|
9629
|
+
removePendingDeposit,
|
|
9630
|
+
removePendingWithdrawal,
|
|
9631
|
+
savePendingDeposit,
|
|
9632
|
+
savePendingWithdrawal,
|
|
7693
9633
|
scanNotesForWallet,
|
|
7694
9634
|
scanTransactions,
|
|
7695
9635
|
sdkLogger,
|
|
7696
9636
|
selectUtxos,
|
|
7697
9637
|
sendTransaction,
|
|
9638
|
+
serializeNote,
|
|
7698
9639
|
serializeUtxo,
|
|
7699
9640
|
setCircuitsPath,
|
|
7700
9641
|
setDebugMode,
|
|
@@ -7708,12 +9649,21 @@ export {
|
|
|
7708
9649
|
transfer,
|
|
7709
9650
|
truncate,
|
|
7710
9651
|
tryDecryptNote,
|
|
9652
|
+
updateNoteWithDeposit,
|
|
9653
|
+
updatePendingDeposit,
|
|
9654
|
+
updatePendingWithdrawal,
|
|
7711
9655
|
bigintToBytes32 as utxoBigintToBytes32,
|
|
7712
9656
|
utxoEquals,
|
|
7713
9657
|
hexToBigint as utxoHexToBigint,
|
|
9658
|
+
validateDepositParams,
|
|
9659
|
+
validateNote,
|
|
7714
9660
|
validateOutputsSum,
|
|
7715
9661
|
validateRoot,
|
|
9662
|
+
validateTransfers,
|
|
7716
9663
|
validateWalletConnected,
|
|
9664
|
+
validateWithdrawableNote,
|
|
9665
|
+
verifyAllCircuits,
|
|
9666
|
+
verifyCircuitIntegrity,
|
|
7717
9667
|
verifyUtxos,
|
|
7718
9668
|
waitForRoot,
|
|
7719
9669
|
withTiming
|