@cloak.dev/sdk 0.1.5 → 0.1.7
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/CHANGELOG.md +173 -0
- package/README.md +183 -96
- package/dist/index.cjs +296 -2256
- package/dist/index.d.cts +443 -1512
- package/dist/index.d.ts +443 -1512
- package/dist/index.js +252 -2161
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -24,10 +24,7 @@ import {
|
|
|
24
24
|
// src/core/CloakSDK.ts
|
|
25
25
|
import {
|
|
26
26
|
Keypair,
|
|
27
|
-
PublicKey as
|
|
28
|
-
Transaction,
|
|
29
|
-
TransactionInstruction as TransactionInstruction2,
|
|
30
|
-
ComputeBudgetProgram
|
|
27
|
+
PublicKey as PublicKey2
|
|
31
28
|
} from "@solana/web3.js";
|
|
32
29
|
|
|
33
30
|
// src/core/types.ts
|
|
@@ -85,79 +82,6 @@ function hexToBigint2(hex) {
|
|
|
85
82
|
const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
86
83
|
return BigInt("0x" + cleanHex);
|
|
87
84
|
}
|
|
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
|
-
}
|
|
161
85
|
function bigintToBytes322(n) {
|
|
162
86
|
const hex = n.toString(16).padStart(64, "0");
|
|
163
87
|
const bytes = new Uint8Array(32);
|
|
@@ -430,223 +354,11 @@ function importKeys(exported) {
|
|
|
430
354
|
return generateCloakKeys(masterSeed);
|
|
431
355
|
}
|
|
432
356
|
|
|
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
|
-
|
|
626
357
|
// src/core/storage.ts
|
|
627
358
|
var MemoryStorageAdapter = class {
|
|
628
359
|
constructor() {
|
|
629
|
-
this.notes = /* @__PURE__ */ new Map();
|
|
630
360
|
this.keys = null;
|
|
631
361
|
}
|
|
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
|
-
}
|
|
650
362
|
saveKeys(keys) {
|
|
651
363
|
this.keys = keys;
|
|
652
364
|
}
|
|
@@ -657,10 +369,10 @@ var MemoryStorageAdapter = class {
|
|
|
657
369
|
this.keys = null;
|
|
658
370
|
}
|
|
659
371
|
};
|
|
660
|
-
var
|
|
661
|
-
constructor(
|
|
662
|
-
this.notesKey = notesKey;
|
|
372
|
+
var _LocalStorageAdapter = class _LocalStorageAdapter {
|
|
373
|
+
constructor(keysKey = "cloak_wallet_keys") {
|
|
663
374
|
this.keysKey = keysKey;
|
|
375
|
+
_LocalStorageAdapter.runLegacyPurgeOnce();
|
|
664
376
|
}
|
|
665
377
|
getStorage() {
|
|
666
378
|
if (typeof globalThis !== "undefined" && globalThis.localStorage) {
|
|
@@ -668,47 +380,6 @@ var LocalStorageAdapter = class {
|
|
|
668
380
|
}
|
|
669
381
|
return null;
|
|
670
382
|
}
|
|
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
|
-
}
|
|
712
383
|
saveKeys(keys) {
|
|
713
384
|
const storage = this.getStorage();
|
|
714
385
|
if (!storage) throw new Error("localStorage not available");
|
|
@@ -731,126 +402,64 @@ var LocalStorageAdapter = class {
|
|
|
731
402
|
storage.removeItem(this.keysKey);
|
|
732
403
|
}
|
|
733
404
|
}
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
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}`);
|
|
754
|
-
}
|
|
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");
|
|
786
|
-
}
|
|
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");
|
|
405
|
+
/**
|
|
406
|
+
* Runs the legacy-purge exactly once per browser origin. Invoked
|
|
407
|
+
* automatically by the constructor; exposed as a static for tests and
|
|
408
|
+
* for callers who want to force the check before the first adapter
|
|
409
|
+
* instantiation.
|
|
410
|
+
*/
|
|
411
|
+
static runLegacyPurgeOnce() {
|
|
412
|
+
if (typeof globalThis === "undefined" || !globalThis.localStorage) {
|
|
413
|
+
return;
|
|
794
414
|
}
|
|
795
|
-
|
|
796
|
-
|
|
415
|
+
const storage = globalThis.localStorage;
|
|
416
|
+
if (storage.getItem(_LocalStorageAdapter.LEGACY_PURGE_SENTINEL_KEY)) {
|
|
417
|
+
return;
|
|
797
418
|
}
|
|
798
|
-
|
|
799
|
-
|
|
419
|
+
storage.removeItem(_LocalStorageAdapter.LEGACY_NOTES_KEY);
|
|
420
|
+
try {
|
|
421
|
+
storage.setItem(_LocalStorageAdapter.LEGACY_PURGE_SENTINEL_KEY, "1");
|
|
422
|
+
} catch {
|
|
800
423
|
}
|
|
801
424
|
}
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
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`);
|
|
425
|
+
/**
|
|
426
|
+
* Manual cleanup helper for callers that stored notes under a non-default
|
|
427
|
+
* key. The auto-purge in the constructor only handles the canonical
|
|
428
|
+
* `cloak_notes` slot; pass the custom key here.
|
|
429
|
+
*
|
|
430
|
+
* Pre-0.1.6 versions stored the user's `CloakNote[]` (including
|
|
431
|
+
* `r` randomness and `sk_spend` secret-key hex) as JSON plaintext.
|
|
432
|
+
* Those notes are no longer usable (the OLD `withdraw_regular.circom`
|
|
433
|
+
* flow is incompatible with the deployed UTXO program) but the plaintext
|
|
434
|
+
* would otherwise linger indefinitely.
|
|
435
|
+
*
|
|
436
|
+
* Idempotent; safe to call when the key doesn't exist or when running
|
|
437
|
+
* outside a browser (no-op). Does NOT update the auto-purge sentinel.
|
|
438
|
+
*
|
|
439
|
+
* @param notesKey - localStorage key to remove (default `"cloak_notes"`,
|
|
440
|
+
* matching the pre-0.1.6 default).
|
|
441
|
+
*/
|
|
442
|
+
static purgeLegacyNoteStorage(notesKey = _LocalStorageAdapter.LEGACY_NOTES_KEY) {
|
|
443
|
+
if (typeof globalThis === "undefined" || !globalThis.localStorage) {
|
|
444
|
+
return;
|
|
845
445
|
}
|
|
446
|
+
globalThis.localStorage.removeItem(notesKey);
|
|
846
447
|
}
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
448
|
+
};
|
|
449
|
+
/**
|
|
450
|
+
* Sentinel key set after the auto-purge runs. Persists across browser
|
|
451
|
+
* sessions so the purge never re-runs on the same origin.
|
|
452
|
+
*/
|
|
453
|
+
_LocalStorageAdapter.LEGACY_PURGE_SENTINEL_KEY = "cloak_purged_v0_1_5";
|
|
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";
|
|
854
463
|
|
|
855
464
|
// src/utils/errors.ts
|
|
856
465
|
var RootNotFoundError = class extends Error {
|
|
@@ -1746,7 +1355,6 @@ function formatErrorForLogging(error) {
|
|
|
1746
1355
|
}
|
|
1747
1356
|
|
|
1748
1357
|
// src/services/RelayService.ts
|
|
1749
|
-
import { sha256 } from "@noble/hashes/sha2";
|
|
1750
1358
|
var RelayService = class {
|
|
1751
1359
|
/**
|
|
1752
1360
|
* Create a new Relay Service client
|
|
@@ -2115,91 +1723,23 @@ var RelayService = class {
|
|
|
2115
1723
|
}
|
|
2116
1724
|
};
|
|
2117
1725
|
|
|
2118
|
-
// src/solana/instructions.ts
|
|
2119
|
-
import {
|
|
2120
|
-
PublicKey as PublicKey2,
|
|
2121
|
-
SystemProgram,
|
|
2122
|
-
TransactionInstruction
|
|
2123
|
-
} from "@solana/web3.js";
|
|
2124
|
-
function createDepositInstruction(params) {
|
|
2125
|
-
if (params.commitment.length !== 32) {
|
|
2126
|
-
throw new Error(
|
|
2127
|
-
`Invalid commitment length: ${params.commitment.length} (expected 32 bytes)`
|
|
2128
|
-
);
|
|
2129
|
-
}
|
|
2130
|
-
if (params.amount <= 0) {
|
|
2131
|
-
throw new Error("Amount must be positive");
|
|
2132
|
-
}
|
|
2133
|
-
const discriminant = new Uint8Array([1]);
|
|
2134
|
-
const amountBytes = new Uint8Array(8);
|
|
2135
|
-
new DataView(amountBytes.buffer).setBigUint64(
|
|
2136
|
-
0,
|
|
2137
|
-
BigInt(params.amount),
|
|
2138
|
-
true
|
|
2139
|
-
// little-endian
|
|
2140
|
-
);
|
|
2141
|
-
const data = new Uint8Array(41);
|
|
2142
|
-
data.set(discriminant, 0);
|
|
2143
|
-
data.set(amountBytes, 1);
|
|
2144
|
-
data.set(params.commitment, 9);
|
|
2145
|
-
return new TransactionInstruction({
|
|
2146
|
-
programId: params.programId,
|
|
2147
|
-
keys: [
|
|
2148
|
-
// Account 0: Payer (signer, writable) - pays for transaction
|
|
2149
|
-
{ pubkey: params.payer, isSigner: true, isWritable: true },
|
|
2150
|
-
// Account 1: Pool (writable) - receives SOL
|
|
2151
|
-
{ pubkey: params.pool, isSigner: false, isWritable: true },
|
|
2152
|
-
// Account 2: System Program (readonly) - for transfers
|
|
2153
|
-
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
2154
|
-
// Account 3: Merkle Tree (writable) - stores on-chain Merkle tree
|
|
2155
|
-
{ pubkey: params.merkleTree, isSigner: false, isWritable: true }
|
|
2156
|
-
],
|
|
2157
|
-
data: Buffer.from(data)
|
|
2158
|
-
});
|
|
2159
|
-
}
|
|
2160
|
-
function validateDepositParams(params) {
|
|
2161
|
-
if (!(params.programId instanceof PublicKey2)) {
|
|
2162
|
-
throw new Error("programId must be a PublicKey");
|
|
2163
|
-
}
|
|
2164
|
-
if (!(params.payer instanceof PublicKey2)) {
|
|
2165
|
-
throw new Error("payer must be a PublicKey");
|
|
2166
|
-
}
|
|
2167
|
-
if (!(params.pool instanceof PublicKey2)) {
|
|
2168
|
-
throw new Error("pool must be a PublicKey");
|
|
2169
|
-
}
|
|
2170
|
-
if (!(params.merkleTree instanceof PublicKey2)) {
|
|
2171
|
-
throw new Error("merkleTree must be a PublicKey");
|
|
2172
|
-
}
|
|
2173
|
-
if (typeof params.amount !== "number" || params.amount <= 0) {
|
|
2174
|
-
throw new Error("amount must be a positive number");
|
|
2175
|
-
}
|
|
2176
|
-
if (!(params.commitment instanceof Uint8Array)) {
|
|
2177
|
-
throw new Error("commitment must be a Uint8Array");
|
|
2178
|
-
}
|
|
2179
|
-
if (params.commitment.length !== 32) {
|
|
2180
|
-
throw new Error(
|
|
2181
|
-
`commitment must be 32 bytes (got ${params.commitment.length})`
|
|
2182
|
-
);
|
|
2183
|
-
}
|
|
2184
|
-
}
|
|
2185
|
-
|
|
2186
1726
|
// src/utils/pda.ts
|
|
2187
|
-
import { PublicKey
|
|
1727
|
+
import { PublicKey } from "@solana/web3.js";
|
|
2188
1728
|
function getShieldPoolPDAs(programId, mint = NATIVE_SOL_MINT) {
|
|
2189
1729
|
const pid = programId || CLOAK_PROGRAM_ID;
|
|
2190
|
-
const [pool] =
|
|
1730
|
+
const [pool] = PublicKey.findProgramAddressSync(
|
|
2191
1731
|
[Buffer.from("pool"), mint.toBuffer()],
|
|
2192
1732
|
pid
|
|
2193
1733
|
);
|
|
2194
|
-
const [merkleTree] =
|
|
1734
|
+
const [merkleTree] = PublicKey.findProgramAddressSync(
|
|
2195
1735
|
[Buffer.from("merkle_tree"), mint.toBuffer()],
|
|
2196
1736
|
pid
|
|
2197
1737
|
);
|
|
2198
|
-
const [treasury] =
|
|
1738
|
+
const [treasury] = PublicKey.findProgramAddressSync(
|
|
2199
1739
|
[Buffer.from("treasury"), mint.toBuffer()],
|
|
2200
1740
|
pid
|
|
2201
1741
|
);
|
|
2202
|
-
const [vaultAuthority] =
|
|
1742
|
+
const [vaultAuthority] = PublicKey.findProgramAddressSync(
|
|
2203
1743
|
[Buffer.from("vault_authority"), mint.toBuffer()],
|
|
2204
1744
|
pid
|
|
2205
1745
|
);
|
|
@@ -2215,7 +1755,7 @@ function getNullifierPDA(poolPubkey, nullifier, programId) {
|
|
|
2215
1755
|
if (nullifier.length !== 32) {
|
|
2216
1756
|
throw new Error(`Nullifier must be 32 bytes, got ${nullifier.length}`);
|
|
2217
1757
|
}
|
|
2218
|
-
return
|
|
1758
|
+
return PublicKey.findProgramAddressSync(
|
|
2219
1759
|
[Buffer.from("nullifier"), poolPubkey.toBuffer(), Buffer.from(nullifier)],
|
|
2220
1760
|
pid
|
|
2221
1761
|
);
|
|
@@ -2225,270 +1765,26 @@ function getSwapStatePDA(poolPubkey, nullifier, programId) {
|
|
|
2225
1765
|
if (nullifier.length !== 32) {
|
|
2226
1766
|
throw new Error(`Nullifier must be 32 bytes, got ${nullifier.length}`);
|
|
2227
1767
|
}
|
|
2228
|
-
return
|
|
1768
|
+
return PublicKey.findProgramAddressSync(
|
|
2229
1769
|
[Buffer.from("swap_state"), poolPubkey.toBuffer(), Buffer.from(nullifier)],
|
|
2230
1770
|
pid
|
|
2231
1771
|
);
|
|
2232
1772
|
}
|
|
2233
1773
|
|
|
2234
|
-
// src/utils/
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
var SKIP_CIRCUIT_VERIFY_ENV = typeof process !== "undefined" && process.env && process.env.CLOAK_SKIP_CIRCUIT_INTEGRITY_CHECK === "1";
|
|
2240
|
-
function isUrl(path) {
|
|
2241
|
-
return path.startsWith("http://") || path.startsWith("https://");
|
|
2242
|
-
}
|
|
2243
|
-
function resolveCircuitsUrl(circuitsPath2) {
|
|
2244
|
-
if (circuitsPath2.trim() !== "" && circuitsPath2.trim() !== DEFAULT_CIRCUITS_URL) {
|
|
2245
|
-
return DEFAULT_CIRCUITS_URL;
|
|
1774
|
+
// src/utils/logger.ts
|
|
1775
|
+
var globalDebugEnabled = false;
|
|
1776
|
+
function checkEnvDebug() {
|
|
1777
|
+
if (typeof process !== "undefined" && process.env) {
|
|
1778
|
+
return !!(process.env.CLOAK_DEBUG === "1" || process.env.CLOAK_DEBUG === "true" || process.env.DEBUG?.includes("cloak"));
|
|
2246
1779
|
}
|
|
2247
|
-
return
|
|
1780
|
+
return false;
|
|
2248
1781
|
}
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
|
|
2253
|
-
}
|
|
2254
|
-
const arrayBuffer = await response.arrayBuffer();
|
|
2255
|
-
return new Uint8Array(arrayBuffer);
|
|
1782
|
+
globalDebugEnabled = checkEnvDebug();
|
|
1783
|
+
function setDebugMode(enabled) {
|
|
1784
|
+
globalDebugEnabled = enabled;
|
|
2256
1785
|
}
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
2260
|
-
const buffer = bytes.buffer.slice(
|
|
2261
|
-
bytes.byteOffset,
|
|
2262
|
-
bytes.byteOffset + bytes.byteLength
|
|
2263
|
-
);
|
|
2264
|
-
const hashBuffer = await crypto.subtle.digest("SHA-256", buffer);
|
|
2265
|
-
return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2266
|
-
}
|
|
2267
|
-
const nodeCrypto = await getNodeCrypto();
|
|
2268
|
-
return nodeCrypto.createHash("sha256").update(data).digest("hex");
|
|
2269
|
-
}
|
|
2270
|
-
var _nodeCrypto = null;
|
|
2271
|
-
function nodeRequire(moduleName) {
|
|
2272
|
-
if (IS_BROWSER) {
|
|
2273
|
-
throw new Error(`Node.js ${moduleName} module not available in browser/React Native`);
|
|
2274
|
-
}
|
|
2275
|
-
try {
|
|
2276
|
-
const requireFunc = new Function("moduleName", "return require(moduleName)");
|
|
2277
|
-
return requireFunc(moduleName);
|
|
2278
|
-
} catch {
|
|
2279
|
-
throw new Error(`Cannot load Node.js module ${moduleName} synchronously in ESM.`);
|
|
2280
|
-
}
|
|
2281
|
-
}
|
|
2282
|
-
async function getNodeCrypto() {
|
|
2283
|
-
if (_nodeCrypto) return _nodeCrypto;
|
|
2284
|
-
_nodeCrypto = nodeRequire("crypto");
|
|
2285
|
-
return _nodeCrypto;
|
|
2286
|
-
}
|
|
2287
|
-
async function generateWithdrawRegularProof(inputs, circuitsPath2) {
|
|
2288
|
-
const circuitsUrl = resolveCircuitsUrl(circuitsPath2);
|
|
2289
|
-
let wasmInput;
|
|
2290
|
-
let zkeyInput;
|
|
2291
|
-
if (IS_BROWSER) {
|
|
2292
|
-
wasmInput = `${circuitsUrl}/withdraw_regular_js/withdraw_regular.wasm`;
|
|
2293
|
-
zkeyInput = `${circuitsUrl}/withdraw_regular_final.zkey`;
|
|
2294
|
-
} else {
|
|
2295
|
-
const wasmUrl = `${circuitsUrl}/withdraw_regular_js/withdraw_regular.wasm`;
|
|
2296
|
-
const zkeyUrl = `${circuitsUrl}/withdraw_regular_final.zkey`;
|
|
2297
|
-
const [wasmData, zkeyData] = await Promise.all([
|
|
2298
|
-
fetchFileAsBuffer(wasmUrl),
|
|
2299
|
-
fetchFileAsBuffer(zkeyUrl)
|
|
2300
|
-
]);
|
|
2301
|
-
wasmInput = wasmData;
|
|
2302
|
-
zkeyInput = zkeyData;
|
|
2303
|
-
}
|
|
2304
|
-
const verification = await verifyCircuitIntegrity(circuitsUrl, "withdraw_regular");
|
|
2305
|
-
if (!verification.valid) {
|
|
2306
|
-
throw new Error(
|
|
2307
|
-
`withdraw_regular circuit integrity verification failed: ${verification.error ?? "hash mismatch"}`
|
|
2308
|
-
);
|
|
2309
|
-
}
|
|
2310
|
-
const circuitInputs = {
|
|
2311
|
-
// Public signals
|
|
2312
|
-
root: inputs.root.toString(),
|
|
2313
|
-
nullifier: inputs.nullifier.toString(),
|
|
2314
|
-
outputs_hash: inputs.outputs_hash.toString(),
|
|
2315
|
-
public_amount: inputs.public_amount.toString(),
|
|
2316
|
-
// Private common inputs
|
|
2317
|
-
amount: inputs.amount.toString(),
|
|
2318
|
-
leaf_index: inputs.leaf_index.toString(),
|
|
2319
|
-
sk: inputs.sk.map((x) => x.toString()),
|
|
2320
|
-
r: inputs.r.map((x) => x.toString()),
|
|
2321
|
-
pathElements: inputs.pathElements.map((x) => x.toString()),
|
|
2322
|
-
pathIndices: inputs.pathIndices,
|
|
2323
|
-
// Outputs
|
|
2324
|
-
num_outputs: inputs.num_outputs,
|
|
2325
|
-
out_addr: inputs.out_addr.map((addr) => addr.map((x) => x.toString())),
|
|
2326
|
-
out_amount: inputs.out_amount.map((x) => x.toString()),
|
|
2327
|
-
out_flags: inputs.out_flags,
|
|
2328
|
-
// Fee calculation
|
|
2329
|
-
var_fee: inputs.var_fee.toString(),
|
|
2330
|
-
rem: inputs.rem.toString()
|
|
2331
|
-
};
|
|
2332
|
-
const { proof, publicSignals } = await snarkjs.groth16.fullProve(circuitInputs, wasmInput, zkeyInput);
|
|
2333
|
-
const proofBytes = proofToBytes(proof);
|
|
2334
|
-
return {
|
|
2335
|
-
proof,
|
|
2336
|
-
publicSignals,
|
|
2337
|
-
proofBytes,
|
|
2338
|
-
publicInputsBytes: new Uint8Array(0)
|
|
2339
|
-
// Not used, kept for compatibility
|
|
2340
|
-
};
|
|
2341
|
-
}
|
|
2342
|
-
async function generateWithdrawSwapProof(inputs, circuitsPath2) {
|
|
2343
|
-
const circuitsUrl = resolveCircuitsUrl(circuitsPath2);
|
|
2344
|
-
let wasmInput;
|
|
2345
|
-
let zkeyInput;
|
|
2346
|
-
if (IS_BROWSER) {
|
|
2347
|
-
wasmInput = `${circuitsUrl}/withdraw_swap_js/withdraw_swap.wasm`;
|
|
2348
|
-
zkeyInput = `${circuitsUrl}/withdraw_swap_final.zkey`;
|
|
2349
|
-
} else {
|
|
2350
|
-
const wasmUrl = `${circuitsUrl}/withdraw_swap_js/withdraw_swap.wasm`;
|
|
2351
|
-
const zkeyUrl = `${circuitsUrl}/withdraw_swap_final.zkey`;
|
|
2352
|
-
const [wasmData, zkeyData] = await Promise.all([
|
|
2353
|
-
fetchFileAsBuffer(wasmUrl),
|
|
2354
|
-
fetchFileAsBuffer(zkeyUrl)
|
|
2355
|
-
]);
|
|
2356
|
-
wasmInput = wasmData;
|
|
2357
|
-
zkeyInput = zkeyData;
|
|
2358
|
-
}
|
|
2359
|
-
const verification = await verifyCircuitIntegrity(circuitsUrl, "withdraw_swap");
|
|
2360
|
-
if (!verification.valid) {
|
|
2361
|
-
throw new Error(
|
|
2362
|
-
`withdraw_swap circuit integrity verification failed: ${verification.error ?? "hash mismatch"}`
|
|
2363
|
-
);
|
|
2364
|
-
}
|
|
2365
|
-
const sk = splitTo2Limbs(inputs.sk_spend);
|
|
2366
|
-
const r = splitTo2Limbs(inputs.r);
|
|
2367
|
-
const circuitInputs = {
|
|
2368
|
-
// Public signals (must be provided for witness generation)
|
|
2369
|
-
root: inputs.root.toString(),
|
|
2370
|
-
nullifier: inputs.nullifier.toString(),
|
|
2371
|
-
outputs_hash: inputs.outputs_hash.toString(),
|
|
2372
|
-
public_amount: inputs.public_amount.toString(),
|
|
2373
|
-
// Private inputs
|
|
2374
|
-
sk: sk.map((x) => x.toString()),
|
|
2375
|
-
r: r.map((x) => x.toString()),
|
|
2376
|
-
amount: inputs.amount.toString(),
|
|
2377
|
-
leaf_index: inputs.leaf_index.toString(),
|
|
2378
|
-
pathElements: inputs.path_elements.map((x) => x.toString()),
|
|
2379
|
-
pathIndices: inputs.path_indices,
|
|
2380
|
-
input_mint: inputs.input_mint.map((x) => x.toString()),
|
|
2381
|
-
output_mint: inputs.output_mint.map((x) => x.toString()),
|
|
2382
|
-
recipient_ata: inputs.recipient_ata.map((x) => x.toString()),
|
|
2383
|
-
min_output_amount: inputs.min_output_amount.toString(),
|
|
2384
|
-
// Note: fee is computed off-chain and validated on-chain:
|
|
2385
|
-
// - fixed_fee is 5000000 (0.005 SOL)
|
|
2386
|
-
// - var_fee and rem are computed from amount * 3 / 1000 (0.3%)
|
|
2387
|
-
var_fee: inputs.var_fee.toString(),
|
|
2388
|
-
rem: inputs.rem.toString()
|
|
2389
|
-
};
|
|
2390
|
-
const { proof, publicSignals } = await snarkjs.groth16.fullProve(
|
|
2391
|
-
circuitInputs,
|
|
2392
|
-
wasmInput,
|
|
2393
|
-
zkeyInput
|
|
2394
|
-
);
|
|
2395
|
-
const proofBytes = proofToBytes(proof);
|
|
2396
|
-
return {
|
|
2397
|
-
proof,
|
|
2398
|
-
publicSignals,
|
|
2399
|
-
proofBytes,
|
|
2400
|
-
publicInputsBytes: new Uint8Array(0)
|
|
2401
|
-
// Not used, kept for compatibility
|
|
2402
|
-
};
|
|
2403
|
-
}
|
|
2404
|
-
async function areCircuitsAvailable(circuitsPath2) {
|
|
2405
|
-
if (!circuitsPath2 || circuitsPath2 === "") {
|
|
2406
|
-
return false;
|
|
2407
|
-
}
|
|
2408
|
-
return isUrl(resolveCircuitsUrl(circuitsPath2));
|
|
2409
|
-
}
|
|
2410
|
-
async function getDefaultCircuitsPath() {
|
|
2411
|
-
return DEFAULT_CIRCUITS_URL;
|
|
2412
|
-
}
|
|
2413
|
-
var EXPECTED_CIRCUIT_HASHES = {
|
|
2414
|
-
withdraw_regular_wasm: "b80c364c926332111945ef437b07a71720fae774e08206cb11b6171f2a2420a3",
|
|
2415
|
-
withdraw_regular_zkey: "6dbca2612f7cc257b897d93384ae9dd09c1617fd1668f8287efb1b124488d144",
|
|
2416
|
-
withdraw_swap_wasm: "a296badc0047c211a4724c96a4e64a77cd75fbde7cfa4cf23af3640a723e5686",
|
|
2417
|
-
withdraw_swap_zkey: "95251954feaac80396651d12a0df3288630fa75db9410aebecb8aaee17a4f6ea"
|
|
2418
|
-
};
|
|
2419
|
-
async function verifyCircuitIntegrity(circuitsPath2, circuit) {
|
|
2420
|
-
const circuitsUrl = resolveCircuitsUrl(circuitsPath2);
|
|
2421
|
-
if (SKIP_CIRCUIT_VERIFY_ENV) {
|
|
2422
|
-
return {
|
|
2423
|
-
valid: true,
|
|
2424
|
-
circuit,
|
|
2425
|
-
error: "Verification skipped (CLOAK_SKIP_CIRCUIT_INTEGRITY_CHECK=1)"
|
|
2426
|
-
};
|
|
2427
|
-
}
|
|
2428
|
-
const expected = circuit === "withdraw_regular" ? {
|
|
2429
|
-
wasm: EXPECTED_CIRCUIT_HASHES.withdraw_regular_wasm,
|
|
2430
|
-
zkey: EXPECTED_CIRCUIT_HASHES.withdraw_regular_zkey
|
|
2431
|
-
} : {
|
|
2432
|
-
wasm: EXPECTED_CIRCUIT_HASHES.withdraw_swap_wasm,
|
|
2433
|
-
zkey: EXPECTED_CIRCUIT_HASHES.withdraw_swap_zkey
|
|
2434
|
-
};
|
|
2435
|
-
const wasmPath = `${circuitsUrl}/${circuit}_js/${circuit}.wasm`;
|
|
2436
|
-
const zkeyPath = `${circuitsUrl}/${circuit}_final.zkey`;
|
|
2437
|
-
try {
|
|
2438
|
-
const [wasmBytes, zkeyBytes] = await Promise.all([
|
|
2439
|
-
fetchFileAsBuffer(wasmPath),
|
|
2440
|
-
fetchFileAsBuffer(zkeyPath)
|
|
2441
|
-
]);
|
|
2442
|
-
const computed = {
|
|
2443
|
-
wasm: await sha256Hex(wasmBytes),
|
|
2444
|
-
zkey: await sha256Hex(zkeyBytes)
|
|
2445
|
-
};
|
|
2446
|
-
if (computed.wasm === expected.wasm && computed.zkey === expected.zkey) {
|
|
2447
|
-
return {
|
|
2448
|
-
valid: true,
|
|
2449
|
-
circuit,
|
|
2450
|
-
computed,
|
|
2451
|
-
expected
|
|
2452
|
-
};
|
|
2453
|
-
}
|
|
2454
|
-
return {
|
|
2455
|
-
valid: false,
|
|
2456
|
-
circuit,
|
|
2457
|
-
error: `Circuit artifact hash mismatch for ${circuit}`,
|
|
2458
|
-
computed,
|
|
2459
|
-
expected
|
|
2460
|
-
};
|
|
2461
|
-
} catch (error) {
|
|
2462
|
-
return {
|
|
2463
|
-
valid: false,
|
|
2464
|
-
circuit,
|
|
2465
|
-
error: `Circuit verification failed: ${error instanceof Error ? error.message : String(error)}`
|
|
2466
|
-
};
|
|
2467
|
-
}
|
|
2468
|
-
}
|
|
2469
|
-
async function verifyAllCircuits(circuitsPath2) {
|
|
2470
|
-
const circuitsUrl = resolveCircuitsUrl(circuitsPath2);
|
|
2471
|
-
const results = await Promise.all([
|
|
2472
|
-
verifyCircuitIntegrity(circuitsUrl, "withdraw_regular"),
|
|
2473
|
-
verifyCircuitIntegrity(circuitsUrl, "withdraw_swap")
|
|
2474
|
-
]);
|
|
2475
|
-
return results;
|
|
2476
|
-
}
|
|
2477
|
-
|
|
2478
|
-
// src/utils/logger.ts
|
|
2479
|
-
var globalDebugEnabled = false;
|
|
2480
|
-
function checkEnvDebug() {
|
|
2481
|
-
if (typeof process !== "undefined" && process.env) {
|
|
2482
|
-
return !!(process.env.CLOAK_DEBUG === "1" || process.env.CLOAK_DEBUG === "true" || process.env.DEBUG?.includes("cloak"));
|
|
2483
|
-
}
|
|
2484
|
-
return false;
|
|
2485
|
-
}
|
|
2486
|
-
globalDebugEnabled = checkEnvDebug();
|
|
2487
|
-
function setDebugMode(enabled) {
|
|
2488
|
-
globalDebugEnabled = enabled;
|
|
2489
|
-
}
|
|
2490
|
-
function isDebugEnabled() {
|
|
2491
|
-
return globalDebugEnabled;
|
|
1786
|
+
function isDebugEnabled() {
|
|
1787
|
+
return globalDebugEnabled;
|
|
2492
1788
|
}
|
|
2493
1789
|
var LOG_COLORS = {
|
|
2494
1790
|
DEBUG: "\x1B[90m",
|
|
@@ -2691,44 +1987,16 @@ async function computeProofInternal(leafIndex, _nextIndex, subtrees) {
|
|
|
2691
1987
|
}
|
|
2692
1988
|
|
|
2693
1989
|
// src/core/CloakSDK.ts
|
|
2694
|
-
var
|
|
2695
|
-
function createSetLoadedAccountsDataSizeLimitInstruction(bytes) {
|
|
2696
|
-
const data = Buffer.alloc(5);
|
|
2697
|
-
data.writeUInt8(4, 0);
|
|
2698
|
-
data.writeUInt32LE(bytes, 1);
|
|
2699
|
-
return new TransactionInstruction2({
|
|
2700
|
-
keys: [],
|
|
2701
|
-
programId: COMPUTE_BUDGET_PROGRAM_ID,
|
|
2702
|
-
data
|
|
2703
|
-
});
|
|
2704
|
-
}
|
|
2705
|
-
var CLOAK_PROGRAM_ID = new PublicKey4("zh1eLd6rSphLejbFfJEneUwzHRfMKxgzrgkfwA6qRkW");
|
|
1990
|
+
var CLOAK_PROGRAM_ID = new PublicKey2("zh1eLd6rSphLejbFfJEneUwzHRfMKxgzrgkfwA6qRkW");
|
|
2706
1991
|
var CLOAK_API_URL = "https://api.cloak.ag";
|
|
2707
1992
|
var CloakSDK = class {
|
|
2708
|
-
/**
|
|
2709
|
-
* Create a new Cloak SDK client
|
|
2710
|
-
*
|
|
2711
|
-
* @param config - Client configuration
|
|
2712
|
-
*
|
|
2713
|
-
* @example Node.js mode (with keypair)
|
|
2714
|
-
* ```typescript
|
|
2715
|
-
* const sdk = new CloakSDK({
|
|
2716
|
-
* keypairBytes: keypair.secretKey,
|
|
2717
|
-
* network: "devnet"
|
|
2718
|
-
* });
|
|
2719
|
-
* ```
|
|
2720
|
-
*
|
|
2721
|
-
* @example Browser mode (with wallet adapter)
|
|
2722
|
-
* ```typescript
|
|
2723
|
-
* const sdk = new CloakSDK({
|
|
2724
|
-
* wallet: walletAdapter,
|
|
2725
|
-
* network: "devnet"
|
|
2726
|
-
* });
|
|
2727
|
-
* ```
|
|
2728
|
-
*/
|
|
2729
1993
|
constructor(config) {
|
|
2730
1994
|
if (!config.keypairBytes && !config.wallet) {
|
|
2731
|
-
throw new
|
|
1995
|
+
throw new CloakError(
|
|
1996
|
+
"Must provide either keypairBytes (Node.js) or wallet (Browser)",
|
|
1997
|
+
"validation",
|
|
1998
|
+
false
|
|
1999
|
+
);
|
|
2732
2000
|
}
|
|
2733
2001
|
if (config.debug) {
|
|
2734
2002
|
setDebugMode(true);
|
|
@@ -2748,13 +2016,9 @@ var CloakSDK = class {
|
|
|
2748
2016
|
}
|
|
2749
2017
|
}
|
|
2750
2018
|
const programId = config.programId || CLOAK_PROGRAM_ID;
|
|
2751
|
-
const { pool, merkleTree, treasury } = getShieldPoolPDAs(
|
|
2752
|
-
programId,
|
|
2753
|
-
NATIVE_SOL_MINT
|
|
2754
|
-
);
|
|
2019
|
+
const { pool, merkleTree, treasury } = getShieldPoolPDAs(programId, NATIVE_SOL_MINT);
|
|
2755
2020
|
this.config = {
|
|
2756
2021
|
network: config.network || "mainnet",
|
|
2757
|
-
keypairBytes: config.keypairBytes,
|
|
2758
2022
|
cloakKeys: config.cloakKeys,
|
|
2759
2023
|
programId,
|
|
2760
2024
|
poolAddress: pool,
|
|
@@ -2763,887 +2027,31 @@ var CloakSDK = class {
|
|
|
2763
2027
|
debug: config.debug
|
|
2764
2028
|
};
|
|
2765
2029
|
}
|
|
2766
|
-
/**
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
if (this.keypair) {
|
|
2771
|
-
return this.keypair.publicKey;
|
|
2772
|
-
}
|
|
2773
|
-
if (this.wallet?.publicKey) {
|
|
2774
|
-
return this.wallet.publicKey;
|
|
2775
|
-
}
|
|
2776
|
-
throw new Error("No public key available - wallet not connected or keypair not provided");
|
|
2777
|
-
}
|
|
2778
|
-
/**
|
|
2779
|
-
* Check if the SDK is using a wallet adapter
|
|
2780
|
-
*/
|
|
2781
|
-
isWalletMode() {
|
|
2782
|
-
return !!this.wallet && !this.keypair;
|
|
2783
|
-
}
|
|
2784
|
-
/**
|
|
2785
|
-
* Deposit SOL into the Cloak protocol
|
|
2786
|
-
*
|
|
2787
|
-
* Creates a new note (or uses a provided one), submits a deposit transaction,
|
|
2788
|
-
* and registers with the indexer.
|
|
2789
|
-
*
|
|
2790
|
-
* @param connection - Solana connection
|
|
2791
|
-
* @param payer - Payer wallet with sendTransaction method
|
|
2792
|
-
* @param amountOrNote - Amount in lamports OR an existing note to deposit
|
|
2793
|
-
* @param options - Optional configuration
|
|
2794
|
-
* @returns Deposit result with note and transaction info
|
|
2795
|
-
*
|
|
2796
|
-
* @example
|
|
2797
|
-
* ```typescript
|
|
2798
|
-
* // Generate and deposit in one step
|
|
2799
|
-
* const result = await client.deposit(
|
|
2800
|
-
* connection,
|
|
2801
|
-
* wallet,
|
|
2802
|
-
* 1_000_000_000,
|
|
2803
|
-
* {
|
|
2804
|
-
* onProgress: (status) => console.log(status)
|
|
2805
|
-
* }
|
|
2806
|
-
* );
|
|
2807
|
-
*
|
|
2808
|
-
* // Or deposit a pre-generated note
|
|
2809
|
-
* const note = client.generateNote(1_000_000_000);
|
|
2810
|
-
* const result = await client.deposit(connection, wallet, note);
|
|
2811
|
-
* ```
|
|
2812
|
-
*/
|
|
2813
|
-
async deposit(connection, amountOrNote, options) {
|
|
2814
|
-
try {
|
|
2815
|
-
let note;
|
|
2816
|
-
let isNewNote = false;
|
|
2817
|
-
if (typeof amountOrNote === "number") {
|
|
2818
|
-
options?.onProgress?.("generating_note", { message: "Generating note with secrets..." });
|
|
2819
|
-
note = await generateNote(amountOrNote, this.config.network);
|
|
2820
|
-
isNewNote = true;
|
|
2821
|
-
} else {
|
|
2822
|
-
note = amountOrNote;
|
|
2823
|
-
if (note.depositSignature) {
|
|
2824
|
-
throw new Error("Note has already been deposited");
|
|
2825
|
-
}
|
|
2826
|
-
}
|
|
2827
|
-
if (isNewNote) {
|
|
2828
|
-
await this.storage.saveNote(note);
|
|
2829
|
-
if (options?.onNoteGenerated) {
|
|
2830
|
-
options?.onProgress?.("awaiting_note_acknowledgment", {
|
|
2831
|
-
message: "Waiting for note to be saved before proceeding with deposit..."
|
|
2832
|
-
});
|
|
2833
|
-
try {
|
|
2834
|
-
await options.onNoteGenerated(note);
|
|
2835
|
-
} catch (error) {
|
|
2836
|
-
throw new Error(
|
|
2837
|
-
`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.`
|
|
2838
|
-
);
|
|
2839
|
-
}
|
|
2840
|
-
}
|
|
2841
|
-
options?.onProgress?.("note_saved", {
|
|
2842
|
-
message: "Note saved. Proceeding with on-chain deposit..."
|
|
2843
|
-
});
|
|
2844
|
-
}
|
|
2845
|
-
const payerPubkey = this.getPublicKey();
|
|
2846
|
-
const balance = await connection.getBalance(payerPubkey);
|
|
2847
|
-
const requiredAmount = note.amount + 5e3;
|
|
2848
|
-
if (balance < requiredAmount) {
|
|
2849
|
-
throw new Error(
|
|
2850
|
-
`Insufficient balance. Required: ${requiredAmount} lamports (${note.amount} + fees), Available: ${balance} lamports`
|
|
2851
|
-
);
|
|
2852
|
-
}
|
|
2853
|
-
const commitmentBytes = hexToBytes(note.commitment);
|
|
2854
|
-
const programId = this.config.programId || CLOAK_PROGRAM_ID;
|
|
2855
|
-
const depositIx = createDepositInstruction({
|
|
2856
|
-
programId,
|
|
2857
|
-
payer: payerPubkey,
|
|
2858
|
-
pool: this.config.poolAddress,
|
|
2859
|
-
merkleTree: this.config.merkleTreeAddress,
|
|
2860
|
-
amount: note.amount,
|
|
2861
|
-
commitment: commitmentBytes
|
|
2862
|
-
});
|
|
2863
|
-
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
|
|
2864
|
-
const priorityFee = options?.priorityFee ?? 1e4;
|
|
2865
|
-
const cuPriceIx = ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priorityFee });
|
|
2866
|
-
const dataSizeLimit = options?.loadedAccountsDataSizeLimit ?? 256 * 1024;
|
|
2867
|
-
const dataSizeLimitIx = dataSizeLimit > 0 ? createSetLoadedAccountsDataSizeLimitInstruction(dataSizeLimit) : null;
|
|
2868
|
-
let computeUnits;
|
|
2869
|
-
if (options?.optimizeCU && this.keypair) {
|
|
2870
|
-
options?.onProgress?.("simulating", { message: "Simulating transaction for optimal CU..." });
|
|
2871
|
-
const simCuLimitIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 2e5 });
|
|
2872
|
-
const simTransaction = new Transaction({
|
|
2873
|
-
feePayer: payerPubkey,
|
|
2874
|
-
recentBlockhash: blockhash
|
|
2875
|
-
}).add(simCuLimitIx).add(cuPriceIx);
|
|
2876
|
-
if (dataSizeLimitIx) {
|
|
2877
|
-
simTransaction.add(dataSizeLimitIx);
|
|
2878
|
-
}
|
|
2879
|
-
simTransaction.add(depositIx);
|
|
2880
|
-
try {
|
|
2881
|
-
const simulation = await connection.simulateTransaction(simTransaction, [this.keypair]);
|
|
2882
|
-
if (simulation.value.err) {
|
|
2883
|
-
console.warn("Simulation failed, using default CU:", simulation.value.err);
|
|
2884
|
-
computeUnits = 4e4;
|
|
2885
|
-
} else {
|
|
2886
|
-
const simulatedCU = simulation.value.unitsConsumed ?? 3e4;
|
|
2887
|
-
computeUnits = Math.ceil(simulatedCU * 1.2);
|
|
2888
|
-
console.log(`CU optimization: ${simulatedCU} simulated \u2192 ${computeUnits} limit`);
|
|
2889
|
-
}
|
|
2890
|
-
} catch (simError) {
|
|
2891
|
-
console.warn("Simulation RPC error, using default CU:", simError);
|
|
2892
|
-
computeUnits = 4e4;
|
|
2893
|
-
}
|
|
2894
|
-
} else if (options?.optimizeCU && this.wallet) {
|
|
2895
|
-
console.warn("CU optimization via simulation not available in wallet mode (would require double signing). Using default.");
|
|
2896
|
-
computeUnits = options?.computeUnits ?? 4e4;
|
|
2897
|
-
} else {
|
|
2898
|
-
computeUnits = options?.computeUnits ?? 4e4;
|
|
2899
|
-
}
|
|
2900
|
-
const cuLimitIx = ComputeBudgetProgram.setComputeUnitLimit({ units: computeUnits });
|
|
2901
|
-
const transaction = new Transaction({
|
|
2902
|
-
feePayer: payerPubkey,
|
|
2903
|
-
recentBlockhash: blockhash
|
|
2904
|
-
}).add(cuLimitIx).add(cuPriceIx);
|
|
2905
|
-
if (dataSizeLimitIx) {
|
|
2906
|
-
transaction.add(dataSizeLimitIx);
|
|
2907
|
-
}
|
|
2908
|
-
transaction.add(depositIx);
|
|
2909
|
-
if (!transaction.feePayer) {
|
|
2910
|
-
throw new Error("Transaction feePayer is not set");
|
|
2911
|
-
}
|
|
2912
|
-
if (!transaction.recentBlockhash) {
|
|
2913
|
-
throw new Error("Transaction recentBlockhash is not set");
|
|
2914
|
-
}
|
|
2915
|
-
let signature;
|
|
2916
|
-
if (this.wallet) {
|
|
2917
|
-
if (!this.wallet.publicKey) {
|
|
2918
|
-
throw new Error("Wallet not connected - publicKey is null");
|
|
2919
|
-
}
|
|
2920
|
-
if (this.wallet.signTransaction) {
|
|
2921
|
-
try {
|
|
2922
|
-
const signedTransaction = await this.wallet.signTransaction(transaction);
|
|
2923
|
-
const rawTransaction = signedTransaction.serialize();
|
|
2924
|
-
signature = await connection.sendRawTransaction(rawTransaction, {
|
|
2925
|
-
skipPreflight: options?.skipPreflight || false,
|
|
2926
|
-
preflightCommitment: "confirmed",
|
|
2927
|
-
maxRetries: 3
|
|
2928
|
-
});
|
|
2929
|
-
} catch (signError) {
|
|
2930
|
-
if (this.wallet.sendTransaction) {
|
|
2931
|
-
signature = await this.wallet.sendTransaction(transaction, connection, {
|
|
2932
|
-
skipPreflight: options?.skipPreflight || false,
|
|
2933
|
-
preflightCommitment: "confirmed",
|
|
2934
|
-
maxRetries: 3
|
|
2935
|
-
});
|
|
2936
|
-
} else {
|
|
2937
|
-
throw signError;
|
|
2938
|
-
}
|
|
2939
|
-
}
|
|
2940
|
-
} else if (this.wallet.sendTransaction) {
|
|
2941
|
-
signature = await this.wallet.sendTransaction(transaction, connection, {
|
|
2942
|
-
skipPreflight: options?.skipPreflight || false,
|
|
2943
|
-
preflightCommitment: "confirmed",
|
|
2944
|
-
maxRetries: 3
|
|
2945
|
-
});
|
|
2946
|
-
} else {
|
|
2947
|
-
throw new Error("Wallet adapter must provide either sendTransaction or signTransaction");
|
|
2948
|
-
}
|
|
2949
|
-
} else if (this.keypair) {
|
|
2950
|
-
signature = await connection.sendTransaction(transaction, [this.keypair], {
|
|
2951
|
-
skipPreflight: options?.skipPreflight || false,
|
|
2952
|
-
preflightCommitment: "confirmed",
|
|
2953
|
-
maxRetries: 3
|
|
2954
|
-
});
|
|
2955
|
-
} else {
|
|
2956
|
-
throw new Error("No signing method available - provide keypair or wallet");
|
|
2957
|
-
}
|
|
2958
|
-
const confirmation = await connection.confirmTransaction({
|
|
2959
|
-
signature,
|
|
2960
|
-
blockhash,
|
|
2961
|
-
lastValidBlockHeight
|
|
2962
|
-
});
|
|
2963
|
-
if (confirmation.value.err) {
|
|
2964
|
-
throw new Error(
|
|
2965
|
-
`Transaction failed: ${JSON.stringify(confirmation.value.err)}`
|
|
2966
|
-
);
|
|
2967
|
-
}
|
|
2968
|
-
const txDetails = await connection.getTransaction(signature, {
|
|
2969
|
-
commitment: "confirmed",
|
|
2970
|
-
maxSupportedTransactionVersion: 0
|
|
2971
|
-
});
|
|
2972
|
-
const depositSlot = txDetails?.slot ?? 0;
|
|
2973
|
-
let leafIndex = null;
|
|
2974
|
-
let root = null;
|
|
2975
|
-
try {
|
|
2976
|
-
if (txDetails?.meta?.logMessages) {
|
|
2977
|
-
for (const log of txDetails.meta.logMessages) {
|
|
2978
|
-
if (log.includes("Program data:")) {
|
|
2979
|
-
try {
|
|
2980
|
-
const parts = log.split("Program data:");
|
|
2981
|
-
if (parts.length > 1) {
|
|
2982
|
-
const base64Data = parts[1].trim();
|
|
2983
|
-
const eventData = Buffer.from(base64Data, "base64");
|
|
2984
|
-
if (eventData.length >= 81 && eventData[0] === 1) {
|
|
2985
|
-
const parsedLeafIndex = Number(eventData.readBigUInt64LE(1));
|
|
2986
|
-
const loggedCommitment = eventData.slice(9, 41);
|
|
2987
|
-
if (Buffer.compare(loggedCommitment, Buffer.from(commitmentBytes)) === 0) {
|
|
2988
|
-
leafIndex = parsedLeafIndex;
|
|
2989
|
-
const loggedRoot = eventData.slice(49, 81);
|
|
2990
|
-
root = Buffer.from(loggedRoot).toString("hex");
|
|
2991
|
-
break;
|
|
2992
|
-
}
|
|
2993
|
-
}
|
|
2994
|
-
}
|
|
2995
|
-
} catch (e) {
|
|
2996
|
-
continue;
|
|
2997
|
-
}
|
|
2998
|
-
}
|
|
2999
|
-
}
|
|
3000
|
-
}
|
|
3001
|
-
} catch (e) {
|
|
3002
|
-
}
|
|
3003
|
-
if (leafIndex === null) {
|
|
3004
|
-
const programId2 = this.config.programId || CLOAK_PROGRAM_ID;
|
|
3005
|
-
const mintForSOL = NATIVE_SOL_MINT;
|
|
3006
|
-
const { merkleTree } = getShieldPoolPDAs(programId2, mintForSOL);
|
|
3007
|
-
const merkleTreeAccount = await connection.getAccountInfo(merkleTree);
|
|
3008
|
-
if (!merkleTreeAccount) {
|
|
3009
|
-
throw new Error("Failed to fetch merkle tree account");
|
|
3010
|
-
}
|
|
3011
|
-
const nextIndex = Number(merkleTreeAccount.data.readBigUInt64LE(32));
|
|
3012
|
-
leafIndex = nextIndex - 1;
|
|
3013
|
-
const rootBytes = merkleTreeAccount.data.slice(1064, 1096);
|
|
3014
|
-
root = Buffer.from(rootBytes).toString("hex");
|
|
3015
|
-
}
|
|
3016
|
-
if (leafIndex === null || root === null) {
|
|
3017
|
-
throw new Error("Failed to get leaf index and root from transaction logs or on-chain state");
|
|
3018
|
-
}
|
|
3019
|
-
let merkleProof;
|
|
3020
|
-
try {
|
|
3021
|
-
const programId2 = this.config.programId || CLOAK_PROGRAM_ID;
|
|
3022
|
-
const mintForSOL = NATIVE_SOL_MINT;
|
|
3023
|
-
const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId2, mintForSOL);
|
|
3024
|
-
const chainProof = await computeProofFromChain(connection, merkleTreePDA, leafIndex);
|
|
3025
|
-
merkleProof = {
|
|
3026
|
-
pathElements: chainProof.pathElements,
|
|
3027
|
-
pathIndices: chainProof.pathIndices
|
|
3028
|
-
};
|
|
3029
|
-
root = chainProof.root;
|
|
3030
|
-
} catch (proofError) {
|
|
3031
|
-
console.warn("[SDK] Could not compute proof from chain, will fetch at withdrawal:", proofError);
|
|
3032
|
-
}
|
|
3033
|
-
const updatedNote = updateNoteWithDeposit(note, {
|
|
3034
|
-
signature,
|
|
3035
|
-
slot: depositSlot,
|
|
3036
|
-
leafIndex,
|
|
3037
|
-
root,
|
|
3038
|
-
merkleProof
|
|
3039
|
-
});
|
|
3040
|
-
await this.storage.updateNote(note.commitment, {
|
|
3041
|
-
depositSignature: signature,
|
|
3042
|
-
depositSlot,
|
|
3043
|
-
leafIndex,
|
|
3044
|
-
root,
|
|
3045
|
-
merkleProof
|
|
3046
|
-
});
|
|
3047
|
-
return {
|
|
3048
|
-
note: updatedNote,
|
|
3049
|
-
signature,
|
|
3050
|
-
leafIndex,
|
|
3051
|
-
root
|
|
3052
|
-
};
|
|
3053
|
-
} catch (error) {
|
|
3054
|
-
throw this.wrapError(error, "Deposit failed");
|
|
3055
|
-
}
|
|
3056
|
-
}
|
|
3057
|
-
/**
|
|
3058
|
-
* Private transfer with up to 5 recipients
|
|
3059
|
-
*
|
|
3060
|
-
* Handles the complete private transfer flow:
|
|
3061
|
-
* 1. If note is not deposited, deposits it first and waits for confirmation
|
|
3062
|
-
* 2. Generates a zero-knowledge proof
|
|
3063
|
-
* 3. Submits the withdrawal via relay service to recipients
|
|
3064
|
-
*
|
|
3065
|
-
* This is the main method for performing private transfers - it handles everything!
|
|
3066
|
-
*
|
|
3067
|
-
* @param connection - Solana connection (required for deposit if not already deposited)
|
|
3068
|
-
* @param payer - Payer wallet (required for deposit if not already deposited)
|
|
3069
|
-
* @param note - Note to spend (can be deposited or not)
|
|
3070
|
-
* @param recipients - Array of 1-5 recipients with amounts
|
|
3071
|
-
* @param options - Optional configuration
|
|
3072
|
-
* @returns Transfer result with signature and outputs
|
|
3073
|
-
*
|
|
3074
|
-
* @example
|
|
3075
|
-
* ```typescript
|
|
3076
|
-
* // Create a note (not deposited yet)
|
|
3077
|
-
* const note = client.generateNote(1_000_000_000);
|
|
3078
|
-
*
|
|
3079
|
-
* // privateTransfer handles the full flow: deposit + withdraw
|
|
3080
|
-
* const result = await client.privateTransfer(
|
|
3081
|
-
* connection,
|
|
3082
|
-
* wallet,
|
|
3083
|
-
* note,
|
|
3084
|
-
* [
|
|
3085
|
-
* { recipient: new PublicKey("..."), amount: 500_000_000 },
|
|
3086
|
-
* { recipient: new PublicKey("..."), amount: 492_500_000 }
|
|
3087
|
-
* ],
|
|
3088
|
-
* {
|
|
3089
|
-
* relayFeeBps: 50, // 0.5%
|
|
3090
|
-
* onProgress: (status) => console.log(status),
|
|
3091
|
-
* onProofProgress: (pct) => console.log(`Proof: ${pct}%`)
|
|
3092
|
-
* }
|
|
3093
|
-
* );
|
|
3094
|
-
* console.log(`Success! TX: ${result.signature}`);
|
|
3095
|
-
* ```
|
|
3096
|
-
*/
|
|
3097
|
-
async privateTransfer(connection, note, recipients, options) {
|
|
3098
|
-
if (!isWithdrawable(note)) {
|
|
3099
|
-
const depositResult = await this.deposit(connection, note, {
|
|
3100
|
-
skipPreflight: false
|
|
3101
|
-
});
|
|
3102
|
-
note = depositResult.note;
|
|
3103
|
-
}
|
|
3104
|
-
const protocolFee = note.amount - getDistributableAmount(note.amount);
|
|
3105
|
-
const feeBps = Math.ceil(protocolFee * 1e4 / note.amount);
|
|
3106
|
-
const distributableAmount = getDistributableAmount(note.amount);
|
|
3107
|
-
validateTransfers(recipients, distributableAmount);
|
|
3108
|
-
if (!note.leafIndex && note.leafIndex !== 0) {
|
|
3109
|
-
throw new Error("Note must have a leaf index (note must be deposited)");
|
|
3110
|
-
}
|
|
3111
|
-
if (!isValidHex(note.r, 32)) {
|
|
3112
|
-
throw new Error("Note r must be 64 hex characters (32 bytes)");
|
|
3113
|
-
}
|
|
3114
|
-
if (!isValidHex(note.sk_spend, 32)) {
|
|
3115
|
-
throw new Error("Note sk_spend must be 64 hex characters (32 bytes)");
|
|
3116
|
-
}
|
|
3117
|
-
const nullifier = await computeNullifierAsync(note.sk_spend, note.leafIndex);
|
|
3118
|
-
const nullifierHex = nullifier.toString(16).padStart(64, "0");
|
|
3119
|
-
const outputsHash = await computeOutputsHashAsync(recipients);
|
|
3120
|
-
const outputsHashHex = outputsHash.toString(16).padStart(64, "0");
|
|
3121
|
-
const circuitsPath2 = await getDefaultCircuitsPath();
|
|
3122
|
-
const useDirectProof = await areCircuitsAvailable(circuitsPath2);
|
|
3123
|
-
if (!useDirectProof) {
|
|
3124
|
-
throw new Error(
|
|
3125
|
-
`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.`
|
|
3126
|
-
);
|
|
3127
|
-
}
|
|
3128
|
-
const MAX_PROOF_RETRIES = 3;
|
|
3129
|
-
let lastError = null;
|
|
3130
|
-
for (let attempt = 0; attempt < MAX_PROOF_RETRIES; attempt++) {
|
|
3131
|
-
try {
|
|
3132
|
-
if (attempt > 0) {
|
|
3133
|
-
options?.onProgress?.(`proof_refresh_${attempt + 1}`);
|
|
3134
|
-
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
3135
|
-
}
|
|
3136
|
-
let merkleProof;
|
|
3137
|
-
let merkleRoot;
|
|
3138
|
-
const hasStoredProof = note.merkleProof && note.root && note.merkleProof.pathElements && note.merkleProof.pathElements.length > 0;
|
|
3139
|
-
if (attempt === 0 && hasStoredProof) {
|
|
3140
|
-
merkleProof = note.merkleProof;
|
|
3141
|
-
merkleRoot = note.root;
|
|
3142
|
-
} else {
|
|
3143
|
-
const programId = this.config.programId || CLOAK_PROGRAM_ID;
|
|
3144
|
-
const mintForSOL = NATIVE_SOL_MINT;
|
|
3145
|
-
const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId, mintForSOL);
|
|
3146
|
-
const chainProof = await computeProofFromChain(
|
|
3147
|
-
connection,
|
|
3148
|
-
merkleTreePDA,
|
|
3149
|
-
note.leafIndex
|
|
3150
|
-
);
|
|
3151
|
-
merkleProof = {
|
|
3152
|
-
pathElements: chainProof.pathElements,
|
|
3153
|
-
pathIndices: chainProof.pathIndices,
|
|
3154
|
-
root: chainProof.root
|
|
3155
|
-
};
|
|
3156
|
-
merkleRoot = chainProof.root;
|
|
3157
|
-
note.merkleProof = merkleProof;
|
|
3158
|
-
note.root = merkleRoot;
|
|
3159
|
-
}
|
|
3160
|
-
if (!merkleProof || !merkleRoot) {
|
|
3161
|
-
throw new Error("Failed to get Merkle proof from chain");
|
|
3162
|
-
}
|
|
3163
|
-
if (!merkleProof.pathElements || merkleProof.pathElements.length === 0) {
|
|
3164
|
-
throw new Error("Merkle proof is invalid: missing path elements");
|
|
3165
|
-
}
|
|
3166
|
-
if (merkleProof.pathElements.length !== merkleProof.pathIndices.length) {
|
|
3167
|
-
throw new Error("Merkle proof is invalid: path elements and indices length mismatch");
|
|
3168
|
-
}
|
|
3169
|
-
for (let i = 0; i < merkleProof.pathIndices.length; i++) {
|
|
3170
|
-
const idx = merkleProof.pathIndices[i];
|
|
3171
|
-
if (idx !== 0 && idx !== 1) {
|
|
3172
|
-
throw new Error(`Merkle proof path index at position ${i} must be 0 or 1, got ${idx}`);
|
|
3173
|
-
}
|
|
3174
|
-
}
|
|
3175
|
-
if (!isValidHex(merkleRoot, 32)) {
|
|
3176
|
-
throw new Error("Merkle root must be 64 hex characters (32 bytes)");
|
|
3177
|
-
}
|
|
3178
|
-
for (let i = 0; i < merkleProof.pathElements.length; i++) {
|
|
3179
|
-
const element = merkleProof.pathElements[i];
|
|
3180
|
-
if (typeof element !== "string" || !isValidHex(element, 32)) {
|
|
3181
|
-
throw new Error(`Merkle proof path element at position ${i} must be 64 hex characters (32 bytes)`);
|
|
3182
|
-
}
|
|
3183
|
-
}
|
|
3184
|
-
const sk_spend_bigint = BigInt("0x" + note.sk_spend);
|
|
3185
|
-
const r_bigint = BigInt("0x" + note.r);
|
|
3186
|
-
const root_bigint = BigInt("0x" + merkleRoot);
|
|
3187
|
-
const nullifier_bigint = BigInt("0x" + nullifierHex);
|
|
3188
|
-
const outputs_hash_bigint = BigInt("0x" + outputsHashHex);
|
|
3189
|
-
const amount_bigint = BigInt(note.amount);
|
|
3190
|
-
const pathElements = merkleProof.pathElements.map((p) => BigInt("0x" + p));
|
|
3191
|
-
const outAddr = [];
|
|
3192
|
-
for (let i = 0; i < 5; i++) {
|
|
3193
|
-
if (i < recipients.length) {
|
|
3194
|
-
const [lo, hi] = pubkeyToLimbs(recipients[i].recipient.toBytes());
|
|
3195
|
-
outAddr.push([lo, hi]);
|
|
3196
|
-
} else {
|
|
3197
|
-
outAddr.push([0n, 0n]);
|
|
3198
|
-
}
|
|
3199
|
-
}
|
|
3200
|
-
const t = amount_bigint * 3n;
|
|
3201
|
-
const varFee = t / 1000n;
|
|
3202
|
-
const rem = t % 1000n;
|
|
3203
|
-
const outAmount = [];
|
|
3204
|
-
const outFlags = [];
|
|
3205
|
-
for (let i = 0; i < 5; i++) {
|
|
3206
|
-
if (i < recipients.length) {
|
|
3207
|
-
outAmount.push(BigInt(recipients[i].amount));
|
|
3208
|
-
outFlags.push(1);
|
|
3209
|
-
} else {
|
|
3210
|
-
outAmount.push(0n);
|
|
3211
|
-
outFlags.push(0);
|
|
3212
|
-
}
|
|
3213
|
-
}
|
|
3214
|
-
const sk = splitTo2Limbs(sk_spend_bigint);
|
|
3215
|
-
const r = splitTo2Limbs(r_bigint);
|
|
3216
|
-
if (attempt === 0) {
|
|
3217
|
-
const computedCommitment = await computeCommitment2(amount_bigint, r_bigint, sk_spend_bigint);
|
|
3218
|
-
const noteCommitment = BigInt("0x" + note.commitment);
|
|
3219
|
-
if (computedCommitment !== noteCommitment) {
|
|
3220
|
-
throw new Error(
|
|
3221
|
-
`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.`
|
|
3222
|
-
);
|
|
3223
|
-
}
|
|
3224
|
-
}
|
|
3225
|
-
const proofInputs = {
|
|
3226
|
-
root: root_bigint,
|
|
3227
|
-
nullifier: nullifier_bigint,
|
|
3228
|
-
outputs_hash: outputs_hash_bigint,
|
|
3229
|
-
public_amount: amount_bigint,
|
|
3230
|
-
amount: amount_bigint,
|
|
3231
|
-
leaf_index: BigInt(note.leafIndex),
|
|
3232
|
-
sk,
|
|
3233
|
-
r,
|
|
3234
|
-
pathElements,
|
|
3235
|
-
pathIndices: merkleProof.pathIndices,
|
|
3236
|
-
num_outputs: recipients.length,
|
|
3237
|
-
out_addr: outAddr,
|
|
3238
|
-
out_amount: outAmount,
|
|
3239
|
-
out_flags: outFlags,
|
|
3240
|
-
var_fee: varFee,
|
|
3241
|
-
rem
|
|
3242
|
-
};
|
|
3243
|
-
options?.onProgress?.("proof_generating");
|
|
3244
|
-
const proofResult = await generateWithdrawRegularProof(proofInputs, circuitsPath2);
|
|
3245
|
-
options?.onProgress?.("proof_complete");
|
|
3246
|
-
const proofHex = Buffer.from(proofResult.proofBytes).toString("hex");
|
|
3247
|
-
const finalPublicInputs = {
|
|
3248
|
-
root: merkleRoot,
|
|
3249
|
-
nf: nullifierHex,
|
|
3250
|
-
outputs_hash: outputsHashHex,
|
|
3251
|
-
amount: note.amount
|
|
3252
|
-
};
|
|
3253
|
-
const metadataBundle = options?.metadataBundle;
|
|
3254
|
-
const signature = await this.relay.submitWithdraw(
|
|
3255
|
-
{
|
|
3256
|
-
proof: proofHex,
|
|
3257
|
-
publicInputs: finalPublicInputs,
|
|
3258
|
-
outputs: recipients.map((r2) => ({
|
|
3259
|
-
recipient: r2.recipient.toBase58(),
|
|
3260
|
-
amount: r2.amount
|
|
3261
|
-
})),
|
|
3262
|
-
feeBps,
|
|
3263
|
-
// Use calculated protocol fee BPS
|
|
3264
|
-
metadataBundle
|
|
3265
|
-
},
|
|
3266
|
-
options?.onProgress
|
|
3267
|
-
);
|
|
3268
|
-
return {
|
|
3269
|
-
signature,
|
|
3270
|
-
outputs: recipients.map((r2) => ({
|
|
3271
|
-
recipient: r2.recipient.toBase58(),
|
|
3272
|
-
amount: r2.amount
|
|
3273
|
-
})),
|
|
3274
|
-
nullifier: nullifierHex,
|
|
3275
|
-
root: merkleRoot
|
|
3276
|
-
};
|
|
3277
|
-
} catch (error) {
|
|
3278
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
3279
|
-
if (error instanceof RootNotFoundError) {
|
|
3280
|
-
console.warn(
|
|
3281
|
-
`[Cloak SDK] Merkle root expired (attempt ${attempt + 1}/${MAX_PROOF_RETRIES}). Regenerating proof with fresh root...`
|
|
3282
|
-
);
|
|
3283
|
-
continue;
|
|
3284
|
-
}
|
|
3285
|
-
throw error;
|
|
3286
|
-
}
|
|
3287
|
-
}
|
|
3288
|
-
throw new Error(
|
|
3289
|
-
`Withdrawal failed after ${MAX_PROOF_RETRIES} proof refresh attempts. The Merkle tree is updating too rapidly. Last error: ${lastError?.message}`
|
|
3290
|
-
);
|
|
3291
|
-
}
|
|
3292
|
-
/**
|
|
3293
|
-
* Withdraw to a single recipient
|
|
3294
|
-
*
|
|
3295
|
-
* Convenience method for withdrawing to one address.
|
|
3296
|
-
* Handles the complete flow: deposits if needed, then withdraws.
|
|
3297
|
-
*
|
|
3298
|
-
* @param connection - Solana connection
|
|
3299
|
-
* @param payer - Payer wallet
|
|
3300
|
-
* @param note - Note to spend
|
|
3301
|
-
* @param recipient - Recipient address
|
|
3302
|
-
* @param options - Optional configuration
|
|
3303
|
-
* @returns Transfer result
|
|
3304
|
-
*
|
|
3305
|
-
* @example
|
|
3306
|
-
* ```typescript
|
|
3307
|
-
* const note = client.generateNote(1_000_000_000);
|
|
3308
|
-
* const result = await client.withdraw(
|
|
3309
|
-
* connection,
|
|
3310
|
-
* wallet,
|
|
3311
|
-
* note,
|
|
3312
|
-
* new PublicKey("..."),
|
|
3313
|
-
* { withdrawAll: true }
|
|
3314
|
-
* );
|
|
3315
|
-
* ```
|
|
3316
|
-
*/
|
|
3317
|
-
async withdraw(connection, note, recipient, options) {
|
|
3318
|
-
const withdrawAll = options?.withdrawAll ?? true;
|
|
3319
|
-
const amount = withdrawAll ? getDistributableAmount(note.amount) : options?.amount || note.amount;
|
|
3320
|
-
if (!withdrawAll && !options?.amount) {
|
|
3321
|
-
throw new Error("Must specify amount or set withdrawAll: true");
|
|
3322
|
-
}
|
|
3323
|
-
return this.privateTransfer(
|
|
3324
|
-
connection,
|
|
3325
|
-
note,
|
|
3326
|
-
[{ recipient, amount }],
|
|
3327
|
-
options
|
|
3328
|
-
);
|
|
3329
|
-
}
|
|
3330
|
-
/**
|
|
3331
|
-
* Send SOL privately to multiple recipients
|
|
3332
|
-
*
|
|
3333
|
-
* Convenience method that wraps privateTransfer with a simpler API.
|
|
3334
|
-
* Handles the complete flow: deposits if needed, then sends to recipients.
|
|
3335
|
-
*
|
|
3336
|
-
* @param connection - Solana connection
|
|
3337
|
-
* @param note - Note to spend
|
|
3338
|
-
* @param recipients - Array of 1-5 recipients with amounts
|
|
3339
|
-
* @param options - Optional configuration
|
|
3340
|
-
* @returns Transfer result
|
|
3341
|
-
*
|
|
3342
|
-
* @example
|
|
3343
|
-
* ```typescript
|
|
3344
|
-
* const note = client.generateNote(1_000_000_000);
|
|
3345
|
-
* const result = await client.send(
|
|
3346
|
-
* connection,
|
|
3347
|
-
* note,
|
|
3348
|
-
* [
|
|
3349
|
-
* { recipient: new PublicKey("..."), amount: 500_000_000 },
|
|
3350
|
-
* { recipient: new PublicKey("..."), amount: 492_500_000 }
|
|
3351
|
-
* ]
|
|
3352
|
-
* );
|
|
3353
|
-
* ```
|
|
3354
|
-
*/
|
|
3355
|
-
async send(connection, note, recipients, options) {
|
|
3356
|
-
return this.privateTransfer(connection, note, recipients, options);
|
|
3357
|
-
}
|
|
3358
|
-
/**
|
|
3359
|
-
* Swap SOL for tokens privately
|
|
3360
|
-
*
|
|
3361
|
-
* Withdraws SOL from a note and swaps it for tokens via the relay service.
|
|
3362
|
-
* Handles the complete flow: deposits if needed, generates proof, and submits swap.
|
|
3363
|
-
*
|
|
3364
|
-
* @param connection - Solana connection
|
|
3365
|
-
* @param note - Note to spend
|
|
3366
|
-
* @param recipient - Recipient address (will receive tokens)
|
|
3367
|
-
* @param options - Swap configuration
|
|
3368
|
-
* @returns Swap result with transaction signature
|
|
3369
|
-
*
|
|
3370
|
-
* @example
|
|
3371
|
-
* ```typescript
|
|
3372
|
-
* const note = client.generateNote(1_000_000_000);
|
|
3373
|
-
* const result = await client.swap(
|
|
3374
|
-
* connection,
|
|
3375
|
-
* note,
|
|
3376
|
-
* new PublicKey("..."), // recipient
|
|
3377
|
-
* {
|
|
3378
|
-
* outputMint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC
|
|
3379
|
-
* slippageBps: 100, // 1%
|
|
3380
|
-
* getQuote: async (amount, mint, slippage) => {
|
|
3381
|
-
* // Fetch quote from your swap API
|
|
3382
|
-
* const quote = await fetchSwapQuote(amount, mint, slippage);
|
|
3383
|
-
* return {
|
|
3384
|
-
* outAmount: quote.outAmount,
|
|
3385
|
-
* minOutputAmount: quote.minOutputAmount
|
|
3386
|
-
* };
|
|
3387
|
-
* }
|
|
3388
|
-
* }
|
|
3389
|
-
* );
|
|
3390
|
-
* ```
|
|
3391
|
-
*/
|
|
3392
|
-
async swap(connection, note, recipient, options) {
|
|
3393
|
-
try {
|
|
3394
|
-
if (!isWithdrawable(note)) {
|
|
3395
|
-
const depositResult = await this.deposit(connection, note, {
|
|
3396
|
-
skipPreflight: false
|
|
3397
|
-
});
|
|
3398
|
-
note = depositResult.note;
|
|
3399
|
-
}
|
|
3400
|
-
const variableFee = Math.floor(note.amount * 3 / 1e3);
|
|
3401
|
-
const feeBps = note.amount === 0 ? 0 : Math.min(Math.floor((variableFee * 1e4 + note.amount - 1) / note.amount), 65535);
|
|
3402
|
-
const withdrawAmountLamports = getDistributableAmount(note.amount);
|
|
3403
|
-
if (withdrawAmountLamports <= 0) {
|
|
3404
|
-
throw new Error("Amount too small after fees");
|
|
3405
|
-
}
|
|
3406
|
-
let minOutputAmount;
|
|
3407
|
-
if (options.minOutputAmount !== void 0) {
|
|
3408
|
-
minOutputAmount = options.minOutputAmount;
|
|
3409
|
-
} else if (options.getQuote) {
|
|
3410
|
-
const quote = await options.getQuote(
|
|
3411
|
-
withdrawAmountLamports,
|
|
3412
|
-
options.outputMint,
|
|
3413
|
-
options.slippageBps || 100
|
|
3414
|
-
);
|
|
3415
|
-
minOutputAmount = quote.minOutputAmount;
|
|
3416
|
-
} else {
|
|
3417
|
-
throw new Error(
|
|
3418
|
-
"Must provide either minOutputAmount or getQuote function"
|
|
3419
|
-
);
|
|
3420
|
-
}
|
|
3421
|
-
let recipientAta;
|
|
3422
|
-
if (options.recipientAta) {
|
|
3423
|
-
recipientAta = new PublicKey4(options.recipientAta);
|
|
3424
|
-
} else {
|
|
3425
|
-
try {
|
|
3426
|
-
const splTokenModule = await import("@solana/spl-token");
|
|
3427
|
-
const getAssociatedTokenAddress = splTokenModule.getAssociatedTokenAddress;
|
|
3428
|
-
if (!getAssociatedTokenAddress) {
|
|
3429
|
-
throw new Error("getAssociatedTokenAddress not found");
|
|
3430
|
-
}
|
|
3431
|
-
const outputMint2 = new PublicKey4(options.outputMint);
|
|
3432
|
-
recipientAta = await getAssociatedTokenAddress(outputMint2, recipient);
|
|
3433
|
-
} catch (error) {
|
|
3434
|
-
throw new Error(
|
|
3435
|
-
`Failed to get associated token account: ${error instanceof Error ? error.message : String(error)}. Please install @solana/spl-token or provide recipientAta in options.`
|
|
3436
|
-
);
|
|
3437
|
-
}
|
|
3438
|
-
}
|
|
3439
|
-
if (!note.leafIndex && note.leafIndex !== 0) {
|
|
3440
|
-
throw new Error("Note must have a leaf index (note must be deposited)");
|
|
3441
|
-
}
|
|
3442
|
-
const nullifier = await computeNullifierAsync(note.sk_spend, note.leafIndex);
|
|
3443
|
-
const nullifierHex = nullifier.toString(16).padStart(64, "0");
|
|
3444
|
-
const inputMint = new PublicKey4("11111111111111111111111111111111");
|
|
3445
|
-
const outputMint = new PublicKey4(options.outputMint);
|
|
3446
|
-
const outputsHash = await computeSwapOutputsHashAsync(
|
|
3447
|
-
inputMint,
|
|
3448
|
-
outputMint,
|
|
3449
|
-
recipientAta,
|
|
3450
|
-
minOutputAmount,
|
|
3451
|
-
note.amount
|
|
3452
|
-
);
|
|
3453
|
-
const outputsHashHex = outputsHash.toString(16).padStart(64, "0");
|
|
3454
|
-
const circuitsPath2 = await getDefaultCircuitsPath();
|
|
3455
|
-
const useDirectProof = await areCircuitsAvailable(circuitsPath2);
|
|
3456
|
-
if (!useDirectProof) {
|
|
3457
|
-
throw new Error(
|
|
3458
|
-
`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.`
|
|
3459
|
-
);
|
|
3460
|
-
}
|
|
3461
|
-
const MAX_PROOF_RETRIES = 3;
|
|
3462
|
-
let lastError = null;
|
|
3463
|
-
for (let attempt = 0; attempt < MAX_PROOF_RETRIES; attempt++) {
|
|
3464
|
-
try {
|
|
3465
|
-
if (attempt > 0) {
|
|
3466
|
-
options?.onProgress?.(`proof_refresh_${attempt + 1}`);
|
|
3467
|
-
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
3468
|
-
}
|
|
3469
|
-
const programId = this.config.programId || CLOAK_PROGRAM_ID;
|
|
3470
|
-
const mintForSOL = NATIVE_SOL_MINT;
|
|
3471
|
-
const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId, mintForSOL);
|
|
3472
|
-
const chainProof = await computeProofFromChain(connection, merkleTreePDA, note.leafIndex);
|
|
3473
|
-
const merkleProof = {
|
|
3474
|
-
pathElements: chainProof.pathElements,
|
|
3475
|
-
pathIndices: chainProof.pathIndices
|
|
3476
|
-
};
|
|
3477
|
-
const merkleRoot = chainProof.root;
|
|
3478
|
-
if (!merkleRoot) {
|
|
3479
|
-
throw new Error("Failed to get Merkle root from chain");
|
|
3480
|
-
}
|
|
3481
|
-
if (!merkleProof.pathElements || merkleProof.pathElements.length === 0) {
|
|
3482
|
-
throw new Error("Merkle proof is invalid: missing path elements");
|
|
3483
|
-
}
|
|
3484
|
-
const sk_spend_bigint = BigInt("0x" + note.sk_spend);
|
|
3485
|
-
const r_bigint = BigInt("0x" + note.r);
|
|
3486
|
-
const root_bigint = BigInt("0x" + merkleRoot);
|
|
3487
|
-
const nullifier_bigint = BigInt("0x" + nullifierHex);
|
|
3488
|
-
const outputs_hash_bigint = BigInt("0x" + outputsHashHex);
|
|
3489
|
-
const amount_bigint = BigInt(note.amount);
|
|
3490
|
-
const pathElements = merkleProof.pathElements.map((p) => BigInt("0x" + p));
|
|
3491
|
-
const inputMintLimbs = pubkeyToLimbs(inputMint.toBytes());
|
|
3492
|
-
const outputMintLimbs = pubkeyToLimbs(outputMint.toBytes());
|
|
3493
|
-
const recipientAtaLimbs = pubkeyToLimbs(recipientAta.toBytes());
|
|
3494
|
-
if (attempt === 0) {
|
|
3495
|
-
const computedCommitment = await computeCommitment2(amount_bigint, r_bigint, sk_spend_bigint);
|
|
3496
|
-
const noteCommitment = BigInt("0x" + note.commitment);
|
|
3497
|
-
if (computedCommitment !== noteCommitment) {
|
|
3498
|
-
throw new Error(
|
|
3499
|
-
`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.`
|
|
3500
|
-
);
|
|
3501
|
-
}
|
|
3502
|
-
}
|
|
3503
|
-
const t = amount_bigint * 3n;
|
|
3504
|
-
const varFee = t / 1000n;
|
|
3505
|
-
const rem = t % 1000n;
|
|
3506
|
-
const proofInputs = {
|
|
3507
|
-
sk_spend: sk_spend_bigint,
|
|
3508
|
-
r: r_bigint,
|
|
3509
|
-
amount: amount_bigint,
|
|
3510
|
-
leaf_index: BigInt(note.leafIndex),
|
|
3511
|
-
path_elements: pathElements,
|
|
3512
|
-
path_indices: merkleProof.pathIndices,
|
|
3513
|
-
root: root_bigint,
|
|
3514
|
-
nullifier: nullifier_bigint,
|
|
3515
|
-
outputs_hash: outputs_hash_bigint,
|
|
3516
|
-
public_amount: amount_bigint,
|
|
3517
|
-
input_mint: inputMintLimbs,
|
|
3518
|
-
output_mint: outputMintLimbs,
|
|
3519
|
-
recipient_ata: recipientAtaLimbs,
|
|
3520
|
-
min_output_amount: BigInt(minOutputAmount),
|
|
3521
|
-
var_fee: varFee,
|
|
3522
|
-
rem
|
|
3523
|
-
};
|
|
3524
|
-
options?.onProgress?.("proof_generating");
|
|
3525
|
-
const proofResult = await generateWithdrawSwapProof(proofInputs, circuitsPath2);
|
|
3526
|
-
options?.onProgress?.("proof_complete");
|
|
3527
|
-
const proofHex = Buffer.from(proofResult.proofBytes).toString("hex");
|
|
3528
|
-
const finalPublicInputs = {
|
|
3529
|
-
root: merkleRoot,
|
|
3530
|
-
nf: nullifierHex,
|
|
3531
|
-
outputs_hash: outputsHashHex,
|
|
3532
|
-
amount: note.amount
|
|
3533
|
-
};
|
|
3534
|
-
const metadataBundle = options?.metadataBundle;
|
|
3535
|
-
const signature = await this.relay.submitSwap(
|
|
3536
|
-
{
|
|
3537
|
-
proof: proofHex,
|
|
3538
|
-
publicInputs: finalPublicInputs,
|
|
3539
|
-
outputs: [
|
|
3540
|
-
{
|
|
3541
|
-
recipient: recipient.toBase58(),
|
|
3542
|
-
amount: withdrawAmountLamports
|
|
3543
|
-
}
|
|
3544
|
-
],
|
|
3545
|
-
feeBps,
|
|
3546
|
-
swap: {
|
|
3547
|
-
output_mint: options.outputMint,
|
|
3548
|
-
slippage_bps: options.slippageBps || 100,
|
|
3549
|
-
min_output_amount: minOutputAmount
|
|
3550
|
-
},
|
|
3551
|
-
metadataBundle
|
|
3552
|
-
},
|
|
3553
|
-
options.onProgress
|
|
3554
|
-
);
|
|
3555
|
-
return {
|
|
3556
|
-
signature,
|
|
3557
|
-
outputs: [
|
|
3558
|
-
{
|
|
3559
|
-
recipient: recipient.toBase58(),
|
|
3560
|
-
amount: withdrawAmountLamports
|
|
3561
|
-
}
|
|
3562
|
-
],
|
|
3563
|
-
nullifier: nullifierHex,
|
|
3564
|
-
root: merkleRoot,
|
|
3565
|
-
outputMint: options.outputMint,
|
|
3566
|
-
minOutputAmount
|
|
3567
|
-
};
|
|
3568
|
-
} catch (error) {
|
|
3569
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
3570
|
-
if (error instanceof RootNotFoundError) {
|
|
3571
|
-
console.warn(
|
|
3572
|
-
`[Cloak SDK] Merkle root expired (attempt ${attempt + 1}/${MAX_PROOF_RETRIES}). Regenerating swap proof with fresh root...`
|
|
3573
|
-
);
|
|
3574
|
-
continue;
|
|
3575
|
-
}
|
|
3576
|
-
throw error;
|
|
3577
|
-
}
|
|
3578
|
-
}
|
|
3579
|
-
throw new Error(
|
|
3580
|
-
`Swap failed after ${MAX_PROOF_RETRIES} proof refresh attempts. The Merkle tree is updating too rapidly. Last error: ${lastError?.message}`
|
|
3581
|
-
);
|
|
3582
|
-
} catch (error) {
|
|
3583
|
-
throw this.wrapError(error, "Swap failed");
|
|
3584
|
-
}
|
|
3585
|
-
}
|
|
3586
|
-
/**
|
|
3587
|
-
* Generate a new note without depositing
|
|
3588
|
-
*
|
|
3589
|
-
* @param amountLamports - Amount for the note
|
|
3590
|
-
* @param useWalletKeys - Whether to use wallet keys (v2.0 recommended)
|
|
3591
|
-
* @returns New note (not yet deposited)
|
|
3592
|
-
*/
|
|
3593
|
-
async generateNote(amountLamports, useWalletKeys = false) {
|
|
3594
|
-
if (useWalletKeys && this.cloakKeys) {
|
|
3595
|
-
return await generateNoteFromWallet(amountLamports, this.cloakKeys, this.config.network);
|
|
3596
|
-
} else if (useWalletKeys) {
|
|
3597
|
-
const keys = generateCloakKeys();
|
|
3598
|
-
this.cloakKeys = keys;
|
|
3599
|
-
const result = this.storage.saveKeys(keys);
|
|
3600
|
-
if (result instanceof Promise) {
|
|
3601
|
-
result.catch(() => {
|
|
3602
|
-
});
|
|
3603
|
-
}
|
|
3604
|
-
return await generateNoteFromWallet(amountLamports, keys, this.config.network);
|
|
2030
|
+
/** Public key of the configured signer (keypair or wallet). */
|
|
2031
|
+
getPublicKey() {
|
|
2032
|
+
if (this.keypair) {
|
|
2033
|
+
return this.keypair.publicKey;
|
|
3605
2034
|
}
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
parseNote(jsonString) {
|
|
3615
|
-
return parseNote2(jsonString);
|
|
3616
|
-
}
|
|
3617
|
-
/**
|
|
3618
|
-
* Export a note to JSON string
|
|
3619
|
-
*
|
|
3620
|
-
* @param note - Note to export
|
|
3621
|
-
* @param pretty - Format with indentation
|
|
3622
|
-
* @returns JSON string
|
|
3623
|
-
*/
|
|
3624
|
-
exportNote(note, pretty = false) {
|
|
3625
|
-
return pretty ? JSON.stringify(note, null, 2) : JSON.stringify(note);
|
|
2035
|
+
if (this.wallet?.publicKey) {
|
|
2036
|
+
return this.wallet.publicKey;
|
|
2037
|
+
}
|
|
2038
|
+
throw new CloakError(
|
|
2039
|
+
"No public key available - wallet not connected and no keypair provided",
|
|
2040
|
+
"wallet",
|
|
2041
|
+
false
|
|
2042
|
+
);
|
|
3626
2043
|
}
|
|
3627
|
-
/**
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
* @param note - Note to check
|
|
3631
|
-
* @returns True if withdrawable
|
|
3632
|
-
*/
|
|
3633
|
-
isWithdrawable(note) {
|
|
3634
|
-
return isWithdrawable(note);
|
|
2044
|
+
/** True when the SDK was constructed with a wallet adapter (browser). */
|
|
2045
|
+
isWalletMode() {
|
|
2046
|
+
return !!this.wallet && !this.keypair;
|
|
3635
2047
|
}
|
|
3636
2048
|
/**
|
|
3637
|
-
*
|
|
3638
|
-
*
|
|
3639
|
-
* @param connection - Solana connection
|
|
3640
|
-
* @param leafIndex - Leaf index in tree
|
|
3641
|
-
* @returns Merkle proof computed from on-chain data
|
|
2049
|
+
* Compute a Merkle proof for `leafIndex` from on-chain state. No relay /
|
|
2050
|
+
* indexer round-trip required.
|
|
3642
2051
|
*/
|
|
3643
2052
|
async getMerkleProof(connection, leafIndex) {
|
|
3644
2053
|
const programId = this.config.programId || CLOAK_PROGRAM_ID;
|
|
3645
|
-
const
|
|
3646
|
-
const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId, mintForSOL);
|
|
2054
|
+
const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId, NATIVE_SOL_MINT);
|
|
3647
2055
|
const chainProof = await computeProofFromChain(connection, merkleTreePDA, leafIndex);
|
|
3648
2056
|
return {
|
|
3649
2057
|
pathElements: chainProof.pathElements,
|
|
@@ -3651,72 +2059,20 @@ var CloakSDK = class {
|
|
|
3651
2059
|
root: chainProof.root
|
|
3652
2060
|
};
|
|
3653
2061
|
}
|
|
3654
|
-
/**
|
|
3655
|
-
* Get current Merkle root directly from on-chain state
|
|
3656
|
-
*
|
|
3657
|
-
* @param connection - Solana connection
|
|
3658
|
-
* @returns Current root hash from on-chain tree
|
|
3659
|
-
*/
|
|
2062
|
+
/** Read the current SOL-pool Merkle root from on-chain state. */
|
|
3660
2063
|
async getCurrentRoot(connection) {
|
|
3661
2064
|
const programId = this.config.programId || CLOAK_PROGRAM_ID;
|
|
3662
|
-
const
|
|
3663
|
-
const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId, mintForSOL);
|
|
2065
|
+
const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId, NATIVE_SOL_MINT);
|
|
3664
2066
|
const state = await readMerkleTreeState(connection, merkleTreePDA);
|
|
3665
2067
|
return state.root;
|
|
3666
2068
|
}
|
|
3667
|
-
/**
|
|
3668
|
-
* Get transaction status from relay service
|
|
3669
|
-
*
|
|
3670
|
-
* @param requestId - Request ID from previous submission
|
|
3671
|
-
* @returns Current status
|
|
3672
|
-
*/
|
|
2069
|
+
/** Poll relay for the status of a previously-submitted request. */
|
|
3673
2070
|
async getTransactionStatus(requestId) {
|
|
3674
2071
|
return this.relay.getStatus(requestId);
|
|
3675
2072
|
}
|
|
3676
|
-
/**
|
|
3677
|
-
* Fetch and decrypt this user's transaction metadata history.
|
|
3678
|
-
* DEPRECATED: This functionality is no longer supported
|
|
3679
|
-
*/
|
|
3680
|
-
async getTransactionMetadata(_options) {
|
|
3681
|
-
throw new Error("getTransactionMetadata is deprecated - use compliance export API instead");
|
|
3682
|
-
}
|
|
3683
|
-
/**
|
|
3684
|
-
* Load all notes from storage
|
|
3685
|
-
*
|
|
3686
|
-
* @returns Array of saved notes
|
|
3687
|
-
*/
|
|
3688
|
-
async loadNotes() {
|
|
3689
|
-
const notes = this.storage.loadAllNotes();
|
|
3690
|
-
return Array.isArray(notes) ? notes : await notes;
|
|
3691
|
-
}
|
|
3692
|
-
/**
|
|
3693
|
-
* Save a note to storage
|
|
3694
|
-
*
|
|
3695
|
-
* @param note - Note to save
|
|
3696
|
-
*/
|
|
3697
|
-
async saveNote(note) {
|
|
3698
|
-
const result = this.storage.saveNote(note);
|
|
3699
|
-
if (result instanceof Promise) {
|
|
3700
|
-
await result;
|
|
3701
|
-
}
|
|
3702
|
-
}
|
|
3703
|
-
/**
|
|
3704
|
-
* Find a note by its commitment
|
|
3705
|
-
*
|
|
3706
|
-
* @param commitment - Commitment hash
|
|
3707
|
-
* @returns Note if found
|
|
3708
|
-
*/
|
|
3709
|
-
async findNote(commitment) {
|
|
3710
|
-
const notes = await this.loadNotes();
|
|
3711
|
-
return findNoteByCommitment(notes, commitment);
|
|
3712
|
-
}
|
|
3713
|
-
/**
|
|
3714
|
-
* Import wallet keys from JSON
|
|
3715
|
-
*
|
|
3716
|
-
* @param keysJson - JSON string containing keys
|
|
3717
|
-
*/
|
|
2073
|
+
/** Import wallet keys from JSON; persists to the configured storage adapter. */
|
|
3718
2074
|
async importWalletKeys(keysJson) {
|
|
3719
|
-
const keys =
|
|
2075
|
+
const keys = importKeys(keysJson);
|
|
3720
2076
|
this.cloakKeys = keys;
|
|
3721
2077
|
const result = this.storage.saveKeys(keys);
|
|
3722
2078
|
if (result instanceof Promise) {
|
|
@@ -3724,134 +2080,32 @@ var CloakSDK = class {
|
|
|
3724
2080
|
}
|
|
3725
2081
|
}
|
|
3726
2082
|
/**
|
|
3727
|
-
* Export wallet keys to JSON
|
|
3728
|
-
*
|
|
3729
|
-
* WARNING:
|
|
3730
|
-
*
|
|
3731
|
-
* @returns JSON string with keys
|
|
2083
|
+
* Export wallet keys to JSON.
|
|
2084
|
+
*
|
|
2085
|
+
* WARNING: this exports secret keys. Store securely.
|
|
3732
2086
|
*/
|
|
3733
2087
|
exportWalletKeys() {
|
|
3734
2088
|
if (!this.cloakKeys) {
|
|
3735
2089
|
throw new CloakError("No wallet keys available", "wallet", false);
|
|
3736
2090
|
}
|
|
3737
|
-
return
|
|
2091
|
+
return exportKeys(this.cloakKeys);
|
|
3738
2092
|
}
|
|
3739
2093
|
/**
|
|
3740
|
-
*
|
|
2094
|
+
* Snapshot of the active SDK configuration.
|
|
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
2103
|
*/
|
|
3742
2104
|
getConfig() {
|
|
3743
2105
|
return { ...this.config };
|
|
3744
2106
|
}
|
|
3745
|
-
// Note: scanNotes was removed - it required the indexer to store encrypted outputs.
|
|
3746
|
-
// Users should maintain their own note storage. This is more privacy-preserving.
|
|
3747
|
-
/**
|
|
3748
|
-
* Wrap errors with better categorization and user-friendly messages
|
|
3749
|
-
*
|
|
3750
|
-
* @private
|
|
3751
|
-
*/
|
|
3752
|
-
wrapError(error, context) {
|
|
3753
|
-
if (error instanceof CloakError) {
|
|
3754
|
-
return error;
|
|
3755
|
-
}
|
|
3756
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3757
|
-
if (errorMessage.includes("duplicate key") || errorMessage.includes("already deposited")) {
|
|
3758
|
-
return new CloakError(
|
|
3759
|
-
"This note was already deposited. Generate a new note to continue.",
|
|
3760
|
-
"validation",
|
|
3761
|
-
false,
|
|
3762
|
-
error instanceof Error ? error : void 0
|
|
3763
|
-
);
|
|
3764
|
-
}
|
|
3765
|
-
if (errorMessage.includes("insufficient funds") || errorMessage.includes("insufficient lamports")) {
|
|
3766
|
-
return new CloakError(
|
|
3767
|
-
"Insufficient funds for this transaction. Please check your wallet balance.",
|
|
3768
|
-
"wallet",
|
|
3769
|
-
false,
|
|
3770
|
-
error instanceof Error ? error : void 0
|
|
3771
|
-
);
|
|
3772
|
-
}
|
|
3773
|
-
if (errorMessage.includes("Merkle tree") || errorMessage.includes("merkle")) {
|
|
3774
|
-
return new CloakError(
|
|
3775
|
-
"Failed to read on-chain Merkle tree state. Please try again.",
|
|
3776
|
-
"network",
|
|
3777
|
-
true,
|
|
3778
|
-
error instanceof Error ? error : void 0
|
|
3779
|
-
);
|
|
3780
|
-
}
|
|
3781
|
-
if (errorMessage.includes("timeout") || errorMessage.includes("timed out")) {
|
|
3782
|
-
return new CloakError(
|
|
3783
|
-
"Network timeout. Please check your connection and try again.",
|
|
3784
|
-
"network",
|
|
3785
|
-
true,
|
|
3786
|
-
error instanceof Error ? error : void 0
|
|
3787
|
-
);
|
|
3788
|
-
}
|
|
3789
|
-
if (errorMessage.includes("not connected") || errorMessage.includes("wallet")) {
|
|
3790
|
-
return new CloakError(
|
|
3791
|
-
"Wallet not connected. Please connect your wallet first.",
|
|
3792
|
-
"wallet",
|
|
3793
|
-
false,
|
|
3794
|
-
error instanceof Error ? error : void 0
|
|
3795
|
-
);
|
|
3796
|
-
}
|
|
3797
|
-
if (errorMessage.includes("proof") && (errorMessage.includes("failed") || errorMessage.includes("error"))) {
|
|
3798
|
-
return new CloakError(
|
|
3799
|
-
"Zero-knowledge proof generation failed. This is usually temporary - please try again.",
|
|
3800
|
-
"prover",
|
|
3801
|
-
true,
|
|
3802
|
-
error instanceof Error ? error : void 0
|
|
3803
|
-
);
|
|
3804
|
-
}
|
|
3805
|
-
if (errorMessage.includes("relay") || errorMessage.includes("withdraw")) {
|
|
3806
|
-
return new CloakError(
|
|
3807
|
-
"Relay service error. Please try again later.",
|
|
3808
|
-
"relay",
|
|
3809
|
-
true,
|
|
3810
|
-
error instanceof Error ? error : void 0
|
|
3811
|
-
);
|
|
3812
|
-
}
|
|
3813
|
-
return new CloakError(
|
|
3814
|
-
`${context}: ${errorMessage}`,
|
|
3815
|
-
"network",
|
|
3816
|
-
false,
|
|
3817
|
-
error instanceof Error ? error : void 0
|
|
3818
|
-
);
|
|
3819
|
-
}
|
|
3820
2107
|
};
|
|
3821
2108
|
|
|
3822
|
-
// src/core/note.ts
|
|
3823
|
-
function serializeNote(note, pretty = false) {
|
|
3824
|
-
validateNote(note);
|
|
3825
|
-
return pretty ? JSON.stringify(note, null, 2) : JSON.stringify(note);
|
|
3826
|
-
}
|
|
3827
|
-
function downloadNote(note, filename) {
|
|
3828
|
-
const g = globalThis;
|
|
3829
|
-
const doc = g?.document;
|
|
3830
|
-
const URL_ = g?.URL;
|
|
3831
|
-
const Blob_ = g?.Blob;
|
|
3832
|
-
if (!doc || !URL_ || !Blob_) {
|
|
3833
|
-
throw new Error("downloadNote is only available in browser environments");
|
|
3834
|
-
}
|
|
3835
|
-
const json = serializeNote(note, true);
|
|
3836
|
-
const blob = new Blob_([json], { type: "application/json" });
|
|
3837
|
-
const url = URL_.createObjectURL(blob);
|
|
3838
|
-
const defaultFilename = `cloak-note-${note.commitment.slice(0, 8)}.json`;
|
|
3839
|
-
const link = doc.createElement("a");
|
|
3840
|
-
link.href = url;
|
|
3841
|
-
link.download = filename || defaultFilename;
|
|
3842
|
-
link.click();
|
|
3843
|
-
URL_.revokeObjectURL(url);
|
|
3844
|
-
}
|
|
3845
|
-
async function copyNoteToClipboard(note) {
|
|
3846
|
-
const g = globalThis;
|
|
3847
|
-
const nav = g?.navigator;
|
|
3848
|
-
if (!nav || !nav.clipboard) {
|
|
3849
|
-
throw new Error("Clipboard API not available");
|
|
3850
|
-
}
|
|
3851
|
-
const json = serializeNote(note, true);
|
|
3852
|
-
await nav.clipboard.writeText(json);
|
|
3853
|
-
}
|
|
3854
|
-
|
|
3855
2109
|
// src/core/compliance-keys.ts
|
|
3856
2110
|
import nacl2 from "tweetnacl";
|
|
3857
2111
|
import { blake3 as blake32 } from "@noble/hashes/blake3";
|
|
@@ -4238,6 +2492,107 @@ async function decryptComplianceMetadataWithMasterKey(encrypted, masterComplianc
|
|
|
4238
2492
|
return decryptWithSharedSecret(payload, sharedSecret);
|
|
4239
2493
|
}
|
|
4240
2494
|
|
|
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
|
+
|
|
4241
2596
|
// src/utils/verify-utxos.ts
|
|
4242
2597
|
async function verifyUtxos(utxos, connection, programId, commitment = "confirmed") {
|
|
4243
2598
|
const checkable = [];
|
|
@@ -4366,45 +2721,6 @@ async function fetchRiskQuoteIx(connection, wallet, rangeApiKey) {
|
|
|
4366
2721
|
};
|
|
4367
2722
|
}
|
|
4368
2723
|
|
|
4369
|
-
// src/helpers/encrypted-output.ts
|
|
4370
|
-
function prepareEncryptedOutput(note, cloakKeys) {
|
|
4371
|
-
const noteData = {
|
|
4372
|
-
amount: note.amount,
|
|
4373
|
-
r: note.r,
|
|
4374
|
-
sk_spend: note.sk_spend,
|
|
4375
|
-
commitment: note.commitment
|
|
4376
|
-
};
|
|
4377
|
-
const encrypted = encryptNoteForRecipient(noteData, cloakKeys.view.pvk);
|
|
4378
|
-
return btoa(JSON.stringify(encrypted));
|
|
4379
|
-
}
|
|
4380
|
-
function prepareEncryptedOutputForRecipient(note, recipientPvkHex) {
|
|
4381
|
-
const noteData = {
|
|
4382
|
-
amount: note.amount,
|
|
4383
|
-
r: note.r,
|
|
4384
|
-
sk_spend: note.sk_spend,
|
|
4385
|
-
commitment: note.commitment
|
|
4386
|
-
};
|
|
4387
|
-
const recipientPvk = hexToBytes(recipientPvkHex);
|
|
4388
|
-
const encrypted = encryptNoteForRecipient(noteData, recipientPvk);
|
|
4389
|
-
return btoa(JSON.stringify(encrypted));
|
|
4390
|
-
}
|
|
4391
|
-
function encodeNoteSimple(note) {
|
|
4392
|
-
const data = {
|
|
4393
|
-
amount: note.amount,
|
|
4394
|
-
r: note.r,
|
|
4395
|
-
sk_spend: note.sk_spend,
|
|
4396
|
-
commitment: note.commitment
|
|
4397
|
-
};
|
|
4398
|
-
const json = JSON.stringify(data);
|
|
4399
|
-
if (typeof Buffer !== "undefined") {
|
|
4400
|
-
return Buffer.from(json).toString("base64");
|
|
4401
|
-
} else if (typeof btoa !== "undefined") {
|
|
4402
|
-
return btoa(json);
|
|
4403
|
-
} else {
|
|
4404
|
-
throw new Error("No base64 encoding method available");
|
|
4405
|
-
}
|
|
4406
|
-
}
|
|
4407
|
-
|
|
4408
2724
|
// src/helpers/wallet-integration.ts
|
|
4409
2725
|
import {
|
|
4410
2726
|
Keypair as Keypair2
|
|
@@ -4916,181 +3232,14 @@ async function preflightCheck(relayUrl, rootHex) {
|
|
|
4916
3232
|
};
|
|
4917
3233
|
}
|
|
4918
3234
|
|
|
4919
|
-
// src/utils/pending-operations.ts
|
|
4920
|
-
var PENDING_DEPOSITS_KEY = "cloak_pending_deposits";
|
|
4921
|
-
var PENDING_WITHDRAWALS_KEY = "cloak_pending_withdrawals";
|
|
4922
|
-
function getStorage() {
|
|
4923
|
-
if (typeof globalThis !== "undefined" && globalThis.localStorage) {
|
|
4924
|
-
return globalThis.localStorage;
|
|
4925
|
-
}
|
|
4926
|
-
return null;
|
|
4927
|
-
}
|
|
4928
|
-
function savePendingDeposit(deposit) {
|
|
4929
|
-
const storage = getStorage();
|
|
4930
|
-
if (!storage) {
|
|
4931
|
-
console.warn("localStorage not available - pending deposit not persisted");
|
|
4932
|
-
return;
|
|
4933
|
-
}
|
|
4934
|
-
const deposits = loadPendingDeposits();
|
|
4935
|
-
const index = deposits.findIndex((d) => d.note.commitment === deposit.note.commitment);
|
|
4936
|
-
if (index >= 0) {
|
|
4937
|
-
deposits[index] = deposit;
|
|
4938
|
-
} else {
|
|
4939
|
-
deposits.push(deposit);
|
|
4940
|
-
}
|
|
4941
|
-
storage.setItem(PENDING_DEPOSITS_KEY, JSON.stringify(deposits));
|
|
4942
|
-
}
|
|
4943
|
-
function loadPendingDeposits() {
|
|
4944
|
-
const storage = getStorage();
|
|
4945
|
-
if (!storage) return [];
|
|
4946
|
-
const stored = storage.getItem(PENDING_DEPOSITS_KEY);
|
|
4947
|
-
if (!stored) return [];
|
|
4948
|
-
try {
|
|
4949
|
-
return JSON.parse(stored);
|
|
4950
|
-
} catch {
|
|
4951
|
-
return [];
|
|
4952
|
-
}
|
|
4953
|
-
}
|
|
4954
|
-
function updatePendingDeposit(commitment, updates) {
|
|
4955
|
-
const storage = getStorage();
|
|
4956
|
-
if (!storage) return;
|
|
4957
|
-
const deposits = loadPendingDeposits();
|
|
4958
|
-
const index = deposits.findIndex((d) => d.note.commitment === commitment);
|
|
4959
|
-
if (index >= 0) {
|
|
4960
|
-
deposits[index] = { ...deposits[index], ...updates };
|
|
4961
|
-
storage.setItem(PENDING_DEPOSITS_KEY, JSON.stringify(deposits));
|
|
4962
|
-
}
|
|
4963
|
-
}
|
|
4964
|
-
function removePendingDeposit(commitment) {
|
|
4965
|
-
const storage = getStorage();
|
|
4966
|
-
if (!storage) return;
|
|
4967
|
-
const deposits = loadPendingDeposits();
|
|
4968
|
-
const filtered = deposits.filter((d) => d.note.commitment !== commitment);
|
|
4969
|
-
storage.setItem(PENDING_DEPOSITS_KEY, JSON.stringify(filtered));
|
|
4970
|
-
}
|
|
4971
|
-
function clearPendingDeposits() {
|
|
4972
|
-
const storage = getStorage();
|
|
4973
|
-
if (storage) {
|
|
4974
|
-
storage.removeItem(PENDING_DEPOSITS_KEY);
|
|
4975
|
-
}
|
|
4976
|
-
}
|
|
4977
|
-
function savePendingWithdrawal(withdrawal) {
|
|
4978
|
-
const storage = getStorage();
|
|
4979
|
-
if (!storage) {
|
|
4980
|
-
console.warn("localStorage not available - pending withdrawal not persisted");
|
|
4981
|
-
return;
|
|
4982
|
-
}
|
|
4983
|
-
const withdrawals = loadPendingWithdrawals();
|
|
4984
|
-
const index = withdrawals.findIndex((w) => w.requestId === withdrawal.requestId);
|
|
4985
|
-
if (index >= 0) {
|
|
4986
|
-
withdrawals[index] = withdrawal;
|
|
4987
|
-
} else {
|
|
4988
|
-
withdrawals.push(withdrawal);
|
|
4989
|
-
}
|
|
4990
|
-
storage.setItem(PENDING_WITHDRAWALS_KEY, JSON.stringify(withdrawals));
|
|
4991
|
-
}
|
|
4992
|
-
function loadPendingWithdrawals() {
|
|
4993
|
-
const storage = getStorage();
|
|
4994
|
-
if (!storage) return [];
|
|
4995
|
-
const stored = storage.getItem(PENDING_WITHDRAWALS_KEY);
|
|
4996
|
-
if (!stored) return [];
|
|
4997
|
-
try {
|
|
4998
|
-
return JSON.parse(stored);
|
|
4999
|
-
} catch {
|
|
5000
|
-
return [];
|
|
5001
|
-
}
|
|
5002
|
-
}
|
|
5003
|
-
function updatePendingWithdrawal(requestId, updates) {
|
|
5004
|
-
const storage = getStorage();
|
|
5005
|
-
if (!storage) return;
|
|
5006
|
-
const withdrawals = loadPendingWithdrawals();
|
|
5007
|
-
const index = withdrawals.findIndex((w) => w.requestId === requestId);
|
|
5008
|
-
if (index >= 0) {
|
|
5009
|
-
withdrawals[index] = { ...withdrawals[index], ...updates };
|
|
5010
|
-
storage.setItem(PENDING_WITHDRAWALS_KEY, JSON.stringify(withdrawals));
|
|
5011
|
-
}
|
|
5012
|
-
}
|
|
5013
|
-
function removePendingWithdrawal(requestId) {
|
|
5014
|
-
const storage = getStorage();
|
|
5015
|
-
if (!storage) return;
|
|
5016
|
-
const withdrawals = loadPendingWithdrawals();
|
|
5017
|
-
const filtered = withdrawals.filter((w) => w.requestId !== requestId);
|
|
5018
|
-
storage.setItem(PENDING_WITHDRAWALS_KEY, JSON.stringify(filtered));
|
|
5019
|
-
}
|
|
5020
|
-
function clearPendingWithdrawals() {
|
|
5021
|
-
const storage = getStorage();
|
|
5022
|
-
if (storage) {
|
|
5023
|
-
storage.removeItem(PENDING_WITHDRAWALS_KEY);
|
|
5024
|
-
}
|
|
5025
|
-
}
|
|
5026
|
-
function hasPendingOperations() {
|
|
5027
|
-
const deposits = loadPendingDeposits();
|
|
5028
|
-
const withdrawals = loadPendingWithdrawals();
|
|
5029
|
-
const pendingDeposits = deposits.filter(
|
|
5030
|
-
(d) => d.status === "pending" || d.status === "tx_sent"
|
|
5031
|
-
);
|
|
5032
|
-
const pendingWithdrawals = withdrawals.filter(
|
|
5033
|
-
(w) => w.status === "pending" || w.status === "processing"
|
|
5034
|
-
);
|
|
5035
|
-
return pendingDeposits.length > 0 || pendingWithdrawals.length > 0;
|
|
5036
|
-
}
|
|
5037
|
-
function getPendingOperationsSummary() {
|
|
5038
|
-
const deposits = loadPendingDeposits().filter(
|
|
5039
|
-
(d) => d.status === "pending" || d.status === "tx_sent"
|
|
5040
|
-
);
|
|
5041
|
-
const withdrawals = loadPendingWithdrawals().filter(
|
|
5042
|
-
(w) => w.status === "pending" || w.status === "processing"
|
|
5043
|
-
);
|
|
5044
|
-
return {
|
|
5045
|
-
deposits,
|
|
5046
|
-
withdrawals,
|
|
5047
|
-
totalPending: deposits.length + withdrawals.length
|
|
5048
|
-
};
|
|
5049
|
-
}
|
|
5050
|
-
function cleanupStalePendingOperations(maxAgeMs = 24 * 60 * 60 * 1e3) {
|
|
5051
|
-
const now = Date.now();
|
|
5052
|
-
let removedDeposits = 0;
|
|
5053
|
-
let removedWithdrawals = 0;
|
|
5054
|
-
const deposits = loadPendingDeposits();
|
|
5055
|
-
const activeDeposits = deposits.filter((d) => {
|
|
5056
|
-
const age = now - d.startedAt;
|
|
5057
|
-
const isStale = age > maxAgeMs;
|
|
5058
|
-
const isTerminal = d.status === "confirmed" || d.status === "failed";
|
|
5059
|
-
if (isStale || isTerminal) {
|
|
5060
|
-
removedDeposits++;
|
|
5061
|
-
return false;
|
|
5062
|
-
}
|
|
5063
|
-
return true;
|
|
5064
|
-
});
|
|
5065
|
-
const storage = getStorage();
|
|
5066
|
-
if (storage) {
|
|
5067
|
-
storage.setItem(PENDING_DEPOSITS_KEY, JSON.stringify(activeDeposits));
|
|
5068
|
-
}
|
|
5069
|
-
const withdrawals = loadPendingWithdrawals();
|
|
5070
|
-
const activeWithdrawals = withdrawals.filter((w) => {
|
|
5071
|
-
const age = now - w.startedAt;
|
|
5072
|
-
const isStale = age > maxAgeMs;
|
|
5073
|
-
const isTerminal = w.status === "completed" || w.status === "failed";
|
|
5074
|
-
if (isStale || isTerminal) {
|
|
5075
|
-
removedWithdrawals++;
|
|
5076
|
-
return false;
|
|
5077
|
-
}
|
|
5078
|
-
return true;
|
|
5079
|
-
});
|
|
5080
|
-
if (storage) {
|
|
5081
|
-
storage.setItem(PENDING_WITHDRAWALS_KEY, JSON.stringify(activeWithdrawals));
|
|
5082
|
-
}
|
|
5083
|
-
return { removedDeposits, removedWithdrawals };
|
|
5084
|
-
}
|
|
5085
|
-
|
|
5086
3235
|
// src/core/transact.ts
|
|
5087
3236
|
import {
|
|
5088
|
-
PublicKey as
|
|
5089
|
-
Transaction as
|
|
5090
|
-
TransactionInstruction
|
|
3237
|
+
PublicKey as PublicKey5,
|
|
3238
|
+
Transaction as Transaction2,
|
|
3239
|
+
TransactionInstruction,
|
|
5091
3240
|
sendAndConfirmTransaction,
|
|
5092
|
-
SystemProgram
|
|
5093
|
-
ComputeBudgetProgram
|
|
3241
|
+
SystemProgram,
|
|
3242
|
+
ComputeBudgetProgram,
|
|
5094
3243
|
SYSVAR_INSTRUCTIONS_PUBKEY,
|
|
5095
3244
|
SYSVAR_SLOT_HASHES_PUBKEY,
|
|
5096
3245
|
VersionedTransaction,
|
|
@@ -5103,7 +3252,7 @@ import {
|
|
|
5103
3252
|
getAssociatedTokenAddressSync,
|
|
5104
3253
|
TOKEN_PROGRAM_ID
|
|
5105
3254
|
} from "@solana/spl-token";
|
|
5106
|
-
import * as
|
|
3255
|
+
import * as snarkjs from "snarkjs";
|
|
5107
3256
|
|
|
5108
3257
|
// src/core/chain-note.ts
|
|
5109
3258
|
var NOTE_VERSION = 2;
|
|
@@ -5253,8 +3402,8 @@ function chainNoteFromBase64(base64) {
|
|
|
5253
3402
|
// src/core/transact.ts
|
|
5254
3403
|
import { buildPoseidon as buildPoseidon3 } from "circomlibjs";
|
|
5255
3404
|
import nacl4 from "tweetnacl";
|
|
5256
|
-
var
|
|
5257
|
-
var DEFAULT_TRANSACTION_CIRCUITS_URL = "https://
|
|
3405
|
+
var IS_BROWSER = typeof window !== "undefined" || typeof globalThis.document !== "undefined";
|
|
3406
|
+
var DEFAULT_TRANSACTION_CIRCUITS_URL = "https://storage.googleapis.com/cloak-circuits/circuits/0.1.0";
|
|
5258
3407
|
var DEFAULT_CLOAK_RELAY_URL = "https://api.cloak.ag";
|
|
5259
3408
|
function resolveDefaultTransactionCircuitsPath() {
|
|
5260
3409
|
return DEFAULT_TRANSACTION_CIRCUITS_URL;
|
|
@@ -5266,7 +3415,7 @@ async function resolveTransactionCircuitFiles() {
|
|
|
5266
3415
|
const baseNorm = base.endsWith("/") ? base : `${base}/`;
|
|
5267
3416
|
const wasmUrl = `${baseNorm}transaction_js/transaction.wasm`;
|
|
5268
3417
|
const zkeyUrl = `${baseNorm}transaction_final.zkey`;
|
|
5269
|
-
if (
|
|
3418
|
+
if (IS_BROWSER) {
|
|
5270
3419
|
return { wasmPath: wasmUrl, zkeyPath: zkeyUrl };
|
|
5271
3420
|
}
|
|
5272
3421
|
if (_remoteTxCircuitsCache?.base === baseNorm) {
|
|
@@ -5403,7 +3552,7 @@ async function resolveAddressLookupTableAccounts(connection, relayUrl, altAddres
|
|
|
5403
3552
|
const fetched = await Promise.all(
|
|
5404
3553
|
altAddresses.map(async (addr) => {
|
|
5405
3554
|
try {
|
|
5406
|
-
const pubkey = new
|
|
3555
|
+
const pubkey = new PublicKey5(addr);
|
|
5407
3556
|
const result = await connection.getAddressLookupTable(pubkey);
|
|
5408
3557
|
return result.value ?? null;
|
|
5409
3558
|
} catch {
|
|
@@ -5565,7 +3714,7 @@ async function generateTransactionProof(inputs, onProgress) {
|
|
|
5565
3714
|
onProgress?.(10);
|
|
5566
3715
|
const { wasmPath: wasmInput, zkeyPath: zkeyInput } = await resolveTransactionCircuitFiles();
|
|
5567
3716
|
onProgress?.(30);
|
|
5568
|
-
const { proof, publicSignals } = await
|
|
3717
|
+
const { proof, publicSignals } = await snarkjs.groth16.fullProve(
|
|
5569
3718
|
inputs,
|
|
5570
3719
|
wasmInput,
|
|
5571
3720
|
zkeyInput
|
|
@@ -5645,7 +3794,7 @@ function buildPublicInputsBytesFromSignals(publicSignals) {
|
|
|
5645
3794
|
}
|
|
5646
3795
|
var TRANSACT_DISCRIMINATOR = 0;
|
|
5647
3796
|
function deriveNullifierPDA(programId, poolPDA, nullifier) {
|
|
5648
|
-
return
|
|
3797
|
+
return PublicKey5.findProgramAddressSync(
|
|
5649
3798
|
[Buffer.from("nullifier"), poolPDA.toBuffer(), Buffer.from(nullifier)],
|
|
5650
3799
|
programId
|
|
5651
3800
|
);
|
|
@@ -5688,7 +3837,7 @@ function buildTransactInstruction(programId, payer, poolPDA, treasuryPDA, merkle
|
|
|
5688
3837
|
// 4. nullifier PDA 0 (writable)
|
|
5689
3838
|
{ pubkey: nullifierPDA1, isSigner: false, isWritable: true },
|
|
5690
3839
|
// 5. nullifier PDA 1 (writable)
|
|
5691
|
-
{ pubkey:
|
|
3840
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false }
|
|
5692
3841
|
// 6. system_program
|
|
5693
3842
|
];
|
|
5694
3843
|
if (splAccounts) {
|
|
@@ -5713,7 +3862,7 @@ function buildTransactInstruction(programId, payer, poolPDA, treasuryPDA, merkle
|
|
|
5713
3862
|
accounts.push({ pubkey: SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
|
|
5714
3863
|
}
|
|
5715
3864
|
}
|
|
5716
|
-
return new
|
|
3865
|
+
return new TransactionInstruction({
|
|
5717
3866
|
programId,
|
|
5718
3867
|
keys: accounts,
|
|
5719
3868
|
data
|
|
@@ -5721,10 +3870,10 @@ function buildTransactInstruction(programId, payer, poolPDA, treasuryPDA, merkle
|
|
|
5721
3870
|
}
|
|
5722
3871
|
function getCommonALTAddresses() {
|
|
5723
3872
|
return [
|
|
5724
|
-
|
|
3873
|
+
SystemProgram.programId,
|
|
5725
3874
|
SYSVAR_SLOT_HASHES_PUBKEY,
|
|
5726
3875
|
SYSVAR_INSTRUCTIONS_PUBKEY,
|
|
5727
|
-
|
|
3876
|
+
ComputeBudgetProgram.programId
|
|
5728
3877
|
];
|
|
5729
3878
|
}
|
|
5730
3879
|
function dedupePubkeys(addresses) {
|
|
@@ -5785,7 +3934,7 @@ async function createEphemeralALT(connection, depositor, onProgress, additionalA
|
|
|
5785
3934
|
lookupTable: altAddress,
|
|
5786
3935
|
addresses: addressesForCreate
|
|
5787
3936
|
});
|
|
5788
|
-
const tx = new
|
|
3937
|
+
const tx = new Transaction2().add(createIx).add(extendIx);
|
|
5789
3938
|
const { blockhash } = await connection.getLatestBlockhash();
|
|
5790
3939
|
tx.recentBlockhash = blockhash;
|
|
5791
3940
|
tx.feePayer = depositor.publicKey;
|
|
@@ -5951,8 +4100,8 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
|
|
|
5951
4100
|
if (riskQuoteIx) {
|
|
5952
4101
|
baseInstructions.unshift(riskQuoteIx);
|
|
5953
4102
|
}
|
|
5954
|
-
const cuLimitIx =
|
|
5955
|
-
const cuPriceIx =
|
|
4103
|
+
const cuLimitIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 4e5 });
|
|
4104
|
+
const cuPriceIx = ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1e5 });
|
|
5956
4105
|
const fullInstructions = [...baseInstructions, cuLimitIx, cuPriceIx, transactIx];
|
|
5957
4106
|
const compactInstructions = [...baseInstructions, cuLimitIx, transactIx];
|
|
5958
4107
|
const minimalInstructions = [...baseInstructions, transactIx];
|
|
@@ -6161,7 +4310,7 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
|
|
|
6161
4310
|
let legacyTransportAttempt = 0;
|
|
6162
4311
|
while (true) {
|
|
6163
4312
|
try {
|
|
6164
|
-
const tx = new
|
|
4313
|
+
const tx = new Transaction2();
|
|
6165
4314
|
for (const ix of instructions) {
|
|
6166
4315
|
tx.add(ix);
|
|
6167
4316
|
}
|
|
@@ -6343,7 +4492,7 @@ async function fetchRiskQuoteInstruction(riskQuoteUrl, wallet, options) {
|
|
|
6343
4492
|
const signerB58 = signerKey;
|
|
6344
4493
|
const signatureBuf = Buffer.from(signatureB64, "base64");
|
|
6345
4494
|
const messageArr = hexToBytes2(messageHex);
|
|
6346
|
-
const publicKeyBytes = new
|
|
4495
|
+
const publicKeyBytes = new PublicKey5(signerB58).toBytes();
|
|
6347
4496
|
if (signatureBuf.length !== 64 || messageArr.length !== 33 && messageArr.length !== 41 && messageArr.length !== 50 || publicKeyBytes.length !== 32) {
|
|
6348
4497
|
throw new Error(
|
|
6349
4498
|
`Invalid risk quote response: invalid lengths (sig=${signatureBuf.length}, msg=${messageArr.length}, pk=${publicKeyBytes.length})`
|
|
@@ -6365,10 +4514,10 @@ async function fetchRiskQuoteInstruction(riskQuoteUrl, wallet, options) {
|
|
|
6365
4514
|
`Invalid risk quote response: expected relay format { signature, message, signer_pubkey } or { instruction }. Received keys: ${keys}`
|
|
6366
4515
|
);
|
|
6367
4516
|
}
|
|
6368
|
-
return new
|
|
6369
|
-
programId: new
|
|
4517
|
+
return new TransactionInstruction({
|
|
4518
|
+
programId: new PublicKey5(raw.programId),
|
|
6370
4519
|
keys: raw.keys.map((k) => ({
|
|
6371
|
-
pubkey: new
|
|
4520
|
+
pubkey: new PublicKey5(k.pubkey),
|
|
6372
4521
|
isSigner: k.isSigner,
|
|
6373
4522
|
isWritable: k.isWritable
|
|
6374
4523
|
})),
|
|
@@ -7252,12 +5401,12 @@ async function transact(params, options) {
|
|
|
7252
5401
|
if (lastError) {
|
|
7253
5402
|
const classified = classifyRelayError(lastError.message);
|
|
7254
5403
|
const isMerkleClass = !(classified instanceof UtxoAlreadySpentError) && !(classified instanceof SanctionsQuoteError) && !isRootNotFoundError2(lastError);
|
|
7255
|
-
if (!
|
|
5404
|
+
if (!IS_BROWSER && isMerkleClass && relayUrl) {
|
|
7256
5405
|
onProgress?.(
|
|
7257
5406
|
"All relay-tree attempts failed. Rebuilding merkle tree from chain as last resort..."
|
|
7258
5407
|
);
|
|
7259
5408
|
try {
|
|
7260
|
-
const [merkleTreePda] =
|
|
5409
|
+
const [merkleTreePda] = PublicKey5.findProgramAddressSync(
|
|
7261
5410
|
[Buffer.from("merkle_tree"), mint.toBuffer()],
|
|
7262
5411
|
programId
|
|
7263
5412
|
);
|
|
@@ -8326,7 +6475,7 @@ async function swapWithChange(inputUtxos, swapAmount, outputMint, recipientAta,
|
|
|
8326
6475
|
// src/core/scanner.ts
|
|
8327
6476
|
import _bs582 from "bs58";
|
|
8328
6477
|
import {
|
|
8329
|
-
PublicKey as
|
|
6478
|
+
PublicKey as PublicKey6
|
|
8330
6479
|
} from "@solana/web3.js";
|
|
8331
6480
|
import { getAssociatedTokenAddressSync as getAssociatedTokenAddressSync2 } from "@solana/spl-token";
|
|
8332
6481
|
var bs582 = _bs582.default || _bs582;
|
|
@@ -8414,7 +6563,7 @@ function parseSwapOutputMint(data) {
|
|
|
8414
6563
|
const mintBytes = data.slice(SWAP_OUTPUT_MINT_OFFSET, end);
|
|
8415
6564
|
if (mintBytes.every((byte) => byte === 0)) return void 0;
|
|
8416
6565
|
try {
|
|
8417
|
-
return new
|
|
6566
|
+
return new PublicKey6(mintBytes).toBase58();
|
|
8418
6567
|
} catch {
|
|
8419
6568
|
return void 0;
|
|
8420
6569
|
}
|
|
@@ -8504,7 +6653,7 @@ function parseSwapRecipientAta(data) {
|
|
|
8504
6653
|
const recipientAtaBytes = data.slice(recipientAtaStart, recipientAtaEnd);
|
|
8505
6654
|
if (recipientAtaBytes.every((byte) => byte === 0)) return void 0;
|
|
8506
6655
|
try {
|
|
8507
|
-
return new
|
|
6656
|
+
return new PublicKey6(recipientAtaBytes).toBase58();
|
|
8508
6657
|
} catch {
|
|
8509
6658
|
return void 0;
|
|
8510
6659
|
}
|
|
@@ -8819,8 +6968,8 @@ async function scanTransactions(opts) {
|
|
|
8819
6968
|
if (onChainAta) {
|
|
8820
6969
|
try {
|
|
8821
6970
|
const expectedAta = getAssociatedTokenAddressSync2(
|
|
8822
|
-
new
|
|
8823
|
-
new
|
|
6971
|
+
new PublicKey6(asset.mint),
|
|
6972
|
+
new PublicKey6(walletPublicKey)
|
|
8824
6973
|
).toBase58();
|
|
8825
6974
|
if (onChainAta === expectedAta) {
|
|
8826
6975
|
isOurs = true;
|
|
@@ -9123,7 +7272,7 @@ function formatComplianceCsv(report) {
|
|
|
9123
7272
|
}
|
|
9124
7273
|
|
|
9125
7274
|
// src/core/wallet.ts
|
|
9126
|
-
import { PublicKey as
|
|
7275
|
+
import { PublicKey as PublicKey7 } from "@solana/web3.js";
|
|
9127
7276
|
var UtxoWallet = class _UtxoWallet {
|
|
9128
7277
|
constructor(viewingKey) {
|
|
9129
7278
|
this.wallets = /* @__PURE__ */ new Map();
|
|
@@ -9335,7 +7484,7 @@ var UtxoWallet = class _UtxoWallet {
|
|
|
9335
7484
|
data.viewingKey ? new Uint8Array(data.viewingKey) : void 0
|
|
9336
7485
|
);
|
|
9337
7486
|
for (const w of data.wallets) {
|
|
9338
|
-
const mint = new
|
|
7487
|
+
const mint = new PublicKey7(w.mint);
|
|
9339
7488
|
for (const u of w.utxos) {
|
|
9340
7489
|
wallet.addUtxo({
|
|
9341
7490
|
amount: BigInt(u.amount),
|
|
@@ -9418,15 +7567,13 @@ var SimpleWallet = class {
|
|
|
9418
7567
|
};
|
|
9419
7568
|
|
|
9420
7569
|
// src/index.ts
|
|
9421
|
-
var VERSION = "0.1.
|
|
7570
|
+
var VERSION = "0.1.6";
|
|
9422
7571
|
var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
9423
7572
|
export {
|
|
9424
7573
|
CLOAK_PROGRAM_ID,
|
|
9425
7574
|
CloakError,
|
|
9426
7575
|
CloakSDK,
|
|
9427
|
-
DEFAULT_CIRCUITS_URL,
|
|
9428
7576
|
DEFAULT_TRANSACTION_CIRCUITS_URL,
|
|
9429
|
-
EXPECTED_CIRCUIT_HASHES,
|
|
9430
7577
|
FIXED_FEE_LAMPORTS,
|
|
9431
7578
|
LAMPORTS_PER_SOL,
|
|
9432
7579
|
LocalStorageAdapter,
|
|
@@ -9449,7 +7596,6 @@ export {
|
|
|
9449
7596
|
VARIABLE_FEE_NUMERATOR,
|
|
9450
7597
|
VARIABLE_FEE_RATE,
|
|
9451
7598
|
VERSION,
|
|
9452
|
-
areCircuitsAvailable,
|
|
9453
7599
|
bigintToBytes322 as bigintToBytes32,
|
|
9454
7600
|
bigintToHex,
|
|
9455
7601
|
buildMerkleTree,
|
|
@@ -9463,30 +7609,15 @@ export {
|
|
|
9463
7609
|
chainNoteFromBase64,
|
|
9464
7610
|
chainNoteToBase64,
|
|
9465
7611
|
classifyRelayError,
|
|
9466
|
-
cleanupStalePendingOperations,
|
|
9467
|
-
clearPendingDeposits,
|
|
9468
|
-
clearPendingWithdrawals,
|
|
9469
7612
|
computeChainNoteHash,
|
|
9470
|
-
computeCommitment2 as computeCommitment,
|
|
9471
7613
|
computeExtDataHash,
|
|
9472
7614
|
computeMerkleRoot,
|
|
9473
|
-
computeNullifier2 as computeNullifier,
|
|
9474
|
-
computeNullifierAsync,
|
|
9475
|
-
computeNullifierSync,
|
|
9476
|
-
computeOutputsHash,
|
|
9477
|
-
computeOutputsHashAsync,
|
|
9478
|
-
computeOutputsHashSync,
|
|
9479
7615
|
computeProofForLatestDeposit,
|
|
9480
7616
|
computeProofFromChain,
|
|
9481
7617
|
computeSignature,
|
|
9482
|
-
computeSwapOutputsHash,
|
|
9483
|
-
computeSwapOutputsHashAsync,
|
|
9484
|
-
computeSwapOutputsHashSync,
|
|
9485
7618
|
computeCommitment as computeUtxoCommitment,
|
|
9486
7619
|
computeNullifier as computeUtxoNullifier,
|
|
9487
|
-
copyNoteToClipboard,
|
|
9488
7620
|
createCloakError,
|
|
9489
|
-
createDepositInstruction,
|
|
9490
7621
|
createLogger,
|
|
9491
7622
|
createUtxo,
|
|
9492
7623
|
createZeroUtxo,
|
|
@@ -9506,78 +7637,52 @@ export {
|
|
|
9506
7637
|
deriveViewingKeyFromUtxoPrivateKey,
|
|
9507
7638
|
deserializeUtxo,
|
|
9508
7639
|
detectNetworkFromRpcUrl,
|
|
9509
|
-
downloadNote,
|
|
9510
|
-
encodeNoteSimple,
|
|
9511
7640
|
encryptCompactChainNote,
|
|
9512
7641
|
encryptNoteForRecipient,
|
|
9513
7642
|
encryptTransactionMetadata,
|
|
9514
7643
|
encryptTransactionMetadataBundle,
|
|
9515
7644
|
expandSpendKey,
|
|
9516
7645
|
exportKeys,
|
|
9517
|
-
exportNote,
|
|
9518
|
-
exportWalletKeys,
|
|
9519
7646
|
fetchCommitments,
|
|
9520
7647
|
fetchRiskQuoteInstruction,
|
|
9521
7648
|
fetchRiskQuoteIx,
|
|
9522
|
-
filterNotesByNetwork,
|
|
9523
|
-
filterWithdrawableNotes,
|
|
9524
|
-
findNoteByCommitment,
|
|
9525
7649
|
formatAmount,
|
|
9526
7650
|
formatComplianceCsv,
|
|
9527
7651
|
formatErrorForLogging,
|
|
9528
7652
|
formatSol,
|
|
9529
7653
|
fullWithdraw,
|
|
9530
7654
|
generateCloakKeys,
|
|
9531
|
-
generateCommitment,
|
|
9532
|
-
generateCommitmentAsync,
|
|
9533
7655
|
generateMasterSeed,
|
|
9534
|
-
generateNote,
|
|
9535
|
-
generateNoteFromWallet,
|
|
9536
7656
|
generateUtxoKeypair,
|
|
9537
7657
|
generateViewingKeyPair,
|
|
9538
|
-
generateWithdrawRegularProof,
|
|
9539
|
-
generateWithdrawSwapProof,
|
|
9540
7658
|
getAddressExplorerUrl,
|
|
9541
7659
|
getCircuitsPath,
|
|
9542
|
-
getDefaultCircuitsPath,
|
|
9543
7660
|
getDistributableAmount,
|
|
9544
7661
|
getExplorerUrl,
|
|
9545
7662
|
getNkFromUtxoPrivateKey,
|
|
9546
7663
|
getNullifierPDA,
|
|
9547
|
-
getPendingOperationsSummary,
|
|
9548
7664
|
getPublicKey,
|
|
9549
|
-
getPublicViewKey,
|
|
9550
|
-
getRecipientAmount,
|
|
9551
7665
|
getRpcUrlForNetwork,
|
|
9552
7666
|
getShieldPoolPDAs,
|
|
9553
7667
|
getSwapStatePDA,
|
|
9554
|
-
getViewKey,
|
|
9555
|
-
hasPendingOperations,
|
|
9556
7668
|
hexToBigint2 as hexToBigint,
|
|
9557
7669
|
hexToBytes,
|
|
9558
7670
|
importKeys,
|
|
9559
|
-
importWalletKeys,
|
|
9560
7671
|
isDebugEnabled,
|
|
9561
7672
|
isRootNotFoundError,
|
|
9562
7673
|
isValidHex,
|
|
9563
7674
|
isValidRpcUrl,
|
|
9564
7675
|
isValidSolanaAddress,
|
|
9565
7676
|
isWithdrawAmountSufficient,
|
|
9566
|
-
isWithdrawable,
|
|
9567
7677
|
keypairToAdapter,
|
|
9568
|
-
loadPendingDeposits,
|
|
9569
|
-
loadPendingWithdrawals,
|
|
9570
7678
|
parseAmount,
|
|
9571
7679
|
parseError,
|
|
9572
|
-
parseNote,
|
|
9573
7680
|
parseRelayErrorResponse,
|
|
9574
7681
|
parseTransactionError,
|
|
9575
7682
|
partialWithdraw,
|
|
9576
7683
|
poseidonHash,
|
|
9577
7684
|
preflightCheck,
|
|
9578
7685
|
preflightNullifiers,
|
|
9579
|
-
prepareEncryptedOutput,
|
|
9580
|
-
prepareEncryptedOutputForRecipient,
|
|
9581
7686
|
proofToBytes,
|
|
9582
7687
|
pubkeyToFieldElement,
|
|
9583
7688
|
pubkeyToLimbs,
|
|
@@ -9585,16 +7690,11 @@ export {
|
|
|
9585
7690
|
randomFieldElement,
|
|
9586
7691
|
readMerkleTreeState,
|
|
9587
7692
|
registerViewingKey,
|
|
9588
|
-
removePendingDeposit,
|
|
9589
|
-
removePendingWithdrawal,
|
|
9590
|
-
savePendingDeposit,
|
|
9591
|
-
savePendingWithdrawal,
|
|
9592
7693
|
scanNotesForWallet,
|
|
9593
7694
|
scanTransactions,
|
|
9594
7695
|
sdkLogger,
|
|
9595
7696
|
selectUtxos,
|
|
9596
7697
|
sendTransaction,
|
|
9597
|
-
serializeNote,
|
|
9598
7698
|
serializeUtxo,
|
|
9599
7699
|
setCircuitsPath,
|
|
9600
7700
|
setDebugMode,
|
|
@@ -9608,21 +7708,12 @@ export {
|
|
|
9608
7708
|
transfer,
|
|
9609
7709
|
truncate,
|
|
9610
7710
|
tryDecryptNote,
|
|
9611
|
-
updateNoteWithDeposit,
|
|
9612
|
-
updatePendingDeposit,
|
|
9613
|
-
updatePendingWithdrawal,
|
|
9614
7711
|
bigintToBytes32 as utxoBigintToBytes32,
|
|
9615
7712
|
utxoEquals,
|
|
9616
7713
|
hexToBigint as utxoHexToBigint,
|
|
9617
|
-
validateDepositParams,
|
|
9618
|
-
validateNote,
|
|
9619
7714
|
validateOutputsSum,
|
|
9620
7715
|
validateRoot,
|
|
9621
|
-
validateTransfers,
|
|
9622
7716
|
validateWalletConnected,
|
|
9623
|
-
validateWithdrawableNote,
|
|
9624
|
-
verifyAllCircuits,
|
|
9625
|
-
verifyCircuitIntegrity,
|
|
9626
7717
|
verifyUtxos,
|
|
9627
7718
|
waitForRoot,
|
|
9628
7719
|
withTiming
|