@cloak.dev/sdk 0.1.7 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -183
- package/dist/index.cjs +2289 -288
- package/dist/index.d.cts +1502 -433
- package/dist/index.d.ts +1502 -433
- package/dist/index.js +2199 -249
- package/package.json +1 -2
- package/CHANGELOG.md +0 -173
package/dist/index.cjs
CHANGED
|
@@ -36,8 +36,8 @@ __export(utxo_exports, {
|
|
|
36
36
|
NATIVE_SOL_MINT: () => NATIVE_SOL_MINT,
|
|
37
37
|
bigintToBytes32: () => bigintToBytes322,
|
|
38
38
|
bigintToHex: () => bigintToHex,
|
|
39
|
-
computeCommitment: () =>
|
|
40
|
-
computeNullifier: () =>
|
|
39
|
+
computeCommitment: () => computeCommitment2,
|
|
40
|
+
computeNullifier: () => computeNullifier2,
|
|
41
41
|
computeSignature: () => computeSignature,
|
|
42
42
|
createUtxo: () => createUtxo,
|
|
43
43
|
createZeroUtxo: () => createZeroUtxo,
|
|
@@ -112,7 +112,7 @@ async function createUtxo(amount, keypair, mintAddress = NATIVE_SOL_MINT) {
|
|
|
112
112
|
blinding,
|
|
113
113
|
mintAddress
|
|
114
114
|
};
|
|
115
|
-
utxo.commitment = await
|
|
115
|
+
utxo.commitment = await computeCommitment2(utxo);
|
|
116
116
|
return utxo;
|
|
117
117
|
}
|
|
118
118
|
async function createZeroUtxo(mintAddress = NATIVE_SOL_MINT, salt) {
|
|
@@ -127,7 +127,7 @@ async function createZeroUtxo(mintAddress = NATIVE_SOL_MINT, salt) {
|
|
|
127
127
|
blinding: BigInt(0),
|
|
128
128
|
mintAddress
|
|
129
129
|
};
|
|
130
|
-
utxo.commitment = await
|
|
130
|
+
utxo.commitment = await computeCommitment2(utxo);
|
|
131
131
|
utxo.index = 0;
|
|
132
132
|
return utxo;
|
|
133
133
|
}
|
|
@@ -139,7 +139,7 @@ function pubkeyToFieldElement(pubkey) {
|
|
|
139
139
|
}
|
|
140
140
|
return value % FIELD_MODULUS;
|
|
141
141
|
}
|
|
142
|
-
async function
|
|
142
|
+
async function computeCommitment2(utxo) {
|
|
143
143
|
const poseidon3 = await getPoseidon2();
|
|
144
144
|
const mintField = pubkeyToFieldElement(utxo.mintAddress);
|
|
145
145
|
const hash = poseidon3([
|
|
@@ -155,12 +155,12 @@ async function computeSignature(privateKey, commitment, pathIndex) {
|
|
|
155
155
|
const hash = poseidon3([privateKey, commitment, pathIndex]);
|
|
156
156
|
return poseidon3.F.toObject(hash);
|
|
157
157
|
}
|
|
158
|
-
async function
|
|
158
|
+
async function computeNullifier2(utxo) {
|
|
159
159
|
if (utxo.index === void 0) {
|
|
160
160
|
throw new Error("UTXO must have an index to compute nullifier");
|
|
161
161
|
}
|
|
162
162
|
const poseidon3 = await getPoseidon2();
|
|
163
|
-
const commitment = utxo.commitment ?? await
|
|
163
|
+
const commitment = utxo.commitment ?? await computeCommitment2(utxo);
|
|
164
164
|
const pathIndex = BigInt(utxo.index);
|
|
165
165
|
const signature = await computeSignature(
|
|
166
166
|
utxo.keypair.privateKey,
|
|
@@ -202,7 +202,7 @@ async function deserializeUtxo(bytes) {
|
|
|
202
202
|
const blindingBytes = bytes.slice(40, 72);
|
|
203
203
|
const blinding = bytesToBigint(blindingBytes);
|
|
204
204
|
const mintBytes = bytes.slice(72, 104);
|
|
205
|
-
const mintAddress = new
|
|
205
|
+
const mintAddress = new import_web33.PublicKey(mintBytes);
|
|
206
206
|
const index = view.getUint32(104, true);
|
|
207
207
|
const utxo = {
|
|
208
208
|
amount,
|
|
@@ -211,7 +211,7 @@ async function deserializeUtxo(bytes) {
|
|
|
211
211
|
mintAddress,
|
|
212
212
|
index: index > 0 ? index : void 0
|
|
213
213
|
};
|
|
214
|
-
utxo.commitment = await
|
|
214
|
+
utxo.commitment = await computeCommitment2(utxo);
|
|
215
215
|
return utxo;
|
|
216
216
|
}
|
|
217
217
|
function bigintToBytes(value, length) {
|
|
@@ -247,8 +247,8 @@ function bigintToBytes322(value) {
|
|
|
247
247
|
return result;
|
|
248
248
|
}
|
|
249
249
|
async function utxoEquals(a, b) {
|
|
250
|
-
const commitmentA = a.commitment ?? await
|
|
251
|
-
const commitmentB = b.commitment ?? await
|
|
250
|
+
const commitmentA = a.commitment ?? await computeCommitment2(a);
|
|
251
|
+
const commitmentB = b.commitment ?? await computeCommitment2(b);
|
|
252
252
|
return commitmentA === commitmentB;
|
|
253
253
|
}
|
|
254
254
|
function sumUtxoAmounts(utxos) {
|
|
@@ -270,16 +270,16 @@ function selectUtxos(available, targetAmount) {
|
|
|
270
270
|
}
|
|
271
271
|
return selected;
|
|
272
272
|
}
|
|
273
|
-
var
|
|
273
|
+
var import_web33, import_circomlibjs2, import_blake32, FIELD_MODULUS, UTXO_KEY_DOMAIN, NATIVE_SOL_MINT, poseidonInstance;
|
|
274
274
|
var init_utxo = __esm({
|
|
275
275
|
"src/core/utxo.ts"() {
|
|
276
276
|
"use strict";
|
|
277
|
-
|
|
277
|
+
import_web33 = require("@solana/web3.js");
|
|
278
278
|
import_circomlibjs2 = require("circomlibjs");
|
|
279
279
|
import_blake32 = require("@noble/hashes/blake3");
|
|
280
280
|
FIELD_MODULUS = BigInt("21888242871839275222246405745257275088548364400416034343698204186575808495617");
|
|
281
281
|
UTXO_KEY_DOMAIN = new TextEncoder().encode("cloak_utxo_priv_v1");
|
|
282
|
-
NATIVE_SOL_MINT = new
|
|
282
|
+
NATIVE_SOL_MINT = new import_web33.PublicKey("So11111111111111111111111111111111111111112");
|
|
283
283
|
poseidonInstance = null;
|
|
284
284
|
}
|
|
285
285
|
});
|
|
@@ -290,7 +290,9 @@ __export(index_exports, {
|
|
|
290
290
|
CLOAK_PROGRAM_ID: () => CLOAK_PROGRAM_ID,
|
|
291
291
|
CloakError: () => CloakError,
|
|
292
292
|
CloakSDK: () => CloakSDK,
|
|
293
|
+
DEFAULT_CIRCUITS_URL: () => DEFAULT_CIRCUITS_URL,
|
|
293
294
|
DEFAULT_TRANSACTION_CIRCUITS_URL: () => DEFAULT_TRANSACTION_CIRCUITS_URL,
|
|
295
|
+
EXPECTED_CIRCUIT_HASHES: () => EXPECTED_CIRCUIT_HASHES,
|
|
294
296
|
FIXED_FEE_LAMPORTS: () => FIXED_FEE_LAMPORTS,
|
|
295
297
|
LAMPORTS_PER_SOL: () => LAMPORTS_PER_SOL,
|
|
296
298
|
LocalStorageAdapter: () => LocalStorageAdapter,
|
|
@@ -313,6 +315,7 @@ __export(index_exports, {
|
|
|
313
315
|
VARIABLE_FEE_NUMERATOR: () => VARIABLE_FEE_NUMERATOR,
|
|
314
316
|
VARIABLE_FEE_RATE: () => VARIABLE_FEE_RATE,
|
|
315
317
|
VERSION: () => VERSION,
|
|
318
|
+
areCircuitsAvailable: () => areCircuitsAvailable,
|
|
316
319
|
bigintToBytes32: () => bigintToBytes32,
|
|
317
320
|
bigintToHex: () => bigintToHex,
|
|
318
321
|
buildMerkleTree: () => buildMerkleTree,
|
|
@@ -326,15 +329,30 @@ __export(index_exports, {
|
|
|
326
329
|
chainNoteFromBase64: () => chainNoteFromBase64,
|
|
327
330
|
chainNoteToBase64: () => chainNoteToBase64,
|
|
328
331
|
classifyRelayError: () => classifyRelayError,
|
|
332
|
+
cleanupStalePendingOperations: () => cleanupStalePendingOperations,
|
|
333
|
+
clearPendingDeposits: () => clearPendingDeposits,
|
|
334
|
+
clearPendingWithdrawals: () => clearPendingWithdrawals,
|
|
329
335
|
computeChainNoteHash: () => computeChainNoteHash,
|
|
336
|
+
computeCommitment: () => computeCommitment,
|
|
330
337
|
computeExtDataHash: () => computeExtDataHash,
|
|
331
338
|
computeMerkleRoot: () => computeMerkleRoot,
|
|
339
|
+
computeNullifier: () => computeNullifier,
|
|
340
|
+
computeNullifierAsync: () => computeNullifierAsync,
|
|
341
|
+
computeNullifierSync: () => computeNullifierSync,
|
|
342
|
+
computeOutputsHash: () => computeOutputsHash,
|
|
343
|
+
computeOutputsHashAsync: () => computeOutputsHashAsync,
|
|
344
|
+
computeOutputsHashSync: () => computeOutputsHashSync,
|
|
332
345
|
computeProofForLatestDeposit: () => computeProofForLatestDeposit,
|
|
333
346
|
computeProofFromChain: () => computeProofFromChain,
|
|
334
347
|
computeSignature: () => computeSignature,
|
|
335
|
-
|
|
336
|
-
|
|
348
|
+
computeSwapOutputsHash: () => computeSwapOutputsHash,
|
|
349
|
+
computeSwapOutputsHashAsync: () => computeSwapOutputsHashAsync,
|
|
350
|
+
computeSwapOutputsHashSync: () => computeSwapOutputsHashSync,
|
|
351
|
+
computeUtxoCommitment: () => computeCommitment2,
|
|
352
|
+
computeUtxoNullifier: () => computeNullifier2,
|
|
353
|
+
copyNoteToClipboard: () => copyNoteToClipboard,
|
|
337
354
|
createCloakError: () => createCloakError,
|
|
355
|
+
createDepositInstruction: () => createDepositInstruction,
|
|
338
356
|
createLogger: () => createLogger,
|
|
339
357
|
createUtxo: () => createUtxo,
|
|
340
358
|
createZeroUtxo: () => createZeroUtxo,
|
|
@@ -354,52 +372,78 @@ __export(index_exports, {
|
|
|
354
372
|
deriveViewingKeyFromUtxoPrivateKey: () => deriveViewingKeyFromUtxoPrivateKey,
|
|
355
373
|
deserializeUtxo: () => deserializeUtxo,
|
|
356
374
|
detectNetworkFromRpcUrl: () => detectNetworkFromRpcUrl,
|
|
375
|
+
downloadNote: () => downloadNote,
|
|
376
|
+
encodeNoteSimple: () => encodeNoteSimple,
|
|
357
377
|
encryptCompactChainNote: () => encryptCompactChainNote,
|
|
358
378
|
encryptNoteForRecipient: () => encryptNoteForRecipient,
|
|
359
379
|
encryptTransactionMetadata: () => encryptTransactionMetadata,
|
|
360
380
|
encryptTransactionMetadataBundle: () => encryptTransactionMetadataBundle,
|
|
361
381
|
expandSpendKey: () => expandSpendKey,
|
|
362
382
|
exportKeys: () => exportKeys,
|
|
383
|
+
exportNote: () => exportNote,
|
|
384
|
+
exportWalletKeys: () => exportWalletKeys,
|
|
363
385
|
fetchCommitments: () => fetchCommitments,
|
|
364
386
|
fetchRiskQuoteInstruction: () => fetchRiskQuoteInstruction,
|
|
365
387
|
fetchRiskQuoteIx: () => fetchRiskQuoteIx,
|
|
388
|
+
filterNotesByNetwork: () => filterNotesByNetwork,
|
|
389
|
+
filterWithdrawableNotes: () => filterWithdrawableNotes,
|
|
390
|
+
findNoteByCommitment: () => findNoteByCommitment,
|
|
366
391
|
formatAmount: () => formatAmount,
|
|
367
392
|
formatComplianceCsv: () => formatComplianceCsv,
|
|
368
393
|
formatErrorForLogging: () => formatErrorForLogging,
|
|
369
394
|
formatSol: () => formatSol,
|
|
370
395
|
fullWithdraw: () => fullWithdraw,
|
|
371
396
|
generateCloakKeys: () => generateCloakKeys,
|
|
397
|
+
generateCommitment: () => generateCommitment,
|
|
398
|
+
generateCommitmentAsync: () => generateCommitmentAsync,
|
|
372
399
|
generateMasterSeed: () => generateMasterSeed,
|
|
400
|
+
generateNote: () => generateNote,
|
|
401
|
+
generateNoteFromWallet: () => generateNoteFromWallet,
|
|
373
402
|
generateUtxoKeypair: () => generateUtxoKeypair,
|
|
374
403
|
generateViewingKeyPair: () => generateViewingKeyPair,
|
|
404
|
+
generateWithdrawRegularProof: () => generateWithdrawRegularProof,
|
|
405
|
+
generateWithdrawSwapProof: () => generateWithdrawSwapProof,
|
|
375
406
|
getAddressExplorerUrl: () => getAddressExplorerUrl,
|
|
376
407
|
getCircuitsPath: () => getCircuitsPath,
|
|
408
|
+
getDefaultCircuitsPath: () => getDefaultCircuitsPath,
|
|
377
409
|
getDistributableAmount: () => getDistributableAmount,
|
|
378
410
|
getExplorerUrl: () => getExplorerUrl,
|
|
379
411
|
getNkFromUtxoPrivateKey: () => getNkFromUtxoPrivateKey,
|
|
380
412
|
getNullifierPDA: () => getNullifierPDA,
|
|
413
|
+
getPendingOperationsSummary: () => getPendingOperationsSummary,
|
|
381
414
|
getPublicKey: () => getPublicKey,
|
|
415
|
+
getPublicViewKey: () => getPublicViewKey,
|
|
416
|
+
getRecipientAmount: () => getRecipientAmount,
|
|
382
417
|
getRpcUrlForNetwork: () => getRpcUrlForNetwork,
|
|
383
418
|
getShieldPoolPDAs: () => getShieldPoolPDAs,
|
|
384
419
|
getSwapStatePDA: () => getSwapStatePDA,
|
|
420
|
+
getViewKey: () => getViewKey,
|
|
421
|
+
hasPendingOperations: () => hasPendingOperations,
|
|
385
422
|
hexToBigint: () => hexToBigint,
|
|
386
423
|
hexToBytes: () => hexToBytes,
|
|
387
424
|
importKeys: () => importKeys,
|
|
425
|
+
importWalletKeys: () => importWalletKeys,
|
|
388
426
|
isDebugEnabled: () => isDebugEnabled,
|
|
389
427
|
isRootNotFoundError: () => isRootNotFoundError,
|
|
390
428
|
isValidHex: () => isValidHex,
|
|
391
429
|
isValidRpcUrl: () => isValidRpcUrl,
|
|
392
430
|
isValidSolanaAddress: () => isValidSolanaAddress,
|
|
393
431
|
isWithdrawAmountSufficient: () => isWithdrawAmountSufficient,
|
|
432
|
+
isWithdrawable: () => isWithdrawable,
|
|
394
433
|
keypairToAdapter: () => keypairToAdapter,
|
|
434
|
+
loadPendingDeposits: () => loadPendingDeposits,
|
|
435
|
+
loadPendingWithdrawals: () => loadPendingWithdrawals,
|
|
395
436
|
parseAmount: () => parseAmount,
|
|
396
437
|
parseError: () => parseError,
|
|
438
|
+
parseNote: () => parseNote,
|
|
397
439
|
parseRelayErrorResponse: () => parseRelayErrorResponse,
|
|
398
440
|
parseTransactionError: () => parseTransactionError,
|
|
399
441
|
partialWithdraw: () => partialWithdraw,
|
|
400
442
|
poseidonHash: () => poseidonHash,
|
|
401
443
|
preflightCheck: () => preflightCheck,
|
|
402
444
|
preflightNullifiers: () => preflightNullifiers,
|
|
445
|
+
prepareEncryptedOutput: () => prepareEncryptedOutput,
|
|
446
|
+
prepareEncryptedOutputForRecipient: () => prepareEncryptedOutputForRecipient,
|
|
403
447
|
proofToBytes: () => proofToBytes,
|
|
404
448
|
pubkeyToFieldElement: () => pubkeyToFieldElement,
|
|
405
449
|
pubkeyToLimbs: () => pubkeyToLimbs,
|
|
@@ -407,11 +451,16 @@ __export(index_exports, {
|
|
|
407
451
|
randomFieldElement: () => randomFieldElement,
|
|
408
452
|
readMerkleTreeState: () => readMerkleTreeState,
|
|
409
453
|
registerViewingKey: () => registerViewingKey,
|
|
454
|
+
removePendingDeposit: () => removePendingDeposit,
|
|
455
|
+
removePendingWithdrawal: () => removePendingWithdrawal,
|
|
456
|
+
savePendingDeposit: () => savePendingDeposit,
|
|
457
|
+
savePendingWithdrawal: () => savePendingWithdrawal,
|
|
410
458
|
scanNotesForWallet: () => scanNotesForWallet,
|
|
411
459
|
scanTransactions: () => scanTransactions,
|
|
412
460
|
sdkLogger: () => sdkLogger,
|
|
413
461
|
selectUtxos: () => selectUtxos,
|
|
414
462
|
sendTransaction: () => sendTransaction,
|
|
463
|
+
serializeNote: () => serializeNote,
|
|
415
464
|
serializeUtxo: () => serializeUtxo,
|
|
416
465
|
setCircuitsPath: () => setCircuitsPath,
|
|
417
466
|
setDebugMode: () => setDebugMode,
|
|
@@ -425,12 +474,21 @@ __export(index_exports, {
|
|
|
425
474
|
transfer: () => transfer,
|
|
426
475
|
truncate: () => truncate,
|
|
427
476
|
tryDecryptNote: () => tryDecryptNote,
|
|
477
|
+
updateNoteWithDeposit: () => updateNoteWithDeposit,
|
|
478
|
+
updatePendingDeposit: () => updatePendingDeposit,
|
|
479
|
+
updatePendingWithdrawal: () => updatePendingWithdrawal,
|
|
428
480
|
utxoBigintToBytes32: () => bigintToBytes322,
|
|
429
481
|
utxoEquals: () => utxoEquals,
|
|
430
482
|
utxoHexToBigint: () => hexToBigint2,
|
|
483
|
+
validateDepositParams: () => validateDepositParams,
|
|
484
|
+
validateNote: () => validateNote,
|
|
431
485
|
validateOutputsSum: () => validateOutputsSum,
|
|
432
486
|
validateRoot: () => validateRoot,
|
|
487
|
+
validateTransfers: () => validateTransfers,
|
|
433
488
|
validateWalletConnected: () => validateWalletConnected,
|
|
489
|
+
validateWithdrawableNote: () => validateWithdrawableNote,
|
|
490
|
+
verifyAllCircuits: () => verifyAllCircuits,
|
|
491
|
+
verifyCircuitIntegrity: () => verifyCircuitIntegrity,
|
|
434
492
|
verifyUtxos: () => verifyUtxos,
|
|
435
493
|
waitForRoot: () => waitForRoot,
|
|
436
494
|
withTiming: () => withTiming
|
|
@@ -438,7 +496,7 @@ __export(index_exports, {
|
|
|
438
496
|
module.exports = __toCommonJS(index_exports);
|
|
439
497
|
|
|
440
498
|
// src/core/CloakSDK.ts
|
|
441
|
-
var
|
|
499
|
+
var import_web35 = require("@solana/web3.js");
|
|
442
500
|
|
|
443
501
|
// src/core/types.ts
|
|
444
502
|
var CloakError = class extends Error {
|
|
@@ -495,6 +553,79 @@ function hexToBigint(hex) {
|
|
|
495
553
|
const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
496
554
|
return BigInt("0x" + cleanHex);
|
|
497
555
|
}
|
|
556
|
+
async function computeCommitment(amount, r, sk_spend) {
|
|
557
|
+
const [sk0, sk1] = splitTo2Limbs(sk_spend);
|
|
558
|
+
const [r0, r1] = splitTo2Limbs(r);
|
|
559
|
+
const pk_spend = await poseidonHash([sk0, sk1]);
|
|
560
|
+
return await poseidonHash([amount, r0, r1, pk_spend]);
|
|
561
|
+
}
|
|
562
|
+
async function generateCommitmentAsync(amountLamports, r, skSpend) {
|
|
563
|
+
const amount = BigInt(amountLamports);
|
|
564
|
+
const rValue = hexToBigint(bytesToHex(r));
|
|
565
|
+
const skValue = hexToBigint(bytesToHex(skSpend));
|
|
566
|
+
return await computeCommitment(amount, rValue, skValue);
|
|
567
|
+
}
|
|
568
|
+
function generateCommitment(_amountLamports, _r, _skSpend) {
|
|
569
|
+
throw new Error("generateCommitment is deprecated. Use generateCommitmentAsync instead.");
|
|
570
|
+
}
|
|
571
|
+
async function computeNullifier(sk_spend, leafIndex) {
|
|
572
|
+
const [sk0, sk1] = splitTo2Limbs(sk_spend);
|
|
573
|
+
return await poseidonHash([sk0, sk1, leafIndex]);
|
|
574
|
+
}
|
|
575
|
+
async function computeNullifierAsync(skSpend, leafIndex) {
|
|
576
|
+
const skValue = typeof skSpend === "string" ? hexToBigint(skSpend) : hexToBigint(bytesToHex(skSpend));
|
|
577
|
+
return await computeNullifier(skValue, BigInt(leafIndex));
|
|
578
|
+
}
|
|
579
|
+
function computeNullifierSync(_skSpend, _leafIndex) {
|
|
580
|
+
throw new Error("computeNullifierSync is deprecated. Use computeNullifierAsync instead.");
|
|
581
|
+
}
|
|
582
|
+
async function computeOutputsHashAsync(outputs) {
|
|
583
|
+
let hash = 0n;
|
|
584
|
+
for (const output of outputs) {
|
|
585
|
+
const [lo, hi] = pubkeyToLimbs(output.recipient);
|
|
586
|
+
hash = await poseidonHash([hash, lo, hi, BigInt(output.amount)]);
|
|
587
|
+
}
|
|
588
|
+
return hash;
|
|
589
|
+
}
|
|
590
|
+
async function computeOutputsHash(outAddr, outAmount, outFlags) {
|
|
591
|
+
let hash = 0n;
|
|
592
|
+
for (let i = 0; i < 5; i++) {
|
|
593
|
+
if (outFlags[i] === 1) {
|
|
594
|
+
hash = await poseidonHash([hash, outAddr[i][0], outAddr[i][1], outAmount[i]]);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
return hash;
|
|
598
|
+
}
|
|
599
|
+
function computeOutputsHashSync(_outputs) {
|
|
600
|
+
throw new Error("computeOutputsHashSync is deprecated. Use computeOutputsHashAsync instead.");
|
|
601
|
+
}
|
|
602
|
+
async function computeSwapOutputsHash(inputMintLimbs, outputMintLimbs, recipientAtaLimbs, minOutputAmount, publicAmount) {
|
|
603
|
+
return await poseidonHash([
|
|
604
|
+
inputMintLimbs[0],
|
|
605
|
+
inputMintLimbs[1],
|
|
606
|
+
outputMintLimbs[0],
|
|
607
|
+
outputMintLimbs[1],
|
|
608
|
+
recipientAtaLimbs[0],
|
|
609
|
+
recipientAtaLimbs[1],
|
|
610
|
+
minOutputAmount,
|
|
611
|
+
publicAmount
|
|
612
|
+
]);
|
|
613
|
+
}
|
|
614
|
+
async function computeSwapOutputsHashAsync(inputMint, outputMint, recipientAta, minOutputAmount, amount) {
|
|
615
|
+
const inputMintLimbs = pubkeyToLimbs(inputMint);
|
|
616
|
+
const outputMintLimbs = pubkeyToLimbs(outputMint);
|
|
617
|
+
const recipientAtaLimbs = pubkeyToLimbs(recipientAta);
|
|
618
|
+
return await computeSwapOutputsHash(
|
|
619
|
+
inputMintLimbs,
|
|
620
|
+
outputMintLimbs,
|
|
621
|
+
recipientAtaLimbs,
|
|
622
|
+
BigInt(minOutputAmount),
|
|
623
|
+
BigInt(amount)
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
function computeSwapOutputsHashSync(_outputMint, _recipientAta, _minOutputAmount, _amount) {
|
|
627
|
+
throw new Error("computeSwapOutputsHashSync is deprecated. Use computeSwapOutputsHashAsync instead.");
|
|
628
|
+
}
|
|
498
629
|
function bigintToBytes32(n) {
|
|
499
630
|
const hex = n.toString(16).padStart(64, "0");
|
|
500
631
|
const bytes = new Uint8Array(32);
|
|
@@ -767,11 +898,223 @@ function importKeys(exported) {
|
|
|
767
898
|
return generateCloakKeys(masterSeed);
|
|
768
899
|
}
|
|
769
900
|
|
|
901
|
+
// src/utils/network.ts
|
|
902
|
+
function detectNetworkFromRpcUrl(rpcUrl) {
|
|
903
|
+
const url = rpcUrl || process.env.NEXT_PUBLIC_SOLANA_RPC_URL || "";
|
|
904
|
+
const lowerUrl = url.toLowerCase();
|
|
905
|
+
if (lowerUrl.includes("mainnet") || lowerUrl.includes("api.mainnet-beta") || lowerUrl.includes("mainnet-beta")) {
|
|
906
|
+
return "mainnet";
|
|
907
|
+
}
|
|
908
|
+
if (lowerUrl.includes("testnet") || lowerUrl.includes("api.testnet")) {
|
|
909
|
+
return "testnet";
|
|
910
|
+
}
|
|
911
|
+
if (lowerUrl.includes("devnet") || lowerUrl.includes("api.devnet")) {
|
|
912
|
+
return "devnet";
|
|
913
|
+
}
|
|
914
|
+
if (lowerUrl.includes("localhost") || lowerUrl.includes("127.0.0.1") || lowerUrl.includes("local")) {
|
|
915
|
+
return "localnet";
|
|
916
|
+
}
|
|
917
|
+
return "mainnet";
|
|
918
|
+
}
|
|
919
|
+
function getRpcUrlForNetwork(network) {
|
|
920
|
+
switch (network) {
|
|
921
|
+
case "mainnet":
|
|
922
|
+
return "https://api.mainnet-beta.solana.com";
|
|
923
|
+
case "devnet":
|
|
924
|
+
return "https://api.devnet.solana.com";
|
|
925
|
+
case "localnet":
|
|
926
|
+
return "http://localhost:8899";
|
|
927
|
+
default:
|
|
928
|
+
return "https://api.mainnet-beta.solana.com";
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
function isValidRpcUrl(url) {
|
|
932
|
+
try {
|
|
933
|
+
const parsed = new URL(url);
|
|
934
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
935
|
+
} catch {
|
|
936
|
+
return false;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
function getExplorerUrl(signature, network = "devnet") {
|
|
940
|
+
const cluster = network === "mainnet" ? "" : `?cluster=${network}`;
|
|
941
|
+
return `https://explorer.solana.com/tx/${signature}${cluster}`;
|
|
942
|
+
}
|
|
943
|
+
function getAddressExplorerUrl(address, network = "devnet") {
|
|
944
|
+
const cluster = network === "mainnet" ? "" : `?cluster=${network}`;
|
|
945
|
+
return `https://explorer.solana.com/address/${address}${cluster}`;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// src/utils/fees.ts
|
|
949
|
+
var LAMPORTS_PER_SOL = 1e9;
|
|
950
|
+
var FIXED_FEE_LAMPORTS = 5e6;
|
|
951
|
+
var VARIABLE_FEE_NUMERATOR = 3;
|
|
952
|
+
var VARIABLE_FEE_DENOMINATOR = 1e3;
|
|
953
|
+
var VARIABLE_FEE_RATE = VARIABLE_FEE_NUMERATOR / VARIABLE_FEE_DENOMINATOR;
|
|
954
|
+
var MIN_DEPOSIT_LAMPORTS = 1e7;
|
|
955
|
+
function calculateFee(amountLamports) {
|
|
956
|
+
const variableFee = Math.floor(amountLamports * VARIABLE_FEE_NUMERATOR / VARIABLE_FEE_DENOMINATOR);
|
|
957
|
+
return FIXED_FEE_LAMPORTS + variableFee;
|
|
958
|
+
}
|
|
959
|
+
function calculateFeeBigint(amountLamports) {
|
|
960
|
+
const fixed = BigInt(FIXED_FEE_LAMPORTS);
|
|
961
|
+
const variable = amountLamports * BigInt(VARIABLE_FEE_NUMERATOR) / BigInt(VARIABLE_FEE_DENOMINATOR);
|
|
962
|
+
return fixed + variable;
|
|
963
|
+
}
|
|
964
|
+
function isWithdrawAmountSufficient(amountLamports) {
|
|
965
|
+
return amountLamports > calculateFeeBigint(amountLamports);
|
|
966
|
+
}
|
|
967
|
+
function getDistributableAmount(amountLamports) {
|
|
968
|
+
return amountLamports - calculateFee(amountLamports);
|
|
969
|
+
}
|
|
970
|
+
function formatAmount(lamports, decimals = 9) {
|
|
971
|
+
return (lamports / LAMPORTS_PER_SOL).toFixed(decimals);
|
|
972
|
+
}
|
|
973
|
+
function parseAmount(sol) {
|
|
974
|
+
const num = parseFloat(sol);
|
|
975
|
+
if (isNaN(num) || num < 0) {
|
|
976
|
+
throw new Error(`Invalid SOL amount: ${sol}`);
|
|
977
|
+
}
|
|
978
|
+
return Math.floor(num * LAMPORTS_PER_SOL);
|
|
979
|
+
}
|
|
980
|
+
function validateOutputsSum(outputs, expectedTotal) {
|
|
981
|
+
const sum = outputs.reduce((acc, out) => acc + out.amount, 0);
|
|
982
|
+
return sum === expectedTotal;
|
|
983
|
+
}
|
|
984
|
+
function calculateRelayFee(amountLamports, feeBps) {
|
|
985
|
+
if (feeBps < 0 || feeBps > 1e4) {
|
|
986
|
+
throw new Error("Fee basis points must be between 0 and 10000");
|
|
987
|
+
}
|
|
988
|
+
return Math.floor(amountLamports * feeBps / 1e4);
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
// src/core/note-manager.ts
|
|
992
|
+
async function generateNote(amountLamports, network) {
|
|
993
|
+
const actualNetwork = network || detectNetworkFromRpcUrl();
|
|
994
|
+
const skSpend = randomBytes(32);
|
|
995
|
+
const rBytes = randomBytes(32);
|
|
996
|
+
const commitmentBigint = await generateCommitmentAsync(amountLamports, rBytes, skSpend);
|
|
997
|
+
const commitmentHex = commitmentBigint.toString(16).padStart(64, "0");
|
|
998
|
+
const skSpendHex = bytesToHex(skSpend);
|
|
999
|
+
const rHex = bytesToHex(rBytes);
|
|
1000
|
+
return {
|
|
1001
|
+
version: "1.0",
|
|
1002
|
+
amount: amountLamports,
|
|
1003
|
+
commitment: commitmentHex,
|
|
1004
|
+
sk_spend: skSpendHex,
|
|
1005
|
+
r: rHex,
|
|
1006
|
+
timestamp: Date.now(),
|
|
1007
|
+
network: actualNetwork
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
async function generateNoteFromWallet(amountLamports, keys, network) {
|
|
1011
|
+
const actualNetwork = network || detectNetworkFromRpcUrl();
|
|
1012
|
+
const rBytes = randomBytes(32);
|
|
1013
|
+
const sk_spend = hexToBytes(keys.spend.sk_spend_hex);
|
|
1014
|
+
const commitmentBigint = await generateCommitmentAsync(amountLamports, rBytes, sk_spend);
|
|
1015
|
+
const commitmentHex = commitmentBigint.toString(16).padStart(64, "0");
|
|
1016
|
+
return {
|
|
1017
|
+
version: "2.0",
|
|
1018
|
+
amount: amountLamports,
|
|
1019
|
+
commitment: commitmentHex,
|
|
1020
|
+
sk_spend: keys.spend.sk_spend_hex,
|
|
1021
|
+
r: bytesToHex(rBytes),
|
|
1022
|
+
timestamp: Date.now(),
|
|
1023
|
+
network: actualNetwork
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
function parseNote(jsonString) {
|
|
1027
|
+
const note = JSON.parse(jsonString);
|
|
1028
|
+
if (!note.version || !note.amount || !note.commitment || !note.sk_spend || !note.r) {
|
|
1029
|
+
throw new CloakError(
|
|
1030
|
+
"Invalid note format: missing required fields",
|
|
1031
|
+
"validation",
|
|
1032
|
+
false
|
|
1033
|
+
);
|
|
1034
|
+
}
|
|
1035
|
+
if (!/^[0-9a-f]{64}$/i.test(note.commitment)) {
|
|
1036
|
+
throw new CloakError("Invalid commitment format", "validation", false);
|
|
1037
|
+
}
|
|
1038
|
+
if (!/^[0-9a-f]{64}$/i.test(note.sk_spend)) {
|
|
1039
|
+
throw new CloakError("Invalid sk_spend format", "validation", false);
|
|
1040
|
+
}
|
|
1041
|
+
if (!/^[0-9a-f]{64}$/i.test(note.r)) {
|
|
1042
|
+
throw new CloakError("Invalid r format", "validation", false);
|
|
1043
|
+
}
|
|
1044
|
+
return note;
|
|
1045
|
+
}
|
|
1046
|
+
function exportNote(note, pretty = false) {
|
|
1047
|
+
return pretty ? JSON.stringify(note, null, 2) : JSON.stringify(note);
|
|
1048
|
+
}
|
|
1049
|
+
function isWithdrawable(note) {
|
|
1050
|
+
return !!(note.depositSignature && note.leafIndex !== void 0 && note.root);
|
|
1051
|
+
}
|
|
1052
|
+
function updateNoteWithDeposit(note, depositInfo) {
|
|
1053
|
+
const updated = {
|
|
1054
|
+
...note,
|
|
1055
|
+
depositSignature: depositInfo.signature,
|
|
1056
|
+
depositSlot: depositInfo.slot,
|
|
1057
|
+
leafIndex: depositInfo.leafIndex,
|
|
1058
|
+
root: depositInfo.root
|
|
1059
|
+
};
|
|
1060
|
+
if (depositInfo.merkleProof) {
|
|
1061
|
+
updated.merkleProof = depositInfo.merkleProof;
|
|
1062
|
+
}
|
|
1063
|
+
return updated;
|
|
1064
|
+
}
|
|
1065
|
+
function findNoteByCommitment(notes, commitment) {
|
|
1066
|
+
return notes.find((n) => n.commitment === commitment);
|
|
1067
|
+
}
|
|
1068
|
+
function filterNotesByNetwork(notes, network) {
|
|
1069
|
+
return notes.filter((n) => n.network === network);
|
|
1070
|
+
}
|
|
1071
|
+
function filterWithdrawableNotes(notes) {
|
|
1072
|
+
return notes.filter(isWithdrawable);
|
|
1073
|
+
}
|
|
1074
|
+
function exportWalletKeys(keys) {
|
|
1075
|
+
return exportKeys(keys);
|
|
1076
|
+
}
|
|
1077
|
+
function importWalletKeys(keysJson) {
|
|
1078
|
+
return importKeys(keysJson);
|
|
1079
|
+
}
|
|
1080
|
+
function getPublicViewKey(keys) {
|
|
1081
|
+
return keys.view.pvk_hex;
|
|
1082
|
+
}
|
|
1083
|
+
function getViewKey(keys) {
|
|
1084
|
+
return keys.view;
|
|
1085
|
+
}
|
|
1086
|
+
var calculateFee2 = calculateFee;
|
|
1087
|
+
function getDistributableAmount2(amountLamports) {
|
|
1088
|
+
return amountLamports - calculateFee2(amountLamports);
|
|
1089
|
+
}
|
|
1090
|
+
function getRecipientAmount(amountLamports) {
|
|
1091
|
+
return getDistributableAmount2(amountLamports);
|
|
1092
|
+
}
|
|
1093
|
+
|
|
770
1094
|
// src/core/storage.ts
|
|
771
1095
|
var MemoryStorageAdapter = class {
|
|
772
1096
|
constructor() {
|
|
1097
|
+
this.notes = /* @__PURE__ */ new Map();
|
|
773
1098
|
this.keys = null;
|
|
774
1099
|
}
|
|
1100
|
+
saveNote(note) {
|
|
1101
|
+
this.notes.set(note.commitment, note);
|
|
1102
|
+
}
|
|
1103
|
+
loadAllNotes() {
|
|
1104
|
+
return Array.from(this.notes.values());
|
|
1105
|
+
}
|
|
1106
|
+
updateNote(commitment, updates) {
|
|
1107
|
+
const existing = this.notes.get(commitment);
|
|
1108
|
+
if (existing) {
|
|
1109
|
+
this.notes.set(commitment, { ...existing, ...updates });
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
deleteNote(commitment) {
|
|
1113
|
+
this.notes.delete(commitment);
|
|
1114
|
+
}
|
|
1115
|
+
clearAllNotes() {
|
|
1116
|
+
this.notes.clear();
|
|
1117
|
+
}
|
|
775
1118
|
saveKeys(keys) {
|
|
776
1119
|
this.keys = keys;
|
|
777
1120
|
}
|
|
@@ -782,10 +1125,10 @@ var MemoryStorageAdapter = class {
|
|
|
782
1125
|
this.keys = null;
|
|
783
1126
|
}
|
|
784
1127
|
};
|
|
785
|
-
var
|
|
786
|
-
constructor(keysKey = "cloak_wallet_keys") {
|
|
1128
|
+
var LocalStorageAdapter = class {
|
|
1129
|
+
constructor(notesKey = "cloak_notes", keysKey = "cloak_wallet_keys") {
|
|
1130
|
+
this.notesKey = notesKey;
|
|
787
1131
|
this.keysKey = keysKey;
|
|
788
|
-
_LocalStorageAdapter.runLegacyPurgeOnce();
|
|
789
1132
|
}
|
|
790
1133
|
getStorage() {
|
|
791
1134
|
if (typeof globalThis !== "undefined" && globalThis.localStorage) {
|
|
@@ -793,6 +1136,47 @@ var _LocalStorageAdapter = class _LocalStorageAdapter {
|
|
|
793
1136
|
}
|
|
794
1137
|
return null;
|
|
795
1138
|
}
|
|
1139
|
+
saveNote(note) {
|
|
1140
|
+
const storage = this.getStorage();
|
|
1141
|
+
if (!storage) throw new Error("localStorage not available");
|
|
1142
|
+
const notes = this.loadAllNotes();
|
|
1143
|
+
notes.push(note);
|
|
1144
|
+
storage.setItem(this.notesKey, JSON.stringify(notes));
|
|
1145
|
+
}
|
|
1146
|
+
loadAllNotes() {
|
|
1147
|
+
const storage = this.getStorage();
|
|
1148
|
+
if (!storage) return [];
|
|
1149
|
+
const stored = storage.getItem(this.notesKey);
|
|
1150
|
+
if (!stored) return [];
|
|
1151
|
+
try {
|
|
1152
|
+
return JSON.parse(stored);
|
|
1153
|
+
} catch {
|
|
1154
|
+
return [];
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
updateNote(commitment, updates) {
|
|
1158
|
+
const storage = this.getStorage();
|
|
1159
|
+
if (!storage) return;
|
|
1160
|
+
const notes = this.loadAllNotes();
|
|
1161
|
+
const index = notes.findIndex((n) => n.commitment === commitment);
|
|
1162
|
+
if (index !== -1) {
|
|
1163
|
+
notes[index] = { ...notes[index], ...updates };
|
|
1164
|
+
storage.setItem(this.notesKey, JSON.stringify(notes));
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
deleteNote(commitment) {
|
|
1168
|
+
const storage = this.getStorage();
|
|
1169
|
+
if (!storage) return;
|
|
1170
|
+
const notes = this.loadAllNotes();
|
|
1171
|
+
const filtered = notes.filter((n) => n.commitment !== commitment);
|
|
1172
|
+
storage.setItem(this.notesKey, JSON.stringify(filtered));
|
|
1173
|
+
}
|
|
1174
|
+
clearAllNotes() {
|
|
1175
|
+
const storage = this.getStorage();
|
|
1176
|
+
if (storage) {
|
|
1177
|
+
storage.removeItem(this.notesKey);
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
796
1180
|
saveKeys(keys) {
|
|
797
1181
|
const storage = this.getStorage();
|
|
798
1182
|
if (!storage) throw new Error("localStorage not available");
|
|
@@ -815,64 +1199,126 @@ var _LocalStorageAdapter = class _LocalStorageAdapter {
|
|
|
815
1199
|
storage.removeItem(this.keysKey);
|
|
816
1200
|
}
|
|
817
1201
|
}
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
1202
|
+
};
|
|
1203
|
+
|
|
1204
|
+
// src/utils/validation.ts
|
|
1205
|
+
var import_web3 = require("@solana/web3.js");
|
|
1206
|
+
function isValidSolanaAddress(address) {
|
|
1207
|
+
try {
|
|
1208
|
+
new import_web3.PublicKey(address);
|
|
1209
|
+
return true;
|
|
1210
|
+
} catch {
|
|
1211
|
+
return false;
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
function validateNote(note) {
|
|
1215
|
+
if (!note || typeof note !== "object") {
|
|
1216
|
+
throw new Error("Note must be an object");
|
|
1217
|
+
}
|
|
1218
|
+
const requiredFields = ["version", "amount", "commitment", "sk_spend", "r", "timestamp", "network"];
|
|
1219
|
+
for (const field of requiredFields) {
|
|
1220
|
+
if (!(field in note)) {
|
|
1221
|
+
throw new Error(`Missing required field: ${field}`);
|
|
827
1222
|
}
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
1223
|
+
}
|
|
1224
|
+
if (typeof note.version !== "string") {
|
|
1225
|
+
throw new Error("Version must be a string");
|
|
1226
|
+
}
|
|
1227
|
+
if (typeof note.amount !== "number" || note.amount <= 0) {
|
|
1228
|
+
throw new Error("Amount must be a positive number");
|
|
1229
|
+
}
|
|
1230
|
+
if (!isValidHex(note.commitment, 32)) {
|
|
1231
|
+
throw new Error("Invalid commitment format (expected 64 hex characters)");
|
|
1232
|
+
}
|
|
1233
|
+
if (!isValidHex(note.sk_spend, 32)) {
|
|
1234
|
+
throw new Error("Invalid sk_spend format (expected 64 hex characters)");
|
|
1235
|
+
}
|
|
1236
|
+
if (!isValidHex(note.r, 32)) {
|
|
1237
|
+
throw new Error("Invalid r format (expected 64 hex characters)");
|
|
1238
|
+
}
|
|
1239
|
+
if (typeof note.timestamp !== "number" || note.timestamp <= 0) {
|
|
1240
|
+
throw new Error("Timestamp must be a positive number");
|
|
1241
|
+
}
|
|
1242
|
+
if (!["localnet", "devnet", "testnet", "mainnet"].includes(note.network)) {
|
|
1243
|
+
throw new Error("Network must be localnet, devnet, testnet, or mainnet");
|
|
1244
|
+
}
|
|
1245
|
+
if (note.depositSignature !== void 0 && typeof note.depositSignature !== "string") {
|
|
1246
|
+
throw new Error("Deposit signature must be a string");
|
|
1247
|
+
}
|
|
1248
|
+
if (note.depositSlot !== void 0 && typeof note.depositSlot !== "number") {
|
|
1249
|
+
throw new Error("Deposit slot must be a number");
|
|
1250
|
+
}
|
|
1251
|
+
if (note.leafIndex !== void 0) {
|
|
1252
|
+
if (typeof note.leafIndex !== "number" || note.leafIndex < 0) {
|
|
1253
|
+
throw new Error("Leaf index must be a non-negative number");
|
|
831
1254
|
}
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
1255
|
+
}
|
|
1256
|
+
if (note.root !== void 0 && !isValidHex(note.root, 32)) {
|
|
1257
|
+
throw new Error("Invalid root format (expected 64 hex characters)");
|
|
1258
|
+
}
|
|
1259
|
+
if (note.merkleProof !== void 0) {
|
|
1260
|
+
if (!Array.isArray(note.merkleProof.pathElements)) {
|
|
1261
|
+
throw new Error("Merkle proof pathElements must be an array");
|
|
1262
|
+
}
|
|
1263
|
+
if (!Array.isArray(note.merkleProof.pathIndices)) {
|
|
1264
|
+
throw new Error("Merkle proof pathIndices must be an array");
|
|
1265
|
+
}
|
|
1266
|
+
if (note.merkleProof.pathElements.length !== note.merkleProof.pathIndices.length) {
|
|
1267
|
+
throw new Error("Merkle proof pathElements and pathIndices must have same length");
|
|
836
1268
|
}
|
|
837
1269
|
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
1270
|
+
}
|
|
1271
|
+
function parseNote2(jsonString) {
|
|
1272
|
+
let parsed;
|
|
1273
|
+
try {
|
|
1274
|
+
parsed = JSON.parse(jsonString);
|
|
1275
|
+
} catch (error) {
|
|
1276
|
+
throw new Error("Invalid JSON format");
|
|
1277
|
+
}
|
|
1278
|
+
validateNote(parsed);
|
|
1279
|
+
return parsed;
|
|
1280
|
+
}
|
|
1281
|
+
function validateWithdrawableNote(note) {
|
|
1282
|
+
if (!note.depositSignature) {
|
|
1283
|
+
throw new Error("Note must be deposited before withdrawal (missing depositSignature)");
|
|
1284
|
+
}
|
|
1285
|
+
if (note.leafIndex === void 0) {
|
|
1286
|
+
throw new Error("Note must be deposited before withdrawal (missing leafIndex)");
|
|
1287
|
+
}
|
|
1288
|
+
if (!note.root) {
|
|
1289
|
+
throw new Error("Note must have historical root for withdrawal");
|
|
1290
|
+
}
|
|
1291
|
+
if (!note.merkleProof) {
|
|
1292
|
+
throw new Error("Note must have Merkle proof for withdrawal");
|
|
1293
|
+
}
|
|
1294
|
+
if (note.merkleProof.pathElements.length === 0) {
|
|
1295
|
+
throw new Error("Merkle proof is empty");
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
function validateTransfers(recipients, totalAmount) {
|
|
1299
|
+
if (recipients.length === 0) {
|
|
1300
|
+
throw new Error("At least one recipient is required");
|
|
1301
|
+
}
|
|
1302
|
+
if (recipients.length > 5) {
|
|
1303
|
+
throw new Error("Maximum 5 recipients allowed");
|
|
1304
|
+
}
|
|
1305
|
+
for (let i = 0; i < recipients.length; i++) {
|
|
1306
|
+
const transfer2 = recipients[i];
|
|
1307
|
+
const isPublicKeyLike = transfer2.recipient && typeof transfer2.recipient.toBase58 === "function" && typeof transfer2.recipient.toBuffer === "function";
|
|
1308
|
+
if (!isPublicKeyLike) {
|
|
1309
|
+
throw new Error(`Recipient ${i} must be a PublicKey (got ${typeof transfer2.recipient})`);
|
|
1310
|
+
}
|
|
1311
|
+
if (typeof transfer2.amount !== "number" || transfer2.amount <= 0) {
|
|
1312
|
+
throw new Error(`Recipient ${i} amount must be a positive number`);
|
|
858
1313
|
}
|
|
859
|
-
globalThis.localStorage.removeItem(notesKey);
|
|
860
1314
|
}
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
* Default localStorage key the legacy `<= 0.1.5` SDK used for the
|
|
869
|
-
* plaintext `CloakNote[]` blob.
|
|
870
|
-
*/
|
|
871
|
-
_LocalStorageAdapter.LEGACY_NOTES_KEY = "cloak_notes";
|
|
872
|
-
var LocalStorageAdapter = _LocalStorageAdapter;
|
|
873
|
-
|
|
874
|
-
// src/services/RelayService.ts
|
|
875
|
-
var import_sha2 = require("@noble/hashes/sha2");
|
|
1315
|
+
const sum = recipients.reduce((acc, t) => acc + t.amount, 0);
|
|
1316
|
+
if (sum !== totalAmount) {
|
|
1317
|
+
throw new Error(
|
|
1318
|
+
`Recipients sum (${sum}) does not match expected total (${totalAmount})`
|
|
1319
|
+
);
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
876
1322
|
|
|
877
1323
|
// src/utils/errors.ts
|
|
878
1324
|
var RootNotFoundError = class extends Error {
|
|
@@ -932,7 +1378,8 @@ function classifyRelayError(responseText, httpStatus) {
|
|
|
932
1378
|
inner = responseText;
|
|
933
1379
|
}
|
|
934
1380
|
const haystack = inner.toLowerCase();
|
|
935
|
-
if (haystack.includes("0x1020") ||
|
|
1381
|
+
if (haystack.includes("0x1020") || // 0x1020 as decimal in a JSON-stringified confirmTransaction err (`"Custom":4128`).
|
|
1382
|
+
haystack.includes('"custom":4128') || haystack.includes("doublespend")) {
|
|
936
1383
|
return new UtxoAlreadySpentError(
|
|
937
1384
|
"This shielded balance was already spent. The UTXO's nullifier is registered on-chain \u2014 typically means you spent it via another session or device. Run verifyUtxos() to reconcile local state."
|
|
938
1385
|
);
|
|
@@ -1768,6 +2215,7 @@ function formatErrorForLogging(error) {
|
|
|
1768
2215
|
}
|
|
1769
2216
|
|
|
1770
2217
|
// src/services/RelayService.ts
|
|
2218
|
+
var import_sha2 = require("@noble/hashes/sha2");
|
|
1771
2219
|
var RelayService = class {
|
|
1772
2220
|
/**
|
|
1773
2221
|
* Create a new Relay Service client
|
|
@@ -2136,24 +2584,88 @@ var RelayService = class {
|
|
|
2136
2584
|
}
|
|
2137
2585
|
};
|
|
2138
2586
|
|
|
2139
|
-
// src/
|
|
2587
|
+
// src/solana/instructions.ts
|
|
2140
2588
|
var import_web32 = require("@solana/web3.js");
|
|
2589
|
+
function createDepositInstruction(params) {
|
|
2590
|
+
if (params.commitment.length !== 32) {
|
|
2591
|
+
throw new Error(
|
|
2592
|
+
`Invalid commitment length: ${params.commitment.length} (expected 32 bytes)`
|
|
2593
|
+
);
|
|
2594
|
+
}
|
|
2595
|
+
if (params.amount <= 0) {
|
|
2596
|
+
throw new Error("Amount must be positive");
|
|
2597
|
+
}
|
|
2598
|
+
const discriminant = new Uint8Array([1]);
|
|
2599
|
+
const amountBytes = new Uint8Array(8);
|
|
2600
|
+
new DataView(amountBytes.buffer).setBigUint64(
|
|
2601
|
+
0,
|
|
2602
|
+
BigInt(params.amount),
|
|
2603
|
+
true
|
|
2604
|
+
// little-endian
|
|
2605
|
+
);
|
|
2606
|
+
const data = new Uint8Array(41);
|
|
2607
|
+
data.set(discriminant, 0);
|
|
2608
|
+
data.set(amountBytes, 1);
|
|
2609
|
+
data.set(params.commitment, 9);
|
|
2610
|
+
return new import_web32.TransactionInstruction({
|
|
2611
|
+
programId: params.programId,
|
|
2612
|
+
keys: [
|
|
2613
|
+
// Account 0: Payer (signer, writable) - pays for transaction
|
|
2614
|
+
{ pubkey: params.payer, isSigner: true, isWritable: true },
|
|
2615
|
+
// Account 1: Pool (writable) - receives SOL
|
|
2616
|
+
{ pubkey: params.pool, isSigner: false, isWritable: true },
|
|
2617
|
+
// Account 2: System Program (readonly) - for transfers
|
|
2618
|
+
{ pubkey: import_web32.SystemProgram.programId, isSigner: false, isWritable: false },
|
|
2619
|
+
// Account 3: Merkle Tree (writable) - stores on-chain Merkle tree
|
|
2620
|
+
{ pubkey: params.merkleTree, isSigner: false, isWritable: true }
|
|
2621
|
+
],
|
|
2622
|
+
data: Buffer.from(data)
|
|
2623
|
+
});
|
|
2624
|
+
}
|
|
2625
|
+
function validateDepositParams(params) {
|
|
2626
|
+
if (!(params.programId instanceof import_web32.PublicKey)) {
|
|
2627
|
+
throw new Error("programId must be a PublicKey");
|
|
2628
|
+
}
|
|
2629
|
+
if (!(params.payer instanceof import_web32.PublicKey)) {
|
|
2630
|
+
throw new Error("payer must be a PublicKey");
|
|
2631
|
+
}
|
|
2632
|
+
if (!(params.pool instanceof import_web32.PublicKey)) {
|
|
2633
|
+
throw new Error("pool must be a PublicKey");
|
|
2634
|
+
}
|
|
2635
|
+
if (!(params.merkleTree instanceof import_web32.PublicKey)) {
|
|
2636
|
+
throw new Error("merkleTree must be a PublicKey");
|
|
2637
|
+
}
|
|
2638
|
+
if (typeof params.amount !== "number" || params.amount <= 0) {
|
|
2639
|
+
throw new Error("amount must be a positive number");
|
|
2640
|
+
}
|
|
2641
|
+
if (!(params.commitment instanceof Uint8Array)) {
|
|
2642
|
+
throw new Error("commitment must be a Uint8Array");
|
|
2643
|
+
}
|
|
2644
|
+
if (params.commitment.length !== 32) {
|
|
2645
|
+
throw new Error(
|
|
2646
|
+
`commitment must be 32 bytes (got ${params.commitment.length})`
|
|
2647
|
+
);
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2651
|
+
// src/utils/pda.ts
|
|
2652
|
+
var import_web34 = require("@solana/web3.js");
|
|
2141
2653
|
init_utxo();
|
|
2142
2654
|
function getShieldPoolPDAs(programId, mint = NATIVE_SOL_MINT) {
|
|
2143
2655
|
const pid = programId || CLOAK_PROGRAM_ID;
|
|
2144
|
-
const [pool] =
|
|
2656
|
+
const [pool] = import_web34.PublicKey.findProgramAddressSync(
|
|
2145
2657
|
[Buffer.from("pool"), mint.toBuffer()],
|
|
2146
2658
|
pid
|
|
2147
2659
|
);
|
|
2148
|
-
const [merkleTree] =
|
|
2660
|
+
const [merkleTree] = import_web34.PublicKey.findProgramAddressSync(
|
|
2149
2661
|
[Buffer.from("merkle_tree"), mint.toBuffer()],
|
|
2150
2662
|
pid
|
|
2151
2663
|
);
|
|
2152
|
-
const [treasury] =
|
|
2664
|
+
const [treasury] = import_web34.PublicKey.findProgramAddressSync(
|
|
2153
2665
|
[Buffer.from("treasury"), mint.toBuffer()],
|
|
2154
2666
|
pid
|
|
2155
2667
|
);
|
|
2156
|
-
const [vaultAuthority] =
|
|
2668
|
+
const [vaultAuthority] = import_web34.PublicKey.findProgramAddressSync(
|
|
2157
2669
|
[Buffer.from("vault_authority"), mint.toBuffer()],
|
|
2158
2670
|
pid
|
|
2159
2671
|
);
|
|
@@ -2169,7 +2681,7 @@ function getNullifierPDA(poolPubkey, nullifier, programId) {
|
|
|
2169
2681
|
if (nullifier.length !== 32) {
|
|
2170
2682
|
throw new Error(`Nullifier must be 32 bytes, got ${nullifier.length}`);
|
|
2171
2683
|
}
|
|
2172
|
-
return
|
|
2684
|
+
return import_web34.PublicKey.findProgramAddressSync(
|
|
2173
2685
|
[Buffer.from("nullifier"), poolPubkey.toBuffer(), Buffer.from(nullifier)],
|
|
2174
2686
|
pid
|
|
2175
2687
|
);
|
|
@@ -2179,29 +2691,273 @@ function getSwapStatePDA(poolPubkey, nullifier, programId) {
|
|
|
2179
2691
|
if (nullifier.length !== 32) {
|
|
2180
2692
|
throw new Error(`Nullifier must be 32 bytes, got ${nullifier.length}`);
|
|
2181
2693
|
}
|
|
2182
|
-
return
|
|
2694
|
+
return import_web34.PublicKey.findProgramAddressSync(
|
|
2183
2695
|
[Buffer.from("swap_state"), poolPubkey.toBuffer(), Buffer.from(nullifier)],
|
|
2184
2696
|
pid
|
|
2185
2697
|
);
|
|
2186
2698
|
}
|
|
2187
2699
|
|
|
2188
|
-
// src/
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
var
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
}
|
|
2197
|
-
return false;
|
|
2700
|
+
// src/utils/proof-generation.ts
|
|
2701
|
+
var snarkjs = __toESM(require("snarkjs"), 1);
|
|
2702
|
+
var IS_REACT_NATIVE = typeof navigator !== "undefined" && navigator.product === "ReactNative";
|
|
2703
|
+
var IS_BROWSER = IS_REACT_NATIVE || typeof window !== "undefined" || typeof globalThis !== "undefined" && typeof globalThis.document !== "undefined";
|
|
2704
|
+
var DEFAULT_CIRCUITS_URL = "https://storage.googleapis.com/cloak-circuits/circuits/0.1.0";
|
|
2705
|
+
var SKIP_CIRCUIT_VERIFY_ENV = typeof process !== "undefined" && process.env && process.env.CLOAK_SKIP_CIRCUIT_INTEGRITY_CHECK === "1";
|
|
2706
|
+
function isUrl(path) {
|
|
2707
|
+
return path.startsWith("http://") || path.startsWith("https://");
|
|
2198
2708
|
}
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2709
|
+
function resolveCircuitsUrl(circuitsPath2) {
|
|
2710
|
+
if (circuitsPath2.trim() !== "" && circuitsPath2.trim() !== DEFAULT_CIRCUITS_URL) {
|
|
2711
|
+
return DEFAULT_CIRCUITS_URL;
|
|
2712
|
+
}
|
|
2713
|
+
return DEFAULT_CIRCUITS_URL;
|
|
2202
2714
|
}
|
|
2203
|
-
function
|
|
2204
|
-
|
|
2715
|
+
async function fetchFileAsBuffer(url) {
|
|
2716
|
+
const response = await fetch(url);
|
|
2717
|
+
if (!response.ok) {
|
|
2718
|
+
throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
|
|
2719
|
+
}
|
|
2720
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
2721
|
+
return new Uint8Array(arrayBuffer);
|
|
2722
|
+
}
|
|
2723
|
+
async function sha256Hex(data) {
|
|
2724
|
+
if (IS_BROWSER) {
|
|
2725
|
+
const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
2726
|
+
const buffer = bytes.buffer.slice(
|
|
2727
|
+
bytes.byteOffset,
|
|
2728
|
+
bytes.byteOffset + bytes.byteLength
|
|
2729
|
+
);
|
|
2730
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", buffer);
|
|
2731
|
+
return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2732
|
+
}
|
|
2733
|
+
const nodeCrypto = await getNodeCrypto();
|
|
2734
|
+
return nodeCrypto.createHash("sha256").update(data).digest("hex");
|
|
2735
|
+
}
|
|
2736
|
+
var _nodeCrypto = null;
|
|
2737
|
+
function nodeRequire(moduleName) {
|
|
2738
|
+
if (IS_BROWSER) {
|
|
2739
|
+
throw new Error(`Node.js ${moduleName} module not available in browser/React Native`);
|
|
2740
|
+
}
|
|
2741
|
+
try {
|
|
2742
|
+
const requireFunc = new Function("moduleName", "return require(moduleName)");
|
|
2743
|
+
return requireFunc(moduleName);
|
|
2744
|
+
} catch {
|
|
2745
|
+
throw new Error(`Cannot load Node.js module ${moduleName} synchronously in ESM.`);
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
async function getNodeCrypto() {
|
|
2749
|
+
if (_nodeCrypto) return _nodeCrypto;
|
|
2750
|
+
_nodeCrypto = nodeRequire("crypto");
|
|
2751
|
+
return _nodeCrypto;
|
|
2752
|
+
}
|
|
2753
|
+
async function generateWithdrawRegularProof(inputs, circuitsPath2) {
|
|
2754
|
+
const circuitsUrl = resolveCircuitsUrl(circuitsPath2);
|
|
2755
|
+
let wasmInput;
|
|
2756
|
+
let zkeyInput;
|
|
2757
|
+
if (IS_BROWSER) {
|
|
2758
|
+
wasmInput = `${circuitsUrl}/withdraw_regular_js/withdraw_regular.wasm`;
|
|
2759
|
+
zkeyInput = `${circuitsUrl}/withdraw_regular_final.zkey`;
|
|
2760
|
+
} else {
|
|
2761
|
+
const wasmUrl = `${circuitsUrl}/withdraw_regular_js/withdraw_regular.wasm`;
|
|
2762
|
+
const zkeyUrl = `${circuitsUrl}/withdraw_regular_final.zkey`;
|
|
2763
|
+
const [wasmData, zkeyData] = await Promise.all([
|
|
2764
|
+
fetchFileAsBuffer(wasmUrl),
|
|
2765
|
+
fetchFileAsBuffer(zkeyUrl)
|
|
2766
|
+
]);
|
|
2767
|
+
wasmInput = wasmData;
|
|
2768
|
+
zkeyInput = zkeyData;
|
|
2769
|
+
}
|
|
2770
|
+
const verification = await verifyCircuitIntegrity(circuitsUrl, "withdraw_regular");
|
|
2771
|
+
if (!verification.valid) {
|
|
2772
|
+
throw new Error(
|
|
2773
|
+
`withdraw_regular circuit integrity verification failed: ${verification.error ?? "hash mismatch"}`
|
|
2774
|
+
);
|
|
2775
|
+
}
|
|
2776
|
+
const circuitInputs = {
|
|
2777
|
+
// Public signals
|
|
2778
|
+
root: inputs.root.toString(),
|
|
2779
|
+
nullifier: inputs.nullifier.toString(),
|
|
2780
|
+
outputs_hash: inputs.outputs_hash.toString(),
|
|
2781
|
+
public_amount: inputs.public_amount.toString(),
|
|
2782
|
+
// Private common inputs
|
|
2783
|
+
amount: inputs.amount.toString(),
|
|
2784
|
+
leaf_index: inputs.leaf_index.toString(),
|
|
2785
|
+
sk: inputs.sk.map((x) => x.toString()),
|
|
2786
|
+
r: inputs.r.map((x) => x.toString()),
|
|
2787
|
+
pathElements: inputs.pathElements.map((x) => x.toString()),
|
|
2788
|
+
pathIndices: inputs.pathIndices,
|
|
2789
|
+
// Outputs
|
|
2790
|
+
num_outputs: inputs.num_outputs,
|
|
2791
|
+
out_addr: inputs.out_addr.map((addr) => addr.map((x) => x.toString())),
|
|
2792
|
+
out_amount: inputs.out_amount.map((x) => x.toString()),
|
|
2793
|
+
out_flags: inputs.out_flags,
|
|
2794
|
+
// Fee calculation
|
|
2795
|
+
var_fee: inputs.var_fee.toString(),
|
|
2796
|
+
rem: inputs.rem.toString()
|
|
2797
|
+
};
|
|
2798
|
+
const { proof, publicSignals } = await snarkjs.groth16.fullProve(circuitInputs, wasmInput, zkeyInput);
|
|
2799
|
+
const proofBytes = proofToBytes(proof);
|
|
2800
|
+
return {
|
|
2801
|
+
proof,
|
|
2802
|
+
publicSignals,
|
|
2803
|
+
proofBytes,
|
|
2804
|
+
publicInputsBytes: new Uint8Array(0)
|
|
2805
|
+
// Not used, kept for compatibility
|
|
2806
|
+
};
|
|
2807
|
+
}
|
|
2808
|
+
async function generateWithdrawSwapProof(inputs, circuitsPath2) {
|
|
2809
|
+
const circuitsUrl = resolveCircuitsUrl(circuitsPath2);
|
|
2810
|
+
let wasmInput;
|
|
2811
|
+
let zkeyInput;
|
|
2812
|
+
if (IS_BROWSER) {
|
|
2813
|
+
wasmInput = `${circuitsUrl}/withdraw_swap_js/withdraw_swap.wasm`;
|
|
2814
|
+
zkeyInput = `${circuitsUrl}/withdraw_swap_final.zkey`;
|
|
2815
|
+
} else {
|
|
2816
|
+
const wasmUrl = `${circuitsUrl}/withdraw_swap_js/withdraw_swap.wasm`;
|
|
2817
|
+
const zkeyUrl = `${circuitsUrl}/withdraw_swap_final.zkey`;
|
|
2818
|
+
const [wasmData, zkeyData] = await Promise.all([
|
|
2819
|
+
fetchFileAsBuffer(wasmUrl),
|
|
2820
|
+
fetchFileAsBuffer(zkeyUrl)
|
|
2821
|
+
]);
|
|
2822
|
+
wasmInput = wasmData;
|
|
2823
|
+
zkeyInput = zkeyData;
|
|
2824
|
+
}
|
|
2825
|
+
const verification = await verifyCircuitIntegrity(circuitsUrl, "withdraw_swap");
|
|
2826
|
+
if (!verification.valid) {
|
|
2827
|
+
throw new Error(
|
|
2828
|
+
`withdraw_swap circuit integrity verification failed: ${verification.error ?? "hash mismatch"}`
|
|
2829
|
+
);
|
|
2830
|
+
}
|
|
2831
|
+
const sk = splitTo2Limbs(inputs.sk_spend);
|
|
2832
|
+
const r = splitTo2Limbs(inputs.r);
|
|
2833
|
+
const circuitInputs = {
|
|
2834
|
+
// Public signals (must be provided for witness generation)
|
|
2835
|
+
root: inputs.root.toString(),
|
|
2836
|
+
nullifier: inputs.nullifier.toString(),
|
|
2837
|
+
outputs_hash: inputs.outputs_hash.toString(),
|
|
2838
|
+
public_amount: inputs.public_amount.toString(),
|
|
2839
|
+
// Private inputs
|
|
2840
|
+
sk: sk.map((x) => x.toString()),
|
|
2841
|
+
r: r.map((x) => x.toString()),
|
|
2842
|
+
amount: inputs.amount.toString(),
|
|
2843
|
+
leaf_index: inputs.leaf_index.toString(),
|
|
2844
|
+
pathElements: inputs.path_elements.map((x) => x.toString()),
|
|
2845
|
+
pathIndices: inputs.path_indices,
|
|
2846
|
+
input_mint: inputs.input_mint.map((x) => x.toString()),
|
|
2847
|
+
output_mint: inputs.output_mint.map((x) => x.toString()),
|
|
2848
|
+
recipient_ata: inputs.recipient_ata.map((x) => x.toString()),
|
|
2849
|
+
min_output_amount: inputs.min_output_amount.toString(),
|
|
2850
|
+
// Note: fee is computed off-chain and validated on-chain:
|
|
2851
|
+
// - fixed_fee is 5000000 (0.005 SOL)
|
|
2852
|
+
// - var_fee and rem are computed from amount * 3 / 1000 (0.3%)
|
|
2853
|
+
var_fee: inputs.var_fee.toString(),
|
|
2854
|
+
rem: inputs.rem.toString()
|
|
2855
|
+
};
|
|
2856
|
+
const { proof, publicSignals } = await snarkjs.groth16.fullProve(
|
|
2857
|
+
circuitInputs,
|
|
2858
|
+
wasmInput,
|
|
2859
|
+
zkeyInput
|
|
2860
|
+
);
|
|
2861
|
+
const proofBytes = proofToBytes(proof);
|
|
2862
|
+
return {
|
|
2863
|
+
proof,
|
|
2864
|
+
publicSignals,
|
|
2865
|
+
proofBytes,
|
|
2866
|
+
publicInputsBytes: new Uint8Array(0)
|
|
2867
|
+
// Not used, kept for compatibility
|
|
2868
|
+
};
|
|
2869
|
+
}
|
|
2870
|
+
async function areCircuitsAvailable(circuitsPath2) {
|
|
2871
|
+
if (!circuitsPath2 || circuitsPath2 === "") {
|
|
2872
|
+
return false;
|
|
2873
|
+
}
|
|
2874
|
+
return isUrl(resolveCircuitsUrl(circuitsPath2));
|
|
2875
|
+
}
|
|
2876
|
+
async function getDefaultCircuitsPath() {
|
|
2877
|
+
return DEFAULT_CIRCUITS_URL;
|
|
2878
|
+
}
|
|
2879
|
+
var EXPECTED_CIRCUIT_HASHES = {
|
|
2880
|
+
withdraw_regular_wasm: "b80c364c926332111945ef437b07a71720fae774e08206cb11b6171f2a2420a3",
|
|
2881
|
+
withdraw_regular_zkey: "6dbca2612f7cc257b897d93384ae9dd09c1617fd1668f8287efb1b124488d144",
|
|
2882
|
+
withdraw_swap_wasm: "a296badc0047c211a4724c96a4e64a77cd75fbde7cfa4cf23af3640a723e5686",
|
|
2883
|
+
withdraw_swap_zkey: "95251954feaac80396651d12a0df3288630fa75db9410aebecb8aaee17a4f6ea"
|
|
2884
|
+
};
|
|
2885
|
+
async function verifyCircuitIntegrity(circuitsPath2, circuit) {
|
|
2886
|
+
const circuitsUrl = resolveCircuitsUrl(circuitsPath2);
|
|
2887
|
+
if (SKIP_CIRCUIT_VERIFY_ENV) {
|
|
2888
|
+
return {
|
|
2889
|
+
valid: true,
|
|
2890
|
+
circuit,
|
|
2891
|
+
error: "Verification skipped (CLOAK_SKIP_CIRCUIT_INTEGRITY_CHECK=1)"
|
|
2892
|
+
};
|
|
2893
|
+
}
|
|
2894
|
+
const expected = circuit === "withdraw_regular" ? {
|
|
2895
|
+
wasm: EXPECTED_CIRCUIT_HASHES.withdraw_regular_wasm,
|
|
2896
|
+
zkey: EXPECTED_CIRCUIT_HASHES.withdraw_regular_zkey
|
|
2897
|
+
} : {
|
|
2898
|
+
wasm: EXPECTED_CIRCUIT_HASHES.withdraw_swap_wasm,
|
|
2899
|
+
zkey: EXPECTED_CIRCUIT_HASHES.withdraw_swap_zkey
|
|
2900
|
+
};
|
|
2901
|
+
const wasmPath = `${circuitsUrl}/${circuit}_js/${circuit}.wasm`;
|
|
2902
|
+
const zkeyPath = `${circuitsUrl}/${circuit}_final.zkey`;
|
|
2903
|
+
try {
|
|
2904
|
+
const [wasmBytes, zkeyBytes] = await Promise.all([
|
|
2905
|
+
fetchFileAsBuffer(wasmPath),
|
|
2906
|
+
fetchFileAsBuffer(zkeyPath)
|
|
2907
|
+
]);
|
|
2908
|
+
const computed = {
|
|
2909
|
+
wasm: await sha256Hex(wasmBytes),
|
|
2910
|
+
zkey: await sha256Hex(zkeyBytes)
|
|
2911
|
+
};
|
|
2912
|
+
if (computed.wasm === expected.wasm && computed.zkey === expected.zkey) {
|
|
2913
|
+
return {
|
|
2914
|
+
valid: true,
|
|
2915
|
+
circuit,
|
|
2916
|
+
computed,
|
|
2917
|
+
expected
|
|
2918
|
+
};
|
|
2919
|
+
}
|
|
2920
|
+
return {
|
|
2921
|
+
valid: false,
|
|
2922
|
+
circuit,
|
|
2923
|
+
error: `Circuit artifact hash mismatch for ${circuit}`,
|
|
2924
|
+
computed,
|
|
2925
|
+
expected
|
|
2926
|
+
};
|
|
2927
|
+
} catch (error) {
|
|
2928
|
+
return {
|
|
2929
|
+
valid: false,
|
|
2930
|
+
circuit,
|
|
2931
|
+
error: `Circuit verification failed: ${error instanceof Error ? error.message : String(error)}`
|
|
2932
|
+
};
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
async function verifyAllCircuits(circuitsPath2) {
|
|
2936
|
+
const circuitsUrl = resolveCircuitsUrl(circuitsPath2);
|
|
2937
|
+
const results = await Promise.all([
|
|
2938
|
+
verifyCircuitIntegrity(circuitsUrl, "withdraw_regular"),
|
|
2939
|
+
verifyCircuitIntegrity(circuitsUrl, "withdraw_swap")
|
|
2940
|
+
]);
|
|
2941
|
+
return results;
|
|
2942
|
+
}
|
|
2943
|
+
|
|
2944
|
+
// src/core/CloakSDK.ts
|
|
2945
|
+
init_utxo();
|
|
2946
|
+
|
|
2947
|
+
// src/utils/logger.ts
|
|
2948
|
+
var globalDebugEnabled = false;
|
|
2949
|
+
function checkEnvDebug() {
|
|
2950
|
+
if (typeof process !== "undefined" && process.env) {
|
|
2951
|
+
return !!(process.env.CLOAK_DEBUG === "1" || process.env.CLOAK_DEBUG === "true" || process.env.DEBUG?.includes("cloak"));
|
|
2952
|
+
}
|
|
2953
|
+
return false;
|
|
2954
|
+
}
|
|
2955
|
+
globalDebugEnabled = checkEnvDebug();
|
|
2956
|
+
function setDebugMode(enabled) {
|
|
2957
|
+
globalDebugEnabled = enabled;
|
|
2958
|
+
}
|
|
2959
|
+
function isDebugEnabled() {
|
|
2960
|
+
return globalDebugEnabled;
|
|
2205
2961
|
}
|
|
2206
2962
|
var LOG_COLORS = {
|
|
2207
2963
|
DEBUG: "\x1B[90m",
|
|
@@ -2404,22 +3160,50 @@ async function computeProofInternal(leafIndex, _nextIndex, subtrees) {
|
|
|
2404
3160
|
}
|
|
2405
3161
|
|
|
2406
3162
|
// src/core/CloakSDK.ts
|
|
2407
|
-
var
|
|
3163
|
+
var COMPUTE_BUDGET_PROGRAM_ID = new import_web35.PublicKey("ComputeBudget111111111111111111111111111111");
|
|
3164
|
+
function createSetLoadedAccountsDataSizeLimitInstruction(bytes) {
|
|
3165
|
+
const data = Buffer.alloc(5);
|
|
3166
|
+
data.writeUInt8(4, 0);
|
|
3167
|
+
data.writeUInt32LE(bytes, 1);
|
|
3168
|
+
return new import_web35.TransactionInstruction({
|
|
3169
|
+
keys: [],
|
|
3170
|
+
programId: COMPUTE_BUDGET_PROGRAM_ID,
|
|
3171
|
+
data
|
|
3172
|
+
});
|
|
3173
|
+
}
|
|
3174
|
+
var CLOAK_PROGRAM_ID = new import_web35.PublicKey("zh1eLd6rSphLejbFfJEneUwzHRfMKxgzrgkfwA6qRkW");
|
|
2408
3175
|
var CLOAK_API_URL = "https://api.cloak.ag";
|
|
2409
3176
|
var CloakSDK = class {
|
|
3177
|
+
/**
|
|
3178
|
+
* Create a new Cloak SDK client
|
|
3179
|
+
*
|
|
3180
|
+
* @param config - Client configuration
|
|
3181
|
+
*
|
|
3182
|
+
* @example Node.js mode (with keypair)
|
|
3183
|
+
* ```typescript
|
|
3184
|
+
* const sdk = new CloakSDK({
|
|
3185
|
+
* keypairBytes: keypair.secretKey,
|
|
3186
|
+
* network: "devnet"
|
|
3187
|
+
* });
|
|
3188
|
+
* ```
|
|
3189
|
+
*
|
|
3190
|
+
* @example Browser mode (with wallet adapter)
|
|
3191
|
+
* ```typescript
|
|
3192
|
+
* const sdk = new CloakSDK({
|
|
3193
|
+
* wallet: walletAdapter,
|
|
3194
|
+
* network: "devnet"
|
|
3195
|
+
* });
|
|
3196
|
+
* ```
|
|
3197
|
+
*/
|
|
2410
3198
|
constructor(config) {
|
|
2411
3199
|
if (!config.keypairBytes && !config.wallet) {
|
|
2412
|
-
throw new
|
|
2413
|
-
"Must provide either keypairBytes (Node.js) or wallet (Browser)",
|
|
2414
|
-
"validation",
|
|
2415
|
-
false
|
|
2416
|
-
);
|
|
3200
|
+
throw new Error("Must provide either keypairBytes (Node.js) or wallet (Browser)");
|
|
2417
3201
|
}
|
|
2418
3202
|
if (config.debug) {
|
|
2419
3203
|
setDebugMode(true);
|
|
2420
3204
|
}
|
|
2421
3205
|
if (config.keypairBytes) {
|
|
2422
|
-
this.keypair =
|
|
3206
|
+
this.keypair = import_web35.Keypair.fromSecretKey(config.keypairBytes);
|
|
2423
3207
|
}
|
|
2424
3208
|
this.wallet = config.wallet;
|
|
2425
3209
|
this.cloakKeys = config.cloakKeys;
|
|
@@ -2433,9 +3217,13 @@ var CloakSDK = class {
|
|
|
2433
3217
|
}
|
|
2434
3218
|
}
|
|
2435
3219
|
const programId = config.programId || CLOAK_PROGRAM_ID;
|
|
2436
|
-
const { pool, merkleTree, treasury } = getShieldPoolPDAs(
|
|
3220
|
+
const { pool, merkleTree, treasury } = getShieldPoolPDAs(
|
|
3221
|
+
programId,
|
|
3222
|
+
NATIVE_SOL_MINT
|
|
3223
|
+
);
|
|
2437
3224
|
this.config = {
|
|
2438
3225
|
network: config.network || "mainnet",
|
|
3226
|
+
keypairBytes: config.keypairBytes,
|
|
2439
3227
|
cloakKeys: config.cloakKeys,
|
|
2440
3228
|
programId,
|
|
2441
3229
|
poolAddress: pool,
|
|
@@ -2444,7 +3232,9 @@ var CloakSDK = class {
|
|
|
2444
3232
|
debug: config.debug
|
|
2445
3233
|
};
|
|
2446
3234
|
}
|
|
2447
|
-
/**
|
|
3235
|
+
/**
|
|
3236
|
+
* Get the public key for deposits (from keypair or wallet)
|
|
3237
|
+
*/
|
|
2448
3238
|
getPublicKey() {
|
|
2449
3239
|
if (this.keypair) {
|
|
2450
3240
|
return this.keypair.publicKey;
|
|
@@ -2452,23 +3242,877 @@ var CloakSDK = class {
|
|
|
2452
3242
|
if (this.wallet?.publicKey) {
|
|
2453
3243
|
return this.wallet.publicKey;
|
|
2454
3244
|
}
|
|
2455
|
-
throw new
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
3245
|
+
throw new Error("No public key available - wallet not connected or keypair not provided");
|
|
3246
|
+
}
|
|
3247
|
+
/**
|
|
3248
|
+
* Check if the SDK is using a wallet adapter
|
|
3249
|
+
*/
|
|
3250
|
+
isWalletMode() {
|
|
3251
|
+
return !!this.wallet && !this.keypair;
|
|
3252
|
+
}
|
|
3253
|
+
/**
|
|
3254
|
+
* Deposit SOL into the Cloak protocol
|
|
3255
|
+
*
|
|
3256
|
+
* Creates a new note (or uses a provided one), submits a deposit transaction,
|
|
3257
|
+
* and registers with the indexer.
|
|
3258
|
+
*
|
|
3259
|
+
* @param connection - Solana connection
|
|
3260
|
+
* @param payer - Payer wallet with sendTransaction method
|
|
3261
|
+
* @param amountOrNote - Amount in lamports OR an existing note to deposit
|
|
3262
|
+
* @param options - Optional configuration
|
|
3263
|
+
* @returns Deposit result with note and transaction info
|
|
3264
|
+
*
|
|
3265
|
+
* @example
|
|
3266
|
+
* ```typescript
|
|
3267
|
+
* // Generate and deposit in one step
|
|
3268
|
+
* const result = await client.deposit(
|
|
3269
|
+
* connection,
|
|
3270
|
+
* wallet,
|
|
3271
|
+
* 1_000_000_000,
|
|
3272
|
+
* {
|
|
3273
|
+
* onProgress: (status) => console.log(status)
|
|
3274
|
+
* }
|
|
3275
|
+
* );
|
|
3276
|
+
*
|
|
3277
|
+
* // Or deposit a pre-generated note
|
|
3278
|
+
* const note = client.generateNote(1_000_000_000);
|
|
3279
|
+
* const result = await client.deposit(connection, wallet, note);
|
|
3280
|
+
* ```
|
|
3281
|
+
*/
|
|
3282
|
+
async deposit(connection, amountOrNote, options) {
|
|
3283
|
+
try {
|
|
3284
|
+
let note;
|
|
3285
|
+
let isNewNote = false;
|
|
3286
|
+
if (typeof amountOrNote === "number") {
|
|
3287
|
+
options?.onProgress?.("generating_note", { message: "Generating note with secrets..." });
|
|
3288
|
+
note = await generateNote(amountOrNote, this.config.network);
|
|
3289
|
+
isNewNote = true;
|
|
3290
|
+
} else {
|
|
3291
|
+
note = amountOrNote;
|
|
3292
|
+
if (note.depositSignature) {
|
|
3293
|
+
throw new Error("Note has already been deposited");
|
|
3294
|
+
}
|
|
3295
|
+
}
|
|
3296
|
+
if (isNewNote) {
|
|
3297
|
+
await this.storage.saveNote(note);
|
|
3298
|
+
if (options?.onNoteGenerated) {
|
|
3299
|
+
options?.onProgress?.("awaiting_note_acknowledgment", {
|
|
3300
|
+
message: "Waiting for note to be saved before proceeding with deposit..."
|
|
3301
|
+
});
|
|
3302
|
+
try {
|
|
3303
|
+
await options.onNoteGenerated(note);
|
|
3304
|
+
} catch (error) {
|
|
3305
|
+
throw new Error(
|
|
3306
|
+
`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.`
|
|
3307
|
+
);
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
options?.onProgress?.("note_saved", {
|
|
3311
|
+
message: "Note saved. Proceeding with on-chain deposit..."
|
|
3312
|
+
});
|
|
3313
|
+
}
|
|
3314
|
+
const payerPubkey = this.getPublicKey();
|
|
3315
|
+
const balance = await connection.getBalance(payerPubkey);
|
|
3316
|
+
const requiredAmount = note.amount + 5e3;
|
|
3317
|
+
if (balance < requiredAmount) {
|
|
3318
|
+
throw new Error(
|
|
3319
|
+
`Insufficient balance. Required: ${requiredAmount} lamports (${note.amount} + fees), Available: ${balance} lamports`
|
|
3320
|
+
);
|
|
3321
|
+
}
|
|
3322
|
+
const commitmentBytes = hexToBytes(note.commitment);
|
|
3323
|
+
const programId = this.config.programId || CLOAK_PROGRAM_ID;
|
|
3324
|
+
const depositIx = createDepositInstruction({
|
|
3325
|
+
programId,
|
|
3326
|
+
payer: payerPubkey,
|
|
3327
|
+
pool: this.config.poolAddress,
|
|
3328
|
+
merkleTree: this.config.merkleTreeAddress,
|
|
3329
|
+
amount: note.amount,
|
|
3330
|
+
commitment: commitmentBytes
|
|
3331
|
+
});
|
|
3332
|
+
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
|
|
3333
|
+
const priorityFee = options?.priorityFee ?? 1e4;
|
|
3334
|
+
const cuPriceIx = import_web35.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priorityFee });
|
|
3335
|
+
const dataSizeLimit = options?.loadedAccountsDataSizeLimit ?? 256 * 1024;
|
|
3336
|
+
const dataSizeLimitIx = dataSizeLimit > 0 ? createSetLoadedAccountsDataSizeLimitInstruction(dataSizeLimit) : null;
|
|
3337
|
+
let computeUnits;
|
|
3338
|
+
if (options?.optimizeCU && this.keypair) {
|
|
3339
|
+
options?.onProgress?.("simulating", { message: "Simulating transaction for optimal CU..." });
|
|
3340
|
+
const simCuLimitIx = import_web35.ComputeBudgetProgram.setComputeUnitLimit({ units: 2e5 });
|
|
3341
|
+
const simTransaction = new import_web35.Transaction({
|
|
3342
|
+
feePayer: payerPubkey,
|
|
3343
|
+
recentBlockhash: blockhash
|
|
3344
|
+
}).add(simCuLimitIx).add(cuPriceIx);
|
|
3345
|
+
if (dataSizeLimitIx) {
|
|
3346
|
+
simTransaction.add(dataSizeLimitIx);
|
|
3347
|
+
}
|
|
3348
|
+
simTransaction.add(depositIx);
|
|
3349
|
+
try {
|
|
3350
|
+
const simulation = await connection.simulateTransaction(simTransaction, [this.keypair]);
|
|
3351
|
+
if (simulation.value.err) {
|
|
3352
|
+
console.warn("Simulation failed, using default CU:", simulation.value.err);
|
|
3353
|
+
computeUnits = 4e4;
|
|
3354
|
+
} else {
|
|
3355
|
+
const simulatedCU = simulation.value.unitsConsumed ?? 3e4;
|
|
3356
|
+
computeUnits = Math.ceil(simulatedCU * 1.2);
|
|
3357
|
+
console.log(`CU optimization: ${simulatedCU} simulated \u2192 ${computeUnits} limit`);
|
|
3358
|
+
}
|
|
3359
|
+
} catch (simError) {
|
|
3360
|
+
console.warn("Simulation RPC error, using default CU:", simError);
|
|
3361
|
+
computeUnits = 4e4;
|
|
3362
|
+
}
|
|
3363
|
+
} else if (options?.optimizeCU && this.wallet) {
|
|
3364
|
+
console.warn("CU optimization via simulation not available in wallet mode (would require double signing). Using default.");
|
|
3365
|
+
computeUnits = options?.computeUnits ?? 4e4;
|
|
3366
|
+
} else {
|
|
3367
|
+
computeUnits = options?.computeUnits ?? 4e4;
|
|
3368
|
+
}
|
|
3369
|
+
const cuLimitIx = import_web35.ComputeBudgetProgram.setComputeUnitLimit({ units: computeUnits });
|
|
3370
|
+
const transaction = new import_web35.Transaction({
|
|
3371
|
+
feePayer: payerPubkey,
|
|
3372
|
+
recentBlockhash: blockhash
|
|
3373
|
+
}).add(cuLimitIx).add(cuPriceIx);
|
|
3374
|
+
if (dataSizeLimitIx) {
|
|
3375
|
+
transaction.add(dataSizeLimitIx);
|
|
3376
|
+
}
|
|
3377
|
+
transaction.add(depositIx);
|
|
3378
|
+
if (!transaction.feePayer) {
|
|
3379
|
+
throw new Error("Transaction feePayer is not set");
|
|
3380
|
+
}
|
|
3381
|
+
if (!transaction.recentBlockhash) {
|
|
3382
|
+
throw new Error("Transaction recentBlockhash is not set");
|
|
3383
|
+
}
|
|
3384
|
+
let signature;
|
|
3385
|
+
if (this.wallet) {
|
|
3386
|
+
if (!this.wallet.publicKey) {
|
|
3387
|
+
throw new Error("Wallet not connected - publicKey is null");
|
|
3388
|
+
}
|
|
3389
|
+
if (this.wallet.signTransaction) {
|
|
3390
|
+
try {
|
|
3391
|
+
const signedTransaction = await this.wallet.signTransaction(transaction);
|
|
3392
|
+
const rawTransaction = signedTransaction.serialize();
|
|
3393
|
+
signature = await connection.sendRawTransaction(rawTransaction, {
|
|
3394
|
+
skipPreflight: options?.skipPreflight || false,
|
|
3395
|
+
preflightCommitment: "confirmed",
|
|
3396
|
+
maxRetries: 3
|
|
3397
|
+
});
|
|
3398
|
+
} catch (signError) {
|
|
3399
|
+
if (this.wallet.sendTransaction) {
|
|
3400
|
+
signature = await this.wallet.sendTransaction(transaction, connection, {
|
|
3401
|
+
skipPreflight: options?.skipPreflight || false,
|
|
3402
|
+
preflightCommitment: "confirmed",
|
|
3403
|
+
maxRetries: 3
|
|
3404
|
+
});
|
|
3405
|
+
} else {
|
|
3406
|
+
throw signError;
|
|
3407
|
+
}
|
|
3408
|
+
}
|
|
3409
|
+
} else if (this.wallet.sendTransaction) {
|
|
3410
|
+
signature = await this.wallet.sendTransaction(transaction, connection, {
|
|
3411
|
+
skipPreflight: options?.skipPreflight || false,
|
|
3412
|
+
preflightCommitment: "confirmed",
|
|
3413
|
+
maxRetries: 3
|
|
3414
|
+
});
|
|
3415
|
+
} else {
|
|
3416
|
+
throw new Error("Wallet adapter must provide either sendTransaction or signTransaction");
|
|
3417
|
+
}
|
|
3418
|
+
} else if (this.keypair) {
|
|
3419
|
+
signature = await connection.sendTransaction(transaction, [this.keypair], {
|
|
3420
|
+
skipPreflight: options?.skipPreflight || false,
|
|
3421
|
+
preflightCommitment: "confirmed",
|
|
3422
|
+
maxRetries: 3
|
|
3423
|
+
});
|
|
3424
|
+
} else {
|
|
3425
|
+
throw new Error("No signing method available - provide keypair or wallet");
|
|
3426
|
+
}
|
|
3427
|
+
const confirmation = await connection.confirmTransaction({
|
|
3428
|
+
signature,
|
|
3429
|
+
blockhash,
|
|
3430
|
+
lastValidBlockHeight
|
|
3431
|
+
});
|
|
3432
|
+
if (confirmation.value.err) {
|
|
3433
|
+
throw new Error(
|
|
3434
|
+
`Transaction failed: ${JSON.stringify(confirmation.value.err)}`
|
|
3435
|
+
);
|
|
3436
|
+
}
|
|
3437
|
+
const txDetails = await connection.getTransaction(signature, {
|
|
3438
|
+
commitment: "confirmed",
|
|
3439
|
+
maxSupportedTransactionVersion: 0
|
|
3440
|
+
});
|
|
3441
|
+
const depositSlot = txDetails?.slot ?? 0;
|
|
3442
|
+
let leafIndex = null;
|
|
3443
|
+
let root = null;
|
|
3444
|
+
try {
|
|
3445
|
+
if (txDetails?.meta?.logMessages) {
|
|
3446
|
+
for (const log of txDetails.meta.logMessages) {
|
|
3447
|
+
if (log.includes("Program data:")) {
|
|
3448
|
+
try {
|
|
3449
|
+
const parts = log.split("Program data:");
|
|
3450
|
+
if (parts.length > 1) {
|
|
3451
|
+
const base64Data = parts[1].trim();
|
|
3452
|
+
const eventData = Buffer.from(base64Data, "base64");
|
|
3453
|
+
if (eventData.length >= 81 && eventData[0] === 1) {
|
|
3454
|
+
const parsedLeafIndex = Number(eventData.readBigUInt64LE(1));
|
|
3455
|
+
const loggedCommitment = eventData.slice(9, 41);
|
|
3456
|
+
if (Buffer.compare(loggedCommitment, Buffer.from(commitmentBytes)) === 0) {
|
|
3457
|
+
leafIndex = parsedLeafIndex;
|
|
3458
|
+
const loggedRoot = eventData.slice(49, 81);
|
|
3459
|
+
root = Buffer.from(loggedRoot).toString("hex");
|
|
3460
|
+
break;
|
|
3461
|
+
}
|
|
3462
|
+
}
|
|
3463
|
+
}
|
|
3464
|
+
} catch (e) {
|
|
3465
|
+
continue;
|
|
3466
|
+
}
|
|
3467
|
+
}
|
|
3468
|
+
}
|
|
3469
|
+
}
|
|
3470
|
+
} catch (e) {
|
|
3471
|
+
}
|
|
3472
|
+
if (leafIndex === null) {
|
|
3473
|
+
const programId2 = this.config.programId || CLOAK_PROGRAM_ID;
|
|
3474
|
+
const mintForSOL = NATIVE_SOL_MINT;
|
|
3475
|
+
const { merkleTree } = getShieldPoolPDAs(programId2, mintForSOL);
|
|
3476
|
+
const merkleTreeAccount = await connection.getAccountInfo(merkleTree);
|
|
3477
|
+
if (!merkleTreeAccount) {
|
|
3478
|
+
throw new Error("Failed to fetch merkle tree account");
|
|
3479
|
+
}
|
|
3480
|
+
const nextIndex = Number(merkleTreeAccount.data.readBigUInt64LE(32));
|
|
3481
|
+
leafIndex = nextIndex - 1;
|
|
3482
|
+
const rootBytes = merkleTreeAccount.data.slice(1064, 1096);
|
|
3483
|
+
root = Buffer.from(rootBytes).toString("hex");
|
|
3484
|
+
}
|
|
3485
|
+
if (leafIndex === null || root === null) {
|
|
3486
|
+
throw new Error("Failed to get leaf index and root from transaction logs or on-chain state");
|
|
3487
|
+
}
|
|
3488
|
+
let merkleProof;
|
|
3489
|
+
try {
|
|
3490
|
+
const programId2 = this.config.programId || CLOAK_PROGRAM_ID;
|
|
3491
|
+
const mintForSOL = NATIVE_SOL_MINT;
|
|
3492
|
+
const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId2, mintForSOL);
|
|
3493
|
+
const chainProof = await computeProofFromChain(connection, merkleTreePDA, leafIndex);
|
|
3494
|
+
merkleProof = {
|
|
3495
|
+
pathElements: chainProof.pathElements,
|
|
3496
|
+
pathIndices: chainProof.pathIndices
|
|
3497
|
+
};
|
|
3498
|
+
root = chainProof.root;
|
|
3499
|
+
} catch (proofError) {
|
|
3500
|
+
console.warn("[SDK] Could not compute proof from chain, will fetch at withdrawal:", proofError);
|
|
3501
|
+
}
|
|
3502
|
+
const updatedNote = updateNoteWithDeposit(note, {
|
|
3503
|
+
signature,
|
|
3504
|
+
slot: depositSlot,
|
|
3505
|
+
leafIndex,
|
|
3506
|
+
root,
|
|
3507
|
+
merkleProof
|
|
3508
|
+
});
|
|
3509
|
+
await this.storage.updateNote(note.commitment, {
|
|
3510
|
+
depositSignature: signature,
|
|
3511
|
+
depositSlot,
|
|
3512
|
+
leafIndex,
|
|
3513
|
+
root,
|
|
3514
|
+
merkleProof
|
|
3515
|
+
});
|
|
3516
|
+
return {
|
|
3517
|
+
note: updatedNote,
|
|
3518
|
+
signature,
|
|
3519
|
+
leafIndex,
|
|
3520
|
+
root
|
|
3521
|
+
};
|
|
3522
|
+
} catch (error) {
|
|
3523
|
+
throw this.wrapError(error, "Deposit failed");
|
|
3524
|
+
}
|
|
3525
|
+
}
|
|
3526
|
+
/**
|
|
3527
|
+
* Private transfer with up to 5 recipients
|
|
3528
|
+
*
|
|
3529
|
+
* Handles the complete private transfer flow:
|
|
3530
|
+
* 1. If note is not deposited, deposits it first and waits for confirmation
|
|
3531
|
+
* 2. Generates a zero-knowledge proof
|
|
3532
|
+
* 3. Submits the withdrawal via relay service to recipients
|
|
3533
|
+
*
|
|
3534
|
+
* This is the main method for performing private transfers - it handles everything!
|
|
3535
|
+
*
|
|
3536
|
+
* @param connection - Solana connection (required for deposit if not already deposited)
|
|
3537
|
+
* @param payer - Payer wallet (required for deposit if not already deposited)
|
|
3538
|
+
* @param note - Note to spend (can be deposited or not)
|
|
3539
|
+
* @param recipients - Array of 1-5 recipients with amounts
|
|
3540
|
+
* @param options - Optional configuration
|
|
3541
|
+
* @returns Transfer result with signature and outputs
|
|
3542
|
+
*
|
|
3543
|
+
* @example
|
|
3544
|
+
* ```typescript
|
|
3545
|
+
* // Create a note (not deposited yet)
|
|
3546
|
+
* const note = client.generateNote(1_000_000_000);
|
|
3547
|
+
*
|
|
3548
|
+
* // privateTransfer handles the full flow: deposit + withdraw
|
|
3549
|
+
* const result = await client.privateTransfer(
|
|
3550
|
+
* connection,
|
|
3551
|
+
* wallet,
|
|
3552
|
+
* note,
|
|
3553
|
+
* [
|
|
3554
|
+
* { recipient: new PublicKey("..."), amount: 500_000_000 },
|
|
3555
|
+
* { recipient: new PublicKey("..."), amount: 492_500_000 }
|
|
3556
|
+
* ],
|
|
3557
|
+
* {
|
|
3558
|
+
* relayFeeBps: 50, // 0.5%
|
|
3559
|
+
* onProgress: (status) => console.log(status),
|
|
3560
|
+
* onProofProgress: (pct) => console.log(`Proof: ${pct}%`)
|
|
3561
|
+
* }
|
|
3562
|
+
* );
|
|
3563
|
+
* console.log(`Success! TX: ${result.signature}`);
|
|
3564
|
+
* ```
|
|
3565
|
+
*/
|
|
3566
|
+
async privateTransfer(connection, note, recipients, options) {
|
|
3567
|
+
if (!isWithdrawable(note)) {
|
|
3568
|
+
const depositResult = await this.deposit(connection, note, {
|
|
3569
|
+
skipPreflight: false
|
|
3570
|
+
});
|
|
3571
|
+
note = depositResult.note;
|
|
3572
|
+
}
|
|
3573
|
+
const protocolFee = note.amount - getDistributableAmount(note.amount);
|
|
3574
|
+
const feeBps = Math.ceil(protocolFee * 1e4 / note.amount);
|
|
3575
|
+
const distributableAmount = getDistributableAmount(note.amount);
|
|
3576
|
+
validateTransfers(recipients, distributableAmount);
|
|
3577
|
+
if (!note.leafIndex && note.leafIndex !== 0) {
|
|
3578
|
+
throw new Error("Note must have a leaf index (note must be deposited)");
|
|
3579
|
+
}
|
|
3580
|
+
if (!isValidHex(note.r, 32)) {
|
|
3581
|
+
throw new Error("Note r must be 64 hex characters (32 bytes)");
|
|
3582
|
+
}
|
|
3583
|
+
if (!isValidHex(note.sk_spend, 32)) {
|
|
3584
|
+
throw new Error("Note sk_spend must be 64 hex characters (32 bytes)");
|
|
3585
|
+
}
|
|
3586
|
+
const nullifier = await computeNullifierAsync(note.sk_spend, note.leafIndex);
|
|
3587
|
+
const nullifierHex = nullifier.toString(16).padStart(64, "0");
|
|
3588
|
+
const outputsHash = await computeOutputsHashAsync(recipients);
|
|
3589
|
+
const outputsHashHex = outputsHash.toString(16).padStart(64, "0");
|
|
3590
|
+
const circuitsPath2 = await getDefaultCircuitsPath();
|
|
3591
|
+
const useDirectProof = await areCircuitsAvailable(circuitsPath2);
|
|
3592
|
+
if (!useDirectProof) {
|
|
3593
|
+
throw new Error(
|
|
3594
|
+
`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.`
|
|
3595
|
+
);
|
|
3596
|
+
}
|
|
3597
|
+
const MAX_PROOF_RETRIES = 3;
|
|
3598
|
+
let lastError = null;
|
|
3599
|
+
for (let attempt = 0; attempt < MAX_PROOF_RETRIES; attempt++) {
|
|
3600
|
+
try {
|
|
3601
|
+
if (attempt > 0) {
|
|
3602
|
+
options?.onProgress?.(`proof_refresh_${attempt + 1}`);
|
|
3603
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
3604
|
+
}
|
|
3605
|
+
let merkleProof;
|
|
3606
|
+
let merkleRoot;
|
|
3607
|
+
const hasStoredProof = note.merkleProof && note.root && note.merkleProof.pathElements && note.merkleProof.pathElements.length > 0;
|
|
3608
|
+
if (attempt === 0 && hasStoredProof) {
|
|
3609
|
+
merkleProof = note.merkleProof;
|
|
3610
|
+
merkleRoot = note.root;
|
|
3611
|
+
} else {
|
|
3612
|
+
const programId = this.config.programId || CLOAK_PROGRAM_ID;
|
|
3613
|
+
const mintForSOL = NATIVE_SOL_MINT;
|
|
3614
|
+
const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId, mintForSOL);
|
|
3615
|
+
const chainProof = await computeProofFromChain(
|
|
3616
|
+
connection,
|
|
3617
|
+
merkleTreePDA,
|
|
3618
|
+
note.leafIndex
|
|
3619
|
+
);
|
|
3620
|
+
merkleProof = {
|
|
3621
|
+
pathElements: chainProof.pathElements,
|
|
3622
|
+
pathIndices: chainProof.pathIndices,
|
|
3623
|
+
root: chainProof.root
|
|
3624
|
+
};
|
|
3625
|
+
merkleRoot = chainProof.root;
|
|
3626
|
+
note.merkleProof = merkleProof;
|
|
3627
|
+
note.root = merkleRoot;
|
|
3628
|
+
}
|
|
3629
|
+
if (!merkleProof || !merkleRoot) {
|
|
3630
|
+
throw new Error("Failed to get Merkle proof from chain");
|
|
3631
|
+
}
|
|
3632
|
+
if (!merkleProof.pathElements || merkleProof.pathElements.length === 0) {
|
|
3633
|
+
throw new Error("Merkle proof is invalid: missing path elements");
|
|
3634
|
+
}
|
|
3635
|
+
if (merkleProof.pathElements.length !== merkleProof.pathIndices.length) {
|
|
3636
|
+
throw new Error("Merkle proof is invalid: path elements and indices length mismatch");
|
|
3637
|
+
}
|
|
3638
|
+
for (let i = 0; i < merkleProof.pathIndices.length; i++) {
|
|
3639
|
+
const idx = merkleProof.pathIndices[i];
|
|
3640
|
+
if (idx !== 0 && idx !== 1) {
|
|
3641
|
+
throw new Error(`Merkle proof path index at position ${i} must be 0 or 1, got ${idx}`);
|
|
3642
|
+
}
|
|
3643
|
+
}
|
|
3644
|
+
if (!isValidHex(merkleRoot, 32)) {
|
|
3645
|
+
throw new Error("Merkle root must be 64 hex characters (32 bytes)");
|
|
3646
|
+
}
|
|
3647
|
+
for (let i = 0; i < merkleProof.pathElements.length; i++) {
|
|
3648
|
+
const element = merkleProof.pathElements[i];
|
|
3649
|
+
if (typeof element !== "string" || !isValidHex(element, 32)) {
|
|
3650
|
+
throw new Error(`Merkle proof path element at position ${i} must be 64 hex characters (32 bytes)`);
|
|
3651
|
+
}
|
|
3652
|
+
}
|
|
3653
|
+
const sk_spend_bigint = BigInt("0x" + note.sk_spend);
|
|
3654
|
+
const r_bigint = BigInt("0x" + note.r);
|
|
3655
|
+
const root_bigint = BigInt("0x" + merkleRoot);
|
|
3656
|
+
const nullifier_bigint = BigInt("0x" + nullifierHex);
|
|
3657
|
+
const outputs_hash_bigint = BigInt("0x" + outputsHashHex);
|
|
3658
|
+
const amount_bigint = BigInt(note.amount);
|
|
3659
|
+
const pathElements = merkleProof.pathElements.map((p) => BigInt("0x" + p));
|
|
3660
|
+
const outAddr = [];
|
|
3661
|
+
for (let i = 0; i < 5; i++) {
|
|
3662
|
+
if (i < recipients.length) {
|
|
3663
|
+
const [lo, hi] = pubkeyToLimbs(recipients[i].recipient.toBytes());
|
|
3664
|
+
outAddr.push([lo, hi]);
|
|
3665
|
+
} else {
|
|
3666
|
+
outAddr.push([0n, 0n]);
|
|
3667
|
+
}
|
|
3668
|
+
}
|
|
3669
|
+
const t = amount_bigint * 3n;
|
|
3670
|
+
const varFee = t / 1000n;
|
|
3671
|
+
const rem = t % 1000n;
|
|
3672
|
+
const outAmount = [];
|
|
3673
|
+
const outFlags = [];
|
|
3674
|
+
for (let i = 0; i < 5; i++) {
|
|
3675
|
+
if (i < recipients.length) {
|
|
3676
|
+
outAmount.push(BigInt(recipients[i].amount));
|
|
3677
|
+
outFlags.push(1);
|
|
3678
|
+
} else {
|
|
3679
|
+
outAmount.push(0n);
|
|
3680
|
+
outFlags.push(0);
|
|
3681
|
+
}
|
|
3682
|
+
}
|
|
3683
|
+
const sk = splitTo2Limbs(sk_spend_bigint);
|
|
3684
|
+
const r = splitTo2Limbs(r_bigint);
|
|
3685
|
+
if (attempt === 0) {
|
|
3686
|
+
const computedCommitment = await computeCommitment(amount_bigint, r_bigint, sk_spend_bigint);
|
|
3687
|
+
const noteCommitment = BigInt("0x" + note.commitment);
|
|
3688
|
+
if (computedCommitment !== noteCommitment) {
|
|
3689
|
+
throw new Error(
|
|
3690
|
+
`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.`
|
|
3691
|
+
);
|
|
3692
|
+
}
|
|
3693
|
+
}
|
|
3694
|
+
const proofInputs = {
|
|
3695
|
+
root: root_bigint,
|
|
3696
|
+
nullifier: nullifier_bigint,
|
|
3697
|
+
outputs_hash: outputs_hash_bigint,
|
|
3698
|
+
public_amount: amount_bigint,
|
|
3699
|
+
amount: amount_bigint,
|
|
3700
|
+
leaf_index: BigInt(note.leafIndex),
|
|
3701
|
+
sk,
|
|
3702
|
+
r,
|
|
3703
|
+
pathElements,
|
|
3704
|
+
pathIndices: merkleProof.pathIndices,
|
|
3705
|
+
num_outputs: recipients.length,
|
|
3706
|
+
out_addr: outAddr,
|
|
3707
|
+
out_amount: outAmount,
|
|
3708
|
+
out_flags: outFlags,
|
|
3709
|
+
var_fee: varFee,
|
|
3710
|
+
rem
|
|
3711
|
+
};
|
|
3712
|
+
options?.onProgress?.("proof_generating");
|
|
3713
|
+
const proofResult = await generateWithdrawRegularProof(proofInputs, circuitsPath2);
|
|
3714
|
+
options?.onProgress?.("proof_complete");
|
|
3715
|
+
const proofHex = Buffer.from(proofResult.proofBytes).toString("hex");
|
|
3716
|
+
const finalPublicInputs = {
|
|
3717
|
+
root: merkleRoot,
|
|
3718
|
+
nf: nullifierHex,
|
|
3719
|
+
outputs_hash: outputsHashHex,
|
|
3720
|
+
amount: note.amount
|
|
3721
|
+
};
|
|
3722
|
+
const metadataBundle = options?.metadataBundle;
|
|
3723
|
+
const signature = await this.relay.submitWithdraw(
|
|
3724
|
+
{
|
|
3725
|
+
proof: proofHex,
|
|
3726
|
+
publicInputs: finalPublicInputs,
|
|
3727
|
+
outputs: recipients.map((r2) => ({
|
|
3728
|
+
recipient: r2.recipient.toBase58(),
|
|
3729
|
+
amount: r2.amount
|
|
3730
|
+
})),
|
|
3731
|
+
feeBps,
|
|
3732
|
+
// Use calculated protocol fee BPS
|
|
3733
|
+
metadataBundle
|
|
3734
|
+
},
|
|
3735
|
+
options?.onProgress
|
|
3736
|
+
);
|
|
3737
|
+
return {
|
|
3738
|
+
signature,
|
|
3739
|
+
outputs: recipients.map((r2) => ({
|
|
3740
|
+
recipient: r2.recipient.toBase58(),
|
|
3741
|
+
amount: r2.amount
|
|
3742
|
+
})),
|
|
3743
|
+
nullifier: nullifierHex,
|
|
3744
|
+
root: merkleRoot
|
|
3745
|
+
};
|
|
3746
|
+
} catch (error) {
|
|
3747
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
3748
|
+
if (error instanceof RootNotFoundError) {
|
|
3749
|
+
console.warn(
|
|
3750
|
+
`[Cloak SDK] Merkle root expired (attempt ${attempt + 1}/${MAX_PROOF_RETRIES}). Regenerating proof with fresh root...`
|
|
3751
|
+
);
|
|
3752
|
+
continue;
|
|
3753
|
+
}
|
|
3754
|
+
throw error;
|
|
3755
|
+
}
|
|
3756
|
+
}
|
|
3757
|
+
throw new Error(
|
|
3758
|
+
`Withdrawal failed after ${MAX_PROOF_RETRIES} proof refresh attempts. The Merkle tree is updating too rapidly. Last error: ${lastError?.message}`
|
|
2459
3759
|
);
|
|
2460
3760
|
}
|
|
2461
|
-
/**
|
|
2462
|
-
|
|
2463
|
-
|
|
3761
|
+
/**
|
|
3762
|
+
* Withdraw to a single recipient
|
|
3763
|
+
*
|
|
3764
|
+
* Convenience method for withdrawing to one address.
|
|
3765
|
+
* Handles the complete flow: deposits if needed, then withdraws.
|
|
3766
|
+
*
|
|
3767
|
+
* @param connection - Solana connection
|
|
3768
|
+
* @param payer - Payer wallet
|
|
3769
|
+
* @param note - Note to spend
|
|
3770
|
+
* @param recipient - Recipient address
|
|
3771
|
+
* @param options - Optional configuration
|
|
3772
|
+
* @returns Transfer result
|
|
3773
|
+
*
|
|
3774
|
+
* @example
|
|
3775
|
+
* ```typescript
|
|
3776
|
+
* const note = client.generateNote(1_000_000_000);
|
|
3777
|
+
* const result = await client.withdraw(
|
|
3778
|
+
* connection,
|
|
3779
|
+
* wallet,
|
|
3780
|
+
* note,
|
|
3781
|
+
* new PublicKey("..."),
|
|
3782
|
+
* { withdrawAll: true }
|
|
3783
|
+
* );
|
|
3784
|
+
* ```
|
|
3785
|
+
*/
|
|
3786
|
+
async withdraw(connection, note, recipient, options) {
|
|
3787
|
+
const withdrawAll = options?.withdrawAll ?? true;
|
|
3788
|
+
const amount = withdrawAll ? getDistributableAmount(note.amount) : options?.amount || note.amount;
|
|
3789
|
+
if (!withdrawAll && !options?.amount) {
|
|
3790
|
+
throw new Error("Must specify amount or set withdrawAll: true");
|
|
3791
|
+
}
|
|
3792
|
+
return this.privateTransfer(
|
|
3793
|
+
connection,
|
|
3794
|
+
note,
|
|
3795
|
+
[{ recipient, amount }],
|
|
3796
|
+
options
|
|
3797
|
+
);
|
|
3798
|
+
}
|
|
3799
|
+
/**
|
|
3800
|
+
* Send SOL privately to multiple recipients
|
|
3801
|
+
*
|
|
3802
|
+
* Convenience method that wraps privateTransfer with a simpler API.
|
|
3803
|
+
* Handles the complete flow: deposits if needed, then sends to recipients.
|
|
3804
|
+
*
|
|
3805
|
+
* @param connection - Solana connection
|
|
3806
|
+
* @param note - Note to spend
|
|
3807
|
+
* @param recipients - Array of 1-5 recipients with amounts
|
|
3808
|
+
* @param options - Optional configuration
|
|
3809
|
+
* @returns Transfer result
|
|
3810
|
+
*
|
|
3811
|
+
* @example
|
|
3812
|
+
* ```typescript
|
|
3813
|
+
* const note = client.generateNote(1_000_000_000);
|
|
3814
|
+
* const result = await client.send(
|
|
3815
|
+
* connection,
|
|
3816
|
+
* note,
|
|
3817
|
+
* [
|
|
3818
|
+
* { recipient: new PublicKey("..."), amount: 500_000_000 },
|
|
3819
|
+
* { recipient: new PublicKey("..."), amount: 492_500_000 }
|
|
3820
|
+
* ]
|
|
3821
|
+
* );
|
|
3822
|
+
* ```
|
|
3823
|
+
*/
|
|
3824
|
+
async send(connection, note, recipients, options) {
|
|
3825
|
+
return this.privateTransfer(connection, note, recipients, options);
|
|
3826
|
+
}
|
|
3827
|
+
/**
|
|
3828
|
+
* Swap SOL for tokens privately
|
|
3829
|
+
*
|
|
3830
|
+
* Withdraws SOL from a note and swaps it for tokens via the relay service.
|
|
3831
|
+
* Handles the complete flow: deposits if needed, generates proof, and submits swap.
|
|
3832
|
+
*
|
|
3833
|
+
* @param connection - Solana connection
|
|
3834
|
+
* @param note - Note to spend
|
|
3835
|
+
* @param recipient - Recipient address (will receive tokens)
|
|
3836
|
+
* @param options - Swap configuration
|
|
3837
|
+
* @returns Swap result with transaction signature
|
|
3838
|
+
*
|
|
3839
|
+
* @example
|
|
3840
|
+
* ```typescript
|
|
3841
|
+
* const note = client.generateNote(1_000_000_000);
|
|
3842
|
+
* const result = await client.swap(
|
|
3843
|
+
* connection,
|
|
3844
|
+
* note,
|
|
3845
|
+
* new PublicKey("..."), // recipient
|
|
3846
|
+
* {
|
|
3847
|
+
* outputMint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC
|
|
3848
|
+
* slippageBps: 100, // 1%
|
|
3849
|
+
* getQuote: async (amount, mint, slippage) => {
|
|
3850
|
+
* // Fetch quote from your swap API
|
|
3851
|
+
* const quote = await fetchSwapQuote(amount, mint, slippage);
|
|
3852
|
+
* return {
|
|
3853
|
+
* outAmount: quote.outAmount,
|
|
3854
|
+
* minOutputAmount: quote.minOutputAmount
|
|
3855
|
+
* };
|
|
3856
|
+
* }
|
|
3857
|
+
* }
|
|
3858
|
+
* );
|
|
3859
|
+
* ```
|
|
3860
|
+
*/
|
|
3861
|
+
async swap(connection, note, recipient, options) {
|
|
3862
|
+
try {
|
|
3863
|
+
if (!isWithdrawable(note)) {
|
|
3864
|
+
const depositResult = await this.deposit(connection, note, {
|
|
3865
|
+
skipPreflight: false
|
|
3866
|
+
});
|
|
3867
|
+
note = depositResult.note;
|
|
3868
|
+
}
|
|
3869
|
+
const variableFee = Math.floor(note.amount * 3 / 1e3);
|
|
3870
|
+
const feeBps = note.amount === 0 ? 0 : Math.min(Math.floor((variableFee * 1e4 + note.amount - 1) / note.amount), 65535);
|
|
3871
|
+
const withdrawAmountLamports = getDistributableAmount(note.amount);
|
|
3872
|
+
if (withdrawAmountLamports <= 0) {
|
|
3873
|
+
throw new Error("Amount too small after fees");
|
|
3874
|
+
}
|
|
3875
|
+
let minOutputAmount;
|
|
3876
|
+
if (options.minOutputAmount !== void 0) {
|
|
3877
|
+
minOutputAmount = options.minOutputAmount;
|
|
3878
|
+
} else if (options.getQuote) {
|
|
3879
|
+
const quote = await options.getQuote(
|
|
3880
|
+
withdrawAmountLamports,
|
|
3881
|
+
options.outputMint,
|
|
3882
|
+
options.slippageBps || 100
|
|
3883
|
+
);
|
|
3884
|
+
minOutputAmount = quote.minOutputAmount;
|
|
3885
|
+
} else {
|
|
3886
|
+
throw new Error(
|
|
3887
|
+
"Must provide either minOutputAmount or getQuote function"
|
|
3888
|
+
);
|
|
3889
|
+
}
|
|
3890
|
+
let recipientAta;
|
|
3891
|
+
if (options.recipientAta) {
|
|
3892
|
+
recipientAta = new import_web35.PublicKey(options.recipientAta);
|
|
3893
|
+
} else {
|
|
3894
|
+
try {
|
|
3895
|
+
const splTokenModule = await import("@solana/spl-token");
|
|
3896
|
+
const getAssociatedTokenAddress = splTokenModule.getAssociatedTokenAddress;
|
|
3897
|
+
if (!getAssociatedTokenAddress) {
|
|
3898
|
+
throw new Error("getAssociatedTokenAddress not found");
|
|
3899
|
+
}
|
|
3900
|
+
const outputMint2 = new import_web35.PublicKey(options.outputMint);
|
|
3901
|
+
recipientAta = await getAssociatedTokenAddress(outputMint2, recipient);
|
|
3902
|
+
} catch (error) {
|
|
3903
|
+
throw new Error(
|
|
3904
|
+
`Failed to get associated token account: ${error instanceof Error ? error.message : String(error)}. Please install @solana/spl-token or provide recipientAta in options.`
|
|
3905
|
+
);
|
|
3906
|
+
}
|
|
3907
|
+
}
|
|
3908
|
+
if (!note.leafIndex && note.leafIndex !== 0) {
|
|
3909
|
+
throw new Error("Note must have a leaf index (note must be deposited)");
|
|
3910
|
+
}
|
|
3911
|
+
const nullifier = await computeNullifierAsync(note.sk_spend, note.leafIndex);
|
|
3912
|
+
const nullifierHex = nullifier.toString(16).padStart(64, "0");
|
|
3913
|
+
const inputMint = new import_web35.PublicKey("11111111111111111111111111111111");
|
|
3914
|
+
const outputMint = new import_web35.PublicKey(options.outputMint);
|
|
3915
|
+
const outputsHash = await computeSwapOutputsHashAsync(
|
|
3916
|
+
inputMint,
|
|
3917
|
+
outputMint,
|
|
3918
|
+
recipientAta,
|
|
3919
|
+
minOutputAmount,
|
|
3920
|
+
note.amount
|
|
3921
|
+
);
|
|
3922
|
+
const outputsHashHex = outputsHash.toString(16).padStart(64, "0");
|
|
3923
|
+
const circuitsPath2 = await getDefaultCircuitsPath();
|
|
3924
|
+
const useDirectProof = await areCircuitsAvailable(circuitsPath2);
|
|
3925
|
+
if (!useDirectProof) {
|
|
3926
|
+
throw new Error(
|
|
3927
|
+
`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.`
|
|
3928
|
+
);
|
|
3929
|
+
}
|
|
3930
|
+
const MAX_PROOF_RETRIES = 3;
|
|
3931
|
+
let lastError = null;
|
|
3932
|
+
for (let attempt = 0; attempt < MAX_PROOF_RETRIES; attempt++) {
|
|
3933
|
+
try {
|
|
3934
|
+
if (attempt > 0) {
|
|
3935
|
+
options?.onProgress?.(`proof_refresh_${attempt + 1}`);
|
|
3936
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
3937
|
+
}
|
|
3938
|
+
const programId = this.config.programId || CLOAK_PROGRAM_ID;
|
|
3939
|
+
const mintForSOL = NATIVE_SOL_MINT;
|
|
3940
|
+
const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId, mintForSOL);
|
|
3941
|
+
const chainProof = await computeProofFromChain(connection, merkleTreePDA, note.leafIndex);
|
|
3942
|
+
const merkleProof = {
|
|
3943
|
+
pathElements: chainProof.pathElements,
|
|
3944
|
+
pathIndices: chainProof.pathIndices
|
|
3945
|
+
};
|
|
3946
|
+
const merkleRoot = chainProof.root;
|
|
3947
|
+
if (!merkleRoot) {
|
|
3948
|
+
throw new Error("Failed to get Merkle root from chain");
|
|
3949
|
+
}
|
|
3950
|
+
if (!merkleProof.pathElements || merkleProof.pathElements.length === 0) {
|
|
3951
|
+
throw new Error("Merkle proof is invalid: missing path elements");
|
|
3952
|
+
}
|
|
3953
|
+
const sk_spend_bigint = BigInt("0x" + note.sk_spend);
|
|
3954
|
+
const r_bigint = BigInt("0x" + note.r);
|
|
3955
|
+
const root_bigint = BigInt("0x" + merkleRoot);
|
|
3956
|
+
const nullifier_bigint = BigInt("0x" + nullifierHex);
|
|
3957
|
+
const outputs_hash_bigint = BigInt("0x" + outputsHashHex);
|
|
3958
|
+
const amount_bigint = BigInt(note.amount);
|
|
3959
|
+
const pathElements = merkleProof.pathElements.map((p) => BigInt("0x" + p));
|
|
3960
|
+
const inputMintLimbs = pubkeyToLimbs(inputMint.toBytes());
|
|
3961
|
+
const outputMintLimbs = pubkeyToLimbs(outputMint.toBytes());
|
|
3962
|
+
const recipientAtaLimbs = pubkeyToLimbs(recipientAta.toBytes());
|
|
3963
|
+
if (attempt === 0) {
|
|
3964
|
+
const computedCommitment = await computeCommitment(amount_bigint, r_bigint, sk_spend_bigint);
|
|
3965
|
+
const noteCommitment = BigInt("0x" + note.commitment);
|
|
3966
|
+
if (computedCommitment !== noteCommitment) {
|
|
3967
|
+
throw new Error(
|
|
3968
|
+
`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.`
|
|
3969
|
+
);
|
|
3970
|
+
}
|
|
3971
|
+
}
|
|
3972
|
+
const t = amount_bigint * 3n;
|
|
3973
|
+
const varFee = t / 1000n;
|
|
3974
|
+
const rem = t % 1000n;
|
|
3975
|
+
const proofInputs = {
|
|
3976
|
+
sk_spend: sk_spend_bigint,
|
|
3977
|
+
r: r_bigint,
|
|
3978
|
+
amount: amount_bigint,
|
|
3979
|
+
leaf_index: BigInt(note.leafIndex),
|
|
3980
|
+
path_elements: pathElements,
|
|
3981
|
+
path_indices: merkleProof.pathIndices,
|
|
3982
|
+
root: root_bigint,
|
|
3983
|
+
nullifier: nullifier_bigint,
|
|
3984
|
+
outputs_hash: outputs_hash_bigint,
|
|
3985
|
+
public_amount: amount_bigint,
|
|
3986
|
+
input_mint: inputMintLimbs,
|
|
3987
|
+
output_mint: outputMintLimbs,
|
|
3988
|
+
recipient_ata: recipientAtaLimbs,
|
|
3989
|
+
min_output_amount: BigInt(minOutputAmount),
|
|
3990
|
+
var_fee: varFee,
|
|
3991
|
+
rem
|
|
3992
|
+
};
|
|
3993
|
+
options?.onProgress?.("proof_generating");
|
|
3994
|
+
const proofResult = await generateWithdrawSwapProof(proofInputs, circuitsPath2);
|
|
3995
|
+
options?.onProgress?.("proof_complete");
|
|
3996
|
+
const proofHex = Buffer.from(proofResult.proofBytes).toString("hex");
|
|
3997
|
+
const finalPublicInputs = {
|
|
3998
|
+
root: merkleRoot,
|
|
3999
|
+
nf: nullifierHex,
|
|
4000
|
+
outputs_hash: outputsHashHex,
|
|
4001
|
+
amount: note.amount
|
|
4002
|
+
};
|
|
4003
|
+
const metadataBundle = options?.metadataBundle;
|
|
4004
|
+
const signature = await this.relay.submitSwap(
|
|
4005
|
+
{
|
|
4006
|
+
proof: proofHex,
|
|
4007
|
+
publicInputs: finalPublicInputs,
|
|
4008
|
+
outputs: [
|
|
4009
|
+
{
|
|
4010
|
+
recipient: recipient.toBase58(),
|
|
4011
|
+
amount: withdrawAmountLamports
|
|
4012
|
+
}
|
|
4013
|
+
],
|
|
4014
|
+
feeBps,
|
|
4015
|
+
swap: {
|
|
4016
|
+
output_mint: options.outputMint,
|
|
4017
|
+
slippage_bps: options.slippageBps || 100,
|
|
4018
|
+
min_output_amount: minOutputAmount
|
|
4019
|
+
},
|
|
4020
|
+
metadataBundle
|
|
4021
|
+
},
|
|
4022
|
+
options.onProgress
|
|
4023
|
+
);
|
|
4024
|
+
return {
|
|
4025
|
+
signature,
|
|
4026
|
+
outputs: [
|
|
4027
|
+
{
|
|
4028
|
+
recipient: recipient.toBase58(),
|
|
4029
|
+
amount: withdrawAmountLamports
|
|
4030
|
+
}
|
|
4031
|
+
],
|
|
4032
|
+
nullifier: nullifierHex,
|
|
4033
|
+
root: merkleRoot,
|
|
4034
|
+
outputMint: options.outputMint,
|
|
4035
|
+
minOutputAmount
|
|
4036
|
+
};
|
|
4037
|
+
} catch (error) {
|
|
4038
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
4039
|
+
if (error instanceof RootNotFoundError) {
|
|
4040
|
+
console.warn(
|
|
4041
|
+
`[Cloak SDK] Merkle root expired (attempt ${attempt + 1}/${MAX_PROOF_RETRIES}). Regenerating swap proof with fresh root...`
|
|
4042
|
+
);
|
|
4043
|
+
continue;
|
|
4044
|
+
}
|
|
4045
|
+
throw error;
|
|
4046
|
+
}
|
|
4047
|
+
}
|
|
4048
|
+
throw new Error(
|
|
4049
|
+
`Swap failed after ${MAX_PROOF_RETRIES} proof refresh attempts. The Merkle tree is updating too rapidly. Last error: ${lastError?.message}`
|
|
4050
|
+
);
|
|
4051
|
+
} catch (error) {
|
|
4052
|
+
throw this.wrapError(error, "Swap failed");
|
|
4053
|
+
}
|
|
4054
|
+
}
|
|
4055
|
+
/**
|
|
4056
|
+
* Generate a new note without depositing
|
|
4057
|
+
*
|
|
4058
|
+
* @param amountLamports - Amount for the note
|
|
4059
|
+
* @param useWalletKeys - Whether to use wallet keys (v2.0 recommended)
|
|
4060
|
+
* @returns New note (not yet deposited)
|
|
4061
|
+
*/
|
|
4062
|
+
async generateNote(amountLamports, useWalletKeys = false) {
|
|
4063
|
+
if (useWalletKeys && this.cloakKeys) {
|
|
4064
|
+
return await generateNoteFromWallet(amountLamports, this.cloakKeys, this.config.network);
|
|
4065
|
+
} else if (useWalletKeys) {
|
|
4066
|
+
const keys = generateCloakKeys();
|
|
4067
|
+
this.cloakKeys = keys;
|
|
4068
|
+
const result = this.storage.saveKeys(keys);
|
|
4069
|
+
if (result instanceof Promise) {
|
|
4070
|
+
result.catch(() => {
|
|
4071
|
+
});
|
|
4072
|
+
}
|
|
4073
|
+
return await generateNoteFromWallet(amountLamports, keys, this.config.network);
|
|
4074
|
+
}
|
|
4075
|
+
return await generateNote(amountLamports, this.config.network);
|
|
4076
|
+
}
|
|
4077
|
+
/**
|
|
4078
|
+
* Parse a note from JSON string
|
|
4079
|
+
*
|
|
4080
|
+
* @param jsonString - JSON representation
|
|
4081
|
+
* @returns Parsed note
|
|
4082
|
+
*/
|
|
4083
|
+
parseNote(jsonString) {
|
|
4084
|
+
return parseNote2(jsonString);
|
|
4085
|
+
}
|
|
4086
|
+
/**
|
|
4087
|
+
* Export a note to JSON string
|
|
4088
|
+
*
|
|
4089
|
+
* @param note - Note to export
|
|
4090
|
+
* @param pretty - Format with indentation
|
|
4091
|
+
* @returns JSON string
|
|
4092
|
+
*/
|
|
4093
|
+
exportNote(note, pretty = false) {
|
|
4094
|
+
return pretty ? JSON.stringify(note, null, 2) : JSON.stringify(note);
|
|
2464
4095
|
}
|
|
2465
4096
|
/**
|
|
2466
|
-
*
|
|
2467
|
-
*
|
|
4097
|
+
* Check if a note is ready for withdrawal
|
|
4098
|
+
*
|
|
4099
|
+
* @param note - Note to check
|
|
4100
|
+
* @returns True if withdrawable
|
|
4101
|
+
*/
|
|
4102
|
+
isWithdrawable(note) {
|
|
4103
|
+
return isWithdrawable(note);
|
|
4104
|
+
}
|
|
4105
|
+
/**
|
|
4106
|
+
* Get Merkle proof for a leaf index directly from on-chain state
|
|
4107
|
+
*
|
|
4108
|
+
* @param connection - Solana connection
|
|
4109
|
+
* @param leafIndex - Leaf index in tree
|
|
4110
|
+
* @returns Merkle proof computed from on-chain data
|
|
2468
4111
|
*/
|
|
2469
4112
|
async getMerkleProof(connection, leafIndex) {
|
|
2470
4113
|
const programId = this.config.programId || CLOAK_PROGRAM_ID;
|
|
2471
|
-
const
|
|
4114
|
+
const mintForSOL = NATIVE_SOL_MINT;
|
|
4115
|
+
const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId, mintForSOL);
|
|
2472
4116
|
const chainProof = await computeProofFromChain(connection, merkleTreePDA, leafIndex);
|
|
2473
4117
|
return {
|
|
2474
4118
|
pathElements: chainProof.pathElements,
|
|
@@ -2476,20 +4120,72 @@ var CloakSDK = class {
|
|
|
2476
4120
|
root: chainProof.root
|
|
2477
4121
|
};
|
|
2478
4122
|
}
|
|
2479
|
-
/**
|
|
4123
|
+
/**
|
|
4124
|
+
* Get current Merkle root directly from on-chain state
|
|
4125
|
+
*
|
|
4126
|
+
* @param connection - Solana connection
|
|
4127
|
+
* @returns Current root hash from on-chain tree
|
|
4128
|
+
*/
|
|
2480
4129
|
async getCurrentRoot(connection) {
|
|
2481
4130
|
const programId = this.config.programId || CLOAK_PROGRAM_ID;
|
|
2482
|
-
const
|
|
4131
|
+
const mintForSOL = NATIVE_SOL_MINT;
|
|
4132
|
+
const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId, mintForSOL);
|
|
2483
4133
|
const state = await readMerkleTreeState(connection, merkleTreePDA);
|
|
2484
4134
|
return state.root;
|
|
2485
4135
|
}
|
|
2486
|
-
/**
|
|
4136
|
+
/**
|
|
4137
|
+
* Get transaction status from relay service
|
|
4138
|
+
*
|
|
4139
|
+
* @param requestId - Request ID from previous submission
|
|
4140
|
+
* @returns Current status
|
|
4141
|
+
*/
|
|
2487
4142
|
async getTransactionStatus(requestId) {
|
|
2488
4143
|
return this.relay.getStatus(requestId);
|
|
2489
4144
|
}
|
|
2490
|
-
/**
|
|
4145
|
+
/**
|
|
4146
|
+
* Fetch and decrypt this user's transaction metadata history.
|
|
4147
|
+
* DEPRECATED: This functionality is no longer supported
|
|
4148
|
+
*/
|
|
4149
|
+
async getTransactionMetadata(_options) {
|
|
4150
|
+
throw new Error("getTransactionMetadata is deprecated - use compliance export API instead");
|
|
4151
|
+
}
|
|
4152
|
+
/**
|
|
4153
|
+
* Load all notes from storage
|
|
4154
|
+
*
|
|
4155
|
+
* @returns Array of saved notes
|
|
4156
|
+
*/
|
|
4157
|
+
async loadNotes() {
|
|
4158
|
+
const notes = this.storage.loadAllNotes();
|
|
4159
|
+
return Array.isArray(notes) ? notes : await notes;
|
|
4160
|
+
}
|
|
4161
|
+
/**
|
|
4162
|
+
* Save a note to storage
|
|
4163
|
+
*
|
|
4164
|
+
* @param note - Note to save
|
|
4165
|
+
*/
|
|
4166
|
+
async saveNote(note) {
|
|
4167
|
+
const result = this.storage.saveNote(note);
|
|
4168
|
+
if (result instanceof Promise) {
|
|
4169
|
+
await result;
|
|
4170
|
+
}
|
|
4171
|
+
}
|
|
4172
|
+
/**
|
|
4173
|
+
* Find a note by its commitment
|
|
4174
|
+
*
|
|
4175
|
+
* @param commitment - Commitment hash
|
|
4176
|
+
* @returns Note if found
|
|
4177
|
+
*/
|
|
4178
|
+
async findNote(commitment) {
|
|
4179
|
+
const notes = await this.loadNotes();
|
|
4180
|
+
return findNoteByCommitment(notes, commitment);
|
|
4181
|
+
}
|
|
4182
|
+
/**
|
|
4183
|
+
* Import wallet keys from JSON
|
|
4184
|
+
*
|
|
4185
|
+
* @param keysJson - JSON string containing keys
|
|
4186
|
+
*/
|
|
2491
4187
|
async importWalletKeys(keysJson) {
|
|
2492
|
-
const keys =
|
|
4188
|
+
const keys = importWalletKeys(keysJson);
|
|
2493
4189
|
this.cloakKeys = keys;
|
|
2494
4190
|
const result = this.storage.saveKeys(keys);
|
|
2495
4191
|
if (result instanceof Promise) {
|
|
@@ -2497,32 +4193,134 @@ var CloakSDK = class {
|
|
|
2497
4193
|
}
|
|
2498
4194
|
}
|
|
2499
4195
|
/**
|
|
2500
|
-
* Export wallet keys to JSON
|
|
2501
|
-
*
|
|
2502
|
-
* WARNING:
|
|
4196
|
+
* Export wallet keys to JSON
|
|
4197
|
+
*
|
|
4198
|
+
* WARNING: This exports secret keys! Store securely.
|
|
4199
|
+
*
|
|
4200
|
+
* @returns JSON string with keys
|
|
2503
4201
|
*/
|
|
2504
4202
|
exportWalletKeys() {
|
|
2505
4203
|
if (!this.cloakKeys) {
|
|
2506
4204
|
throw new CloakError("No wallet keys available", "wallet", false);
|
|
2507
4205
|
}
|
|
2508
|
-
return
|
|
4206
|
+
return exportWalletKeys(this.cloakKeys);
|
|
2509
4207
|
}
|
|
2510
4208
|
/**
|
|
2511
|
-
*
|
|
2512
|
-
*
|
|
2513
|
-
* The return type ({@link CloakConfig}) is structurally narrower than the
|
|
2514
|
-
* constructor input ({@link CloakSDKOptions}): `keypairBytes`, `wallet`,
|
|
2515
|
-
* and `storage` are intentionally absent from the snapshot. The secrets
|
|
2516
|
-
* are consumed into private SDK state at construction time and are not
|
|
2517
|
-
* re-exposable through this method — the type system enforces it. To
|
|
2518
|
-
* sign, pass a `Keypair` / `WalletAdapter` directly to the standalone
|
|
2519
|
-
* `transact` / `partialWithdraw` / `fullWithdraw` / `swapUtxo` helpers.
|
|
4209
|
+
* Get the configuration
|
|
2520
4210
|
*/
|
|
2521
4211
|
getConfig() {
|
|
2522
4212
|
return { ...this.config };
|
|
2523
4213
|
}
|
|
4214
|
+
// Note: scanNotes was removed - it required the indexer to store encrypted outputs.
|
|
4215
|
+
// Users should maintain their own note storage. This is more privacy-preserving.
|
|
4216
|
+
/**
|
|
4217
|
+
* Wrap errors with better categorization and user-friendly messages
|
|
4218
|
+
*
|
|
4219
|
+
* @private
|
|
4220
|
+
*/
|
|
4221
|
+
wrapError(error, context) {
|
|
4222
|
+
if (error instanceof CloakError) {
|
|
4223
|
+
return error;
|
|
4224
|
+
}
|
|
4225
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4226
|
+
if (errorMessage.includes("duplicate key") || errorMessage.includes("already deposited")) {
|
|
4227
|
+
return new CloakError(
|
|
4228
|
+
"This note was already deposited. Generate a new note to continue.",
|
|
4229
|
+
"validation",
|
|
4230
|
+
false,
|
|
4231
|
+
error instanceof Error ? error : void 0
|
|
4232
|
+
);
|
|
4233
|
+
}
|
|
4234
|
+
if (errorMessage.includes("insufficient funds") || errorMessage.includes("insufficient lamports")) {
|
|
4235
|
+
return new CloakError(
|
|
4236
|
+
"Insufficient funds for this transaction. Please check your wallet balance.",
|
|
4237
|
+
"wallet",
|
|
4238
|
+
false,
|
|
4239
|
+
error instanceof Error ? error : void 0
|
|
4240
|
+
);
|
|
4241
|
+
}
|
|
4242
|
+
if (errorMessage.includes("Merkle tree") || errorMessage.includes("merkle")) {
|
|
4243
|
+
return new CloakError(
|
|
4244
|
+
"Failed to read on-chain Merkle tree state. Please try again.",
|
|
4245
|
+
"network",
|
|
4246
|
+
true,
|
|
4247
|
+
error instanceof Error ? error : void 0
|
|
4248
|
+
);
|
|
4249
|
+
}
|
|
4250
|
+
if (errorMessage.includes("timeout") || errorMessage.includes("timed out")) {
|
|
4251
|
+
return new CloakError(
|
|
4252
|
+
"Network timeout. Please check your connection and try again.",
|
|
4253
|
+
"network",
|
|
4254
|
+
true,
|
|
4255
|
+
error instanceof Error ? error : void 0
|
|
4256
|
+
);
|
|
4257
|
+
}
|
|
4258
|
+
if (errorMessage.includes("not connected") || errorMessage.includes("wallet")) {
|
|
4259
|
+
return new CloakError(
|
|
4260
|
+
"Wallet not connected. Please connect your wallet first.",
|
|
4261
|
+
"wallet",
|
|
4262
|
+
false,
|
|
4263
|
+
error instanceof Error ? error : void 0
|
|
4264
|
+
);
|
|
4265
|
+
}
|
|
4266
|
+
if (errorMessage.includes("proof") && (errorMessage.includes("failed") || errorMessage.includes("error"))) {
|
|
4267
|
+
return new CloakError(
|
|
4268
|
+
"Zero-knowledge proof generation failed. This is usually temporary - please try again.",
|
|
4269
|
+
"prover",
|
|
4270
|
+
true,
|
|
4271
|
+
error instanceof Error ? error : void 0
|
|
4272
|
+
);
|
|
4273
|
+
}
|
|
4274
|
+
if (errorMessage.includes("relay") || errorMessage.includes("withdraw")) {
|
|
4275
|
+
return new CloakError(
|
|
4276
|
+
"Relay service error. Please try again later.",
|
|
4277
|
+
"relay",
|
|
4278
|
+
true,
|
|
4279
|
+
error instanceof Error ? error : void 0
|
|
4280
|
+
);
|
|
4281
|
+
}
|
|
4282
|
+
return new CloakError(
|
|
4283
|
+
`${context}: ${errorMessage}`,
|
|
4284
|
+
"network",
|
|
4285
|
+
false,
|
|
4286
|
+
error instanceof Error ? error : void 0
|
|
4287
|
+
);
|
|
4288
|
+
}
|
|
2524
4289
|
};
|
|
2525
4290
|
|
|
4291
|
+
// src/core/note.ts
|
|
4292
|
+
function serializeNote(note, pretty = false) {
|
|
4293
|
+
validateNote(note);
|
|
4294
|
+
return pretty ? JSON.stringify(note, null, 2) : JSON.stringify(note);
|
|
4295
|
+
}
|
|
4296
|
+
function downloadNote(note, filename) {
|
|
4297
|
+
const g = globalThis;
|
|
4298
|
+
const doc = g?.document;
|
|
4299
|
+
const URL_ = g?.URL;
|
|
4300
|
+
const Blob_ = g?.Blob;
|
|
4301
|
+
if (!doc || !URL_ || !Blob_) {
|
|
4302
|
+
throw new Error("downloadNote is only available in browser environments");
|
|
4303
|
+
}
|
|
4304
|
+
const json = serializeNote(note, true);
|
|
4305
|
+
const blob = new Blob_([json], { type: "application/json" });
|
|
4306
|
+
const url = URL_.createObjectURL(blob);
|
|
4307
|
+
const defaultFilename = `cloak-note-${note.commitment.slice(0, 8)}.json`;
|
|
4308
|
+
const link = doc.createElement("a");
|
|
4309
|
+
link.href = url;
|
|
4310
|
+
link.download = filename || defaultFilename;
|
|
4311
|
+
link.click();
|
|
4312
|
+
URL_.revokeObjectURL(url);
|
|
4313
|
+
}
|
|
4314
|
+
async function copyNoteToClipboard(note) {
|
|
4315
|
+
const g = globalThis;
|
|
4316
|
+
const nav = g?.navigator;
|
|
4317
|
+
if (!nav || !nav.clipboard) {
|
|
4318
|
+
throw new Error("Clipboard API not available");
|
|
4319
|
+
}
|
|
4320
|
+
const json = serializeNote(note, true);
|
|
4321
|
+
await nav.clipboard.writeText(json);
|
|
4322
|
+
}
|
|
4323
|
+
|
|
2526
4324
|
// src/core/compliance-keys.ts
|
|
2527
4325
|
var import_tweetnacl2 = __toESM(require("tweetnacl"), 1);
|
|
2528
4326
|
var import_blake33 = require("@noble/hashes/blake3");
|
|
@@ -2909,107 +4707,6 @@ async function decryptComplianceMetadataWithMasterKey(encrypted, masterComplianc
|
|
|
2909
4707
|
return decryptWithSharedSecret(payload, sharedSecret);
|
|
2910
4708
|
}
|
|
2911
4709
|
|
|
2912
|
-
// src/utils/fees.ts
|
|
2913
|
-
var LAMPORTS_PER_SOL = 1e9;
|
|
2914
|
-
var FIXED_FEE_LAMPORTS = 5e6;
|
|
2915
|
-
var VARIABLE_FEE_NUMERATOR = 3;
|
|
2916
|
-
var VARIABLE_FEE_DENOMINATOR = 1e3;
|
|
2917
|
-
var VARIABLE_FEE_RATE = VARIABLE_FEE_NUMERATOR / VARIABLE_FEE_DENOMINATOR;
|
|
2918
|
-
var MIN_DEPOSIT_LAMPORTS = 1e7;
|
|
2919
|
-
function calculateFee(amountLamports) {
|
|
2920
|
-
const variableFee = Math.floor(amountLamports * VARIABLE_FEE_NUMERATOR / VARIABLE_FEE_DENOMINATOR);
|
|
2921
|
-
return FIXED_FEE_LAMPORTS + variableFee;
|
|
2922
|
-
}
|
|
2923
|
-
function calculateFeeBigint(amountLamports) {
|
|
2924
|
-
const fixed = BigInt(FIXED_FEE_LAMPORTS);
|
|
2925
|
-
const variable = amountLamports * BigInt(VARIABLE_FEE_NUMERATOR) / BigInt(VARIABLE_FEE_DENOMINATOR);
|
|
2926
|
-
return fixed + variable;
|
|
2927
|
-
}
|
|
2928
|
-
function isWithdrawAmountSufficient(amountLamports) {
|
|
2929
|
-
return amountLamports > calculateFeeBigint(amountLamports);
|
|
2930
|
-
}
|
|
2931
|
-
function getDistributableAmount(amountLamports) {
|
|
2932
|
-
return amountLamports - calculateFee(amountLamports);
|
|
2933
|
-
}
|
|
2934
|
-
function formatAmount(lamports, decimals = 9) {
|
|
2935
|
-
return (lamports / LAMPORTS_PER_SOL).toFixed(decimals);
|
|
2936
|
-
}
|
|
2937
|
-
function parseAmount(sol) {
|
|
2938
|
-
const num = parseFloat(sol);
|
|
2939
|
-
if (isNaN(num) || num < 0) {
|
|
2940
|
-
throw new Error(`Invalid SOL amount: ${sol}`);
|
|
2941
|
-
}
|
|
2942
|
-
return Math.floor(num * LAMPORTS_PER_SOL);
|
|
2943
|
-
}
|
|
2944
|
-
function validateOutputsSum(outputs, expectedTotal) {
|
|
2945
|
-
const sum = outputs.reduce((acc, out) => acc + out.amount, 0);
|
|
2946
|
-
return sum === expectedTotal;
|
|
2947
|
-
}
|
|
2948
|
-
function calculateRelayFee(amountLamports, feeBps) {
|
|
2949
|
-
if (feeBps < 0 || feeBps > 1e4) {
|
|
2950
|
-
throw new Error("Fee basis points must be between 0 and 10000");
|
|
2951
|
-
}
|
|
2952
|
-
return Math.floor(amountLamports * feeBps / 1e4);
|
|
2953
|
-
}
|
|
2954
|
-
|
|
2955
|
-
// src/utils/validation.ts
|
|
2956
|
-
var import_web34 = require("@solana/web3.js");
|
|
2957
|
-
function isValidSolanaAddress(address) {
|
|
2958
|
-
try {
|
|
2959
|
-
new import_web34.PublicKey(address);
|
|
2960
|
-
return true;
|
|
2961
|
-
} catch {
|
|
2962
|
-
return false;
|
|
2963
|
-
}
|
|
2964
|
-
}
|
|
2965
|
-
|
|
2966
|
-
// src/utils/network.ts
|
|
2967
|
-
function detectNetworkFromRpcUrl(rpcUrl) {
|
|
2968
|
-
const url = rpcUrl || process.env.NEXT_PUBLIC_SOLANA_RPC_URL || "";
|
|
2969
|
-
const lowerUrl = url.toLowerCase();
|
|
2970
|
-
if (lowerUrl.includes("mainnet") || lowerUrl.includes("api.mainnet-beta") || lowerUrl.includes("mainnet-beta")) {
|
|
2971
|
-
return "mainnet";
|
|
2972
|
-
}
|
|
2973
|
-
if (lowerUrl.includes("testnet") || lowerUrl.includes("api.testnet")) {
|
|
2974
|
-
return "testnet";
|
|
2975
|
-
}
|
|
2976
|
-
if (lowerUrl.includes("devnet") || lowerUrl.includes("api.devnet")) {
|
|
2977
|
-
return "devnet";
|
|
2978
|
-
}
|
|
2979
|
-
if (lowerUrl.includes("localhost") || lowerUrl.includes("127.0.0.1") || lowerUrl.includes("local")) {
|
|
2980
|
-
return "localnet";
|
|
2981
|
-
}
|
|
2982
|
-
return "mainnet";
|
|
2983
|
-
}
|
|
2984
|
-
function getRpcUrlForNetwork(network) {
|
|
2985
|
-
switch (network) {
|
|
2986
|
-
case "mainnet":
|
|
2987
|
-
return "https://api.mainnet-beta.solana.com";
|
|
2988
|
-
case "devnet":
|
|
2989
|
-
return "https://api.devnet.solana.com";
|
|
2990
|
-
case "localnet":
|
|
2991
|
-
return "http://localhost:8899";
|
|
2992
|
-
default:
|
|
2993
|
-
return "https://api.mainnet-beta.solana.com";
|
|
2994
|
-
}
|
|
2995
|
-
}
|
|
2996
|
-
function isValidRpcUrl(url) {
|
|
2997
|
-
try {
|
|
2998
|
-
const parsed = new URL(url);
|
|
2999
|
-
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
3000
|
-
} catch {
|
|
3001
|
-
return false;
|
|
3002
|
-
}
|
|
3003
|
-
}
|
|
3004
|
-
function getExplorerUrl(signature, network = "devnet") {
|
|
3005
|
-
const cluster = network === "mainnet" ? "" : `?cluster=${network}`;
|
|
3006
|
-
return `https://explorer.solana.com/tx/${signature}${cluster}`;
|
|
3007
|
-
}
|
|
3008
|
-
function getAddressExplorerUrl(address, network = "devnet") {
|
|
3009
|
-
const cluster = network === "mainnet" ? "" : `?cluster=${network}`;
|
|
3010
|
-
return `https://explorer.solana.com/address/${address}${cluster}`;
|
|
3011
|
-
}
|
|
3012
|
-
|
|
3013
4710
|
// src/utils/verify-utxos.ts
|
|
3014
4711
|
init_utxo();
|
|
3015
4712
|
async function verifyUtxos(utxos, connection, programId, commitment = "confirmed") {
|
|
@@ -3021,7 +4718,7 @@ async function verifyUtxos(utxos, connection, programId, commitment = "confirmed
|
|
|
3021
4718
|
continue;
|
|
3022
4719
|
}
|
|
3023
4720
|
try {
|
|
3024
|
-
const nullifierBig = await
|
|
4721
|
+
const nullifierBig = await computeNullifier2(utxo);
|
|
3025
4722
|
const nullifierHex = nullifierBig.toString(16).padStart(64, "0");
|
|
3026
4723
|
const nullifierBytes = Buffer.from(nullifierHex, "hex");
|
|
3027
4724
|
const { pool } = getShieldPoolPDAs(programId, utxo.mintAddress);
|
|
@@ -3139,10 +4836,49 @@ async function fetchRiskQuoteIx(connection, wallet, rangeApiKey) {
|
|
|
3139
4836
|
};
|
|
3140
4837
|
}
|
|
3141
4838
|
|
|
4839
|
+
// src/helpers/encrypted-output.ts
|
|
4840
|
+
function prepareEncryptedOutput(note, cloakKeys) {
|
|
4841
|
+
const noteData = {
|
|
4842
|
+
amount: note.amount,
|
|
4843
|
+
r: note.r,
|
|
4844
|
+
sk_spend: note.sk_spend,
|
|
4845
|
+
commitment: note.commitment
|
|
4846
|
+
};
|
|
4847
|
+
const encrypted = encryptNoteForRecipient(noteData, cloakKeys.view.pvk);
|
|
4848
|
+
return btoa(JSON.stringify(encrypted));
|
|
4849
|
+
}
|
|
4850
|
+
function prepareEncryptedOutputForRecipient(note, recipientPvkHex) {
|
|
4851
|
+
const noteData = {
|
|
4852
|
+
amount: note.amount,
|
|
4853
|
+
r: note.r,
|
|
4854
|
+
sk_spend: note.sk_spend,
|
|
4855
|
+
commitment: note.commitment
|
|
4856
|
+
};
|
|
4857
|
+
const recipientPvk = hexToBytes(recipientPvkHex);
|
|
4858
|
+
const encrypted = encryptNoteForRecipient(noteData, recipientPvk);
|
|
4859
|
+
return btoa(JSON.stringify(encrypted));
|
|
4860
|
+
}
|
|
4861
|
+
function encodeNoteSimple(note) {
|
|
4862
|
+
const data = {
|
|
4863
|
+
amount: note.amount,
|
|
4864
|
+
r: note.r,
|
|
4865
|
+
sk_spend: note.sk_spend,
|
|
4866
|
+
commitment: note.commitment
|
|
4867
|
+
};
|
|
4868
|
+
const json = JSON.stringify(data);
|
|
4869
|
+
if (typeof Buffer !== "undefined") {
|
|
4870
|
+
return Buffer.from(json).toString("base64");
|
|
4871
|
+
} else if (typeof btoa !== "undefined") {
|
|
4872
|
+
return btoa(json);
|
|
4873
|
+
} else {
|
|
4874
|
+
throw new Error("No base64 encoding method available");
|
|
4875
|
+
}
|
|
4876
|
+
}
|
|
4877
|
+
|
|
3142
4878
|
// src/helpers/wallet-integration.ts
|
|
3143
|
-
var
|
|
4879
|
+
var import_web36 = require("@solana/web3.js");
|
|
3144
4880
|
function validateWalletConnected(wallet) {
|
|
3145
|
-
if (wallet instanceof
|
|
4881
|
+
if (wallet instanceof import_web36.Keypair) {
|
|
3146
4882
|
return;
|
|
3147
4883
|
}
|
|
3148
4884
|
if (!wallet.publicKey) {
|
|
@@ -3154,7 +4890,7 @@ function validateWalletConnected(wallet) {
|
|
|
3154
4890
|
}
|
|
3155
4891
|
}
|
|
3156
4892
|
function getPublicKey(wallet) {
|
|
3157
|
-
if (wallet instanceof
|
|
4893
|
+
if (wallet instanceof import_web36.Keypair) {
|
|
3158
4894
|
return wallet.publicKey;
|
|
3159
4895
|
}
|
|
3160
4896
|
if (!wallet.publicKey) {
|
|
@@ -3167,7 +4903,7 @@ function getPublicKey(wallet) {
|
|
|
3167
4903
|
return wallet.publicKey;
|
|
3168
4904
|
}
|
|
3169
4905
|
async function sendTransaction(transaction, wallet, connection, options) {
|
|
3170
|
-
if (wallet instanceof
|
|
4906
|
+
if (wallet instanceof import_web36.Keypair) {
|
|
3171
4907
|
return await connection.sendTransaction(transaction, [wallet], options);
|
|
3172
4908
|
}
|
|
3173
4909
|
if (wallet.sendTransaction) {
|
|
@@ -3184,7 +4920,7 @@ async function sendTransaction(transaction, wallet, connection, options) {
|
|
|
3184
4920
|
}
|
|
3185
4921
|
}
|
|
3186
4922
|
async function signTransaction(transaction, wallet) {
|
|
3187
|
-
if (wallet instanceof
|
|
4923
|
+
if (wallet instanceof import_web36.Keypair) {
|
|
3188
4924
|
transaction.sign(wallet);
|
|
3189
4925
|
return transaction;
|
|
3190
4926
|
}
|
|
@@ -3648,13 +5384,180 @@ async function preflightCheck(relayUrl, rootHex) {
|
|
|
3648
5384
|
};
|
|
3649
5385
|
}
|
|
3650
5386
|
|
|
5387
|
+
// src/utils/pending-operations.ts
|
|
5388
|
+
var PENDING_DEPOSITS_KEY = "cloak_pending_deposits";
|
|
5389
|
+
var PENDING_WITHDRAWALS_KEY = "cloak_pending_withdrawals";
|
|
5390
|
+
function getStorage() {
|
|
5391
|
+
if (typeof globalThis !== "undefined" && globalThis.localStorage) {
|
|
5392
|
+
return globalThis.localStorage;
|
|
5393
|
+
}
|
|
5394
|
+
return null;
|
|
5395
|
+
}
|
|
5396
|
+
function savePendingDeposit(deposit) {
|
|
5397
|
+
const storage = getStorage();
|
|
5398
|
+
if (!storage) {
|
|
5399
|
+
console.warn("localStorage not available - pending deposit not persisted");
|
|
5400
|
+
return;
|
|
5401
|
+
}
|
|
5402
|
+
const deposits = loadPendingDeposits();
|
|
5403
|
+
const index = deposits.findIndex((d) => d.note.commitment === deposit.note.commitment);
|
|
5404
|
+
if (index >= 0) {
|
|
5405
|
+
deposits[index] = deposit;
|
|
5406
|
+
} else {
|
|
5407
|
+
deposits.push(deposit);
|
|
5408
|
+
}
|
|
5409
|
+
storage.setItem(PENDING_DEPOSITS_KEY, JSON.stringify(deposits));
|
|
5410
|
+
}
|
|
5411
|
+
function loadPendingDeposits() {
|
|
5412
|
+
const storage = getStorage();
|
|
5413
|
+
if (!storage) return [];
|
|
5414
|
+
const stored = storage.getItem(PENDING_DEPOSITS_KEY);
|
|
5415
|
+
if (!stored) return [];
|
|
5416
|
+
try {
|
|
5417
|
+
return JSON.parse(stored);
|
|
5418
|
+
} catch {
|
|
5419
|
+
return [];
|
|
5420
|
+
}
|
|
5421
|
+
}
|
|
5422
|
+
function updatePendingDeposit(commitment, updates) {
|
|
5423
|
+
const storage = getStorage();
|
|
5424
|
+
if (!storage) return;
|
|
5425
|
+
const deposits = loadPendingDeposits();
|
|
5426
|
+
const index = deposits.findIndex((d) => d.note.commitment === commitment);
|
|
5427
|
+
if (index >= 0) {
|
|
5428
|
+
deposits[index] = { ...deposits[index], ...updates };
|
|
5429
|
+
storage.setItem(PENDING_DEPOSITS_KEY, JSON.stringify(deposits));
|
|
5430
|
+
}
|
|
5431
|
+
}
|
|
5432
|
+
function removePendingDeposit(commitment) {
|
|
5433
|
+
const storage = getStorage();
|
|
5434
|
+
if (!storage) return;
|
|
5435
|
+
const deposits = loadPendingDeposits();
|
|
5436
|
+
const filtered = deposits.filter((d) => d.note.commitment !== commitment);
|
|
5437
|
+
storage.setItem(PENDING_DEPOSITS_KEY, JSON.stringify(filtered));
|
|
5438
|
+
}
|
|
5439
|
+
function clearPendingDeposits() {
|
|
5440
|
+
const storage = getStorage();
|
|
5441
|
+
if (storage) {
|
|
5442
|
+
storage.removeItem(PENDING_DEPOSITS_KEY);
|
|
5443
|
+
}
|
|
5444
|
+
}
|
|
5445
|
+
function savePendingWithdrawal(withdrawal) {
|
|
5446
|
+
const storage = getStorage();
|
|
5447
|
+
if (!storage) {
|
|
5448
|
+
console.warn("localStorage not available - pending withdrawal not persisted");
|
|
5449
|
+
return;
|
|
5450
|
+
}
|
|
5451
|
+
const withdrawals = loadPendingWithdrawals();
|
|
5452
|
+
const index = withdrawals.findIndex((w) => w.requestId === withdrawal.requestId);
|
|
5453
|
+
if (index >= 0) {
|
|
5454
|
+
withdrawals[index] = withdrawal;
|
|
5455
|
+
} else {
|
|
5456
|
+
withdrawals.push(withdrawal);
|
|
5457
|
+
}
|
|
5458
|
+
storage.setItem(PENDING_WITHDRAWALS_KEY, JSON.stringify(withdrawals));
|
|
5459
|
+
}
|
|
5460
|
+
function loadPendingWithdrawals() {
|
|
5461
|
+
const storage = getStorage();
|
|
5462
|
+
if (!storage) return [];
|
|
5463
|
+
const stored = storage.getItem(PENDING_WITHDRAWALS_KEY);
|
|
5464
|
+
if (!stored) return [];
|
|
5465
|
+
try {
|
|
5466
|
+
return JSON.parse(stored);
|
|
5467
|
+
} catch {
|
|
5468
|
+
return [];
|
|
5469
|
+
}
|
|
5470
|
+
}
|
|
5471
|
+
function updatePendingWithdrawal(requestId, updates) {
|
|
5472
|
+
const storage = getStorage();
|
|
5473
|
+
if (!storage) return;
|
|
5474
|
+
const withdrawals = loadPendingWithdrawals();
|
|
5475
|
+
const index = withdrawals.findIndex((w) => w.requestId === requestId);
|
|
5476
|
+
if (index >= 0) {
|
|
5477
|
+
withdrawals[index] = { ...withdrawals[index], ...updates };
|
|
5478
|
+
storage.setItem(PENDING_WITHDRAWALS_KEY, JSON.stringify(withdrawals));
|
|
5479
|
+
}
|
|
5480
|
+
}
|
|
5481
|
+
function removePendingWithdrawal(requestId) {
|
|
5482
|
+
const storage = getStorage();
|
|
5483
|
+
if (!storage) return;
|
|
5484
|
+
const withdrawals = loadPendingWithdrawals();
|
|
5485
|
+
const filtered = withdrawals.filter((w) => w.requestId !== requestId);
|
|
5486
|
+
storage.setItem(PENDING_WITHDRAWALS_KEY, JSON.stringify(filtered));
|
|
5487
|
+
}
|
|
5488
|
+
function clearPendingWithdrawals() {
|
|
5489
|
+
const storage = getStorage();
|
|
5490
|
+
if (storage) {
|
|
5491
|
+
storage.removeItem(PENDING_WITHDRAWALS_KEY);
|
|
5492
|
+
}
|
|
5493
|
+
}
|
|
5494
|
+
function hasPendingOperations() {
|
|
5495
|
+
const deposits = loadPendingDeposits();
|
|
5496
|
+
const withdrawals = loadPendingWithdrawals();
|
|
5497
|
+
const pendingDeposits = deposits.filter(
|
|
5498
|
+
(d) => d.status === "pending" || d.status === "tx_sent"
|
|
5499
|
+
);
|
|
5500
|
+
const pendingWithdrawals = withdrawals.filter(
|
|
5501
|
+
(w) => w.status === "pending" || w.status === "processing"
|
|
5502
|
+
);
|
|
5503
|
+
return pendingDeposits.length > 0 || pendingWithdrawals.length > 0;
|
|
5504
|
+
}
|
|
5505
|
+
function getPendingOperationsSummary() {
|
|
5506
|
+
const deposits = loadPendingDeposits().filter(
|
|
5507
|
+
(d) => d.status === "pending" || d.status === "tx_sent"
|
|
5508
|
+
);
|
|
5509
|
+
const withdrawals = loadPendingWithdrawals().filter(
|
|
5510
|
+
(w) => w.status === "pending" || w.status === "processing"
|
|
5511
|
+
);
|
|
5512
|
+
return {
|
|
5513
|
+
deposits,
|
|
5514
|
+
withdrawals,
|
|
5515
|
+
totalPending: deposits.length + withdrawals.length
|
|
5516
|
+
};
|
|
5517
|
+
}
|
|
5518
|
+
function cleanupStalePendingOperations(maxAgeMs = 24 * 60 * 60 * 1e3) {
|
|
5519
|
+
const now = Date.now();
|
|
5520
|
+
let removedDeposits = 0;
|
|
5521
|
+
let removedWithdrawals = 0;
|
|
5522
|
+
const deposits = loadPendingDeposits();
|
|
5523
|
+
const activeDeposits = deposits.filter((d) => {
|
|
5524
|
+
const age = now - d.startedAt;
|
|
5525
|
+
const isStale = age > maxAgeMs;
|
|
5526
|
+
const isTerminal = d.status === "confirmed" || d.status === "failed";
|
|
5527
|
+
if (isStale || isTerminal) {
|
|
5528
|
+
removedDeposits++;
|
|
5529
|
+
return false;
|
|
5530
|
+
}
|
|
5531
|
+
return true;
|
|
5532
|
+
});
|
|
5533
|
+
const storage = getStorage();
|
|
5534
|
+
if (storage) {
|
|
5535
|
+
storage.setItem(PENDING_DEPOSITS_KEY, JSON.stringify(activeDeposits));
|
|
5536
|
+
}
|
|
5537
|
+
const withdrawals = loadPendingWithdrawals();
|
|
5538
|
+
const activeWithdrawals = withdrawals.filter((w) => {
|
|
5539
|
+
const age = now - w.startedAt;
|
|
5540
|
+
const isStale = age > maxAgeMs;
|
|
5541
|
+
const isTerminal = w.status === "completed" || w.status === "failed";
|
|
5542
|
+
if (isStale || isTerminal) {
|
|
5543
|
+
removedWithdrawals++;
|
|
5544
|
+
return false;
|
|
5545
|
+
}
|
|
5546
|
+
return true;
|
|
5547
|
+
});
|
|
5548
|
+
if (storage) {
|
|
5549
|
+
storage.setItem(PENDING_WITHDRAWALS_KEY, JSON.stringify(activeWithdrawals));
|
|
5550
|
+
}
|
|
5551
|
+
return { removedDeposits, removedWithdrawals };
|
|
5552
|
+
}
|
|
5553
|
+
|
|
3651
5554
|
// src/index.ts
|
|
3652
5555
|
init_utxo();
|
|
3653
5556
|
|
|
3654
5557
|
// src/core/transact.ts
|
|
3655
|
-
var
|
|
5558
|
+
var import_web37 = require("@solana/web3.js");
|
|
3656
5559
|
var import_spl_token = require("@solana/spl-token");
|
|
3657
|
-
var
|
|
5560
|
+
var snarkjs2 = __toESM(require("snarkjs"), 1);
|
|
3658
5561
|
init_utxo();
|
|
3659
5562
|
|
|
3660
5563
|
// src/core/chain-note.ts
|
|
@@ -3805,7 +5708,7 @@ function chainNoteFromBase64(base64) {
|
|
|
3805
5708
|
// src/core/transact.ts
|
|
3806
5709
|
var import_circomlibjs4 = require("circomlibjs");
|
|
3807
5710
|
var import_tweetnacl4 = __toESM(require("tweetnacl"), 1);
|
|
3808
|
-
var
|
|
5711
|
+
var IS_BROWSER2 = typeof window !== "undefined" || typeof globalThis.document !== "undefined";
|
|
3809
5712
|
var DEFAULT_TRANSACTION_CIRCUITS_URL = "https://storage.googleapis.com/cloak-circuits/circuits/0.1.0";
|
|
3810
5713
|
var DEFAULT_CLOAK_RELAY_URL = "https://api.cloak.ag";
|
|
3811
5714
|
function resolveDefaultTransactionCircuitsPath() {
|
|
@@ -3818,7 +5721,7 @@ async function resolveTransactionCircuitFiles() {
|
|
|
3818
5721
|
const baseNorm = base.endsWith("/") ? base : `${base}/`;
|
|
3819
5722
|
const wasmUrl = `${baseNorm}transaction_js/transaction.wasm`;
|
|
3820
5723
|
const zkeyUrl = `${baseNorm}transaction_final.zkey`;
|
|
3821
|
-
if (
|
|
5724
|
+
if (IS_BROWSER2) {
|
|
3822
5725
|
return { wasmPath: wasmUrl, zkeyPath: zkeyUrl };
|
|
3823
5726
|
}
|
|
3824
5727
|
if (_remoteTxCircuitsCache?.base === baseNorm) {
|
|
@@ -3955,7 +5858,7 @@ async function resolveAddressLookupTableAccounts(connection, relayUrl, altAddres
|
|
|
3955
5858
|
const fetched = await Promise.all(
|
|
3956
5859
|
altAddresses.map(async (addr) => {
|
|
3957
5860
|
try {
|
|
3958
|
-
const pubkey = new
|
|
5861
|
+
const pubkey = new import_web37.PublicKey(addr);
|
|
3959
5862
|
const result = await connection.getAddressLookupTable(pubkey);
|
|
3960
5863
|
return result.value ?? null;
|
|
3961
5864
|
} catch {
|
|
@@ -4117,7 +6020,7 @@ async function generateTransactionProof(inputs, onProgress) {
|
|
|
4117
6020
|
onProgress?.(10);
|
|
4118
6021
|
const { wasmPath: wasmInput, zkeyPath: zkeyInput } = await resolveTransactionCircuitFiles();
|
|
4119
6022
|
onProgress?.(30);
|
|
4120
|
-
const { proof, publicSignals } = await
|
|
6023
|
+
const { proof, publicSignals } = await snarkjs2.groth16.fullProve(
|
|
4121
6024
|
inputs,
|
|
4122
6025
|
wasmInput,
|
|
4123
6026
|
zkeyInput
|
|
@@ -4197,7 +6100,7 @@ function buildPublicInputsBytesFromSignals(publicSignals) {
|
|
|
4197
6100
|
}
|
|
4198
6101
|
var TRANSACT_DISCRIMINATOR = 0;
|
|
4199
6102
|
function deriveNullifierPDA(programId, poolPDA, nullifier) {
|
|
4200
|
-
return
|
|
6103
|
+
return import_web37.PublicKey.findProgramAddressSync(
|
|
4201
6104
|
[Buffer.from("nullifier"), poolPDA.toBuffer(), Buffer.from(nullifier)],
|
|
4202
6105
|
programId
|
|
4203
6106
|
);
|
|
@@ -4240,7 +6143,7 @@ function buildTransactInstruction(programId, payer, poolPDA, treasuryPDA, merkle
|
|
|
4240
6143
|
// 4. nullifier PDA 0 (writable)
|
|
4241
6144
|
{ pubkey: nullifierPDA1, isSigner: false, isWritable: true },
|
|
4242
6145
|
// 5. nullifier PDA 1 (writable)
|
|
4243
|
-
{ pubkey:
|
|
6146
|
+
{ pubkey: import_web37.SystemProgram.programId, isSigner: false, isWritable: false }
|
|
4244
6147
|
// 6. system_program
|
|
4245
6148
|
];
|
|
4246
6149
|
if (splAccounts) {
|
|
@@ -4249,23 +6152,23 @@ function buildTransactInstruction(programId, payer, poolPDA, treasuryPDA, merkle
|
|
|
4249
6152
|
accounts.push({ pubkey: splAccounts.tokenProgram, isSigner: false, isWritable: false });
|
|
4250
6153
|
if (splAccounts.payerAta) {
|
|
4251
6154
|
if (enableRiskCheck) {
|
|
4252
|
-
accounts.push({ pubkey:
|
|
6155
|
+
accounts.push({ pubkey: import_web37.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
|
|
4253
6156
|
}
|
|
4254
6157
|
accounts.push({ pubkey: splAccounts.payerAta, isSigner: false, isWritable: true });
|
|
4255
6158
|
} else if (enableRiskCheck && !recipient) {
|
|
4256
|
-
accounts.push({ pubkey:
|
|
6159
|
+
accounts.push({ pubkey: import_web37.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
|
|
4257
6160
|
}
|
|
4258
6161
|
} else {
|
|
4259
6162
|
if (recipient) {
|
|
4260
6163
|
accounts.push({ pubkey: recipient, isSigner: false, isWritable: true });
|
|
4261
6164
|
if (enableRiskCheck) {
|
|
4262
|
-
accounts.push({ pubkey:
|
|
6165
|
+
accounts.push({ pubkey: import_web37.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
|
|
4263
6166
|
}
|
|
4264
6167
|
} else if (enableRiskCheck) {
|
|
4265
|
-
accounts.push({ pubkey:
|
|
6168
|
+
accounts.push({ pubkey: import_web37.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
|
|
4266
6169
|
}
|
|
4267
6170
|
}
|
|
4268
|
-
return new
|
|
6171
|
+
return new import_web37.TransactionInstruction({
|
|
4269
6172
|
programId,
|
|
4270
6173
|
keys: accounts,
|
|
4271
6174
|
data
|
|
@@ -4273,10 +6176,10 @@ function buildTransactInstruction(programId, payer, poolPDA, treasuryPDA, merkle
|
|
|
4273
6176
|
}
|
|
4274
6177
|
function getCommonALTAddresses() {
|
|
4275
6178
|
return [
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
6179
|
+
import_web37.SystemProgram.programId,
|
|
6180
|
+
import_web37.SYSVAR_SLOT_HASHES_PUBKEY,
|
|
6181
|
+
import_web37.SYSVAR_INSTRUCTIONS_PUBKEY,
|
|
6182
|
+
import_web37.ComputeBudgetProgram.programId
|
|
4280
6183
|
];
|
|
4281
6184
|
}
|
|
4282
6185
|
function dedupePubkeys(addresses) {
|
|
@@ -4326,23 +6229,23 @@ async function createEphemeralALT(connection, depositor, onProgress, additionalA
|
|
|
4326
6229
|
for (let altAttempt = 0; altAttempt < MAX_ALT_CREATE_RETRIES; altAttempt++) {
|
|
4327
6230
|
try {
|
|
4328
6231
|
const slot = await connection.getSlot("finalized");
|
|
4329
|
-
const [createIx, altAddress] =
|
|
6232
|
+
const [createIx, altAddress] = import_web37.AddressLookupTableProgram.createLookupTable({
|
|
4330
6233
|
authority: depositor.publicKey,
|
|
4331
6234
|
payer: depositor.publicKey,
|
|
4332
6235
|
recentSlot: slot
|
|
4333
6236
|
});
|
|
4334
|
-
const extendIx =
|
|
6237
|
+
const extendIx = import_web37.AddressLookupTableProgram.extendLookupTable({
|
|
4335
6238
|
payer: depositor.publicKey,
|
|
4336
6239
|
authority: depositor.publicKey,
|
|
4337
6240
|
lookupTable: altAddress,
|
|
4338
6241
|
addresses: addressesForCreate
|
|
4339
6242
|
});
|
|
4340
|
-
const tx = new
|
|
6243
|
+
const tx = new import_web37.Transaction().add(createIx).add(extendIx);
|
|
4341
6244
|
const { blockhash } = await connection.getLatestBlockhash();
|
|
4342
6245
|
tx.recentBlockhash = blockhash;
|
|
4343
6246
|
tx.feePayer = depositor.publicKey;
|
|
4344
6247
|
if (depositor.keypair) {
|
|
4345
|
-
await (0,
|
|
6248
|
+
await (0, import_web37.sendAndConfirmTransaction)(connection, tx, [depositor.keypair], { commitment: "confirmed" });
|
|
4346
6249
|
} else if (depositor.signTransaction) {
|
|
4347
6250
|
const signedTx = await depositor.signTransaction(tx);
|
|
4348
6251
|
const sig = await connection.sendRawTransaction(signedTx.serialize());
|
|
@@ -4503,8 +6406,8 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
|
|
|
4503
6406
|
if (riskQuoteIx) {
|
|
4504
6407
|
baseInstructions.unshift(riskQuoteIx);
|
|
4505
6408
|
}
|
|
4506
|
-
const cuLimitIx =
|
|
4507
|
-
const cuPriceIx =
|
|
6409
|
+
const cuLimitIx = import_web37.ComputeBudgetProgram.setComputeUnitLimit({ units: 4e5 });
|
|
6410
|
+
const cuPriceIx = import_web37.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1e5 });
|
|
4508
6411
|
const fullInstructions = [...baseInstructions, cuLimitIx, cuPriceIx, transactIx];
|
|
4509
6412
|
const compactInstructions = [...baseInstructions, cuLimitIx, transactIx];
|
|
4510
6413
|
const minimalInstructions = [...baseInstructions, transactIx];
|
|
@@ -4515,7 +6418,7 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
|
|
|
4515
6418
|
if (useV0) {
|
|
4516
6419
|
const estimateV0Size = (ixs) => {
|
|
4517
6420
|
try {
|
|
4518
|
-
const messageV0 = new
|
|
6421
|
+
const messageV0 = new import_web37.TransactionMessage({
|
|
4519
6422
|
payerKey: depositor.publicKey,
|
|
4520
6423
|
recentBlockhash: blockhash,
|
|
4521
6424
|
instructions: ixs
|
|
@@ -4566,6 +6469,38 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
|
|
|
4566
6469
|
const sendV0 = async (ixs, options) => {
|
|
4567
6470
|
const maxBlockhashRetries = 2;
|
|
4568
6471
|
const maxTransportRetries = 3;
|
|
6472
|
+
const alreadyLanded = async (sig) => {
|
|
6473
|
+
if (!sig) return false;
|
|
6474
|
+
for (let i = 0; i < 6; i++) {
|
|
6475
|
+
try {
|
|
6476
|
+
const { value } = await connection.getSignatureStatus(sig, {
|
|
6477
|
+
searchTransactionHistory: true
|
|
6478
|
+
});
|
|
6479
|
+
if (value) {
|
|
6480
|
+
if (value.err) return false;
|
|
6481
|
+
if (value.confirmationStatus === "confirmed" || value.confirmationStatus === "finalized") {
|
|
6482
|
+
return true;
|
|
6483
|
+
}
|
|
6484
|
+
}
|
|
6485
|
+
} catch {
|
|
6486
|
+
}
|
|
6487
|
+
await new Promise((r) => setTimeout(r, 400));
|
|
6488
|
+
}
|
|
6489
|
+
return false;
|
|
6490
|
+
};
|
|
6491
|
+
const looksLikeDoubleSpend = (error) => {
|
|
6492
|
+
const m = error instanceof Error ? error.message : String(error);
|
|
6493
|
+
return m.includes("0x1020") || // confirmTransaction().value.err is JSON-stringified, so 0x1020 appears as
|
|
6494
|
+
// its decimal form ("Custom":4128) rather than the hex "0x1020".
|
|
6495
|
+
m.includes('"Custom":4128') || m.includes("already been processed") || m.toLowerCase().includes("doublespend");
|
|
6496
|
+
};
|
|
6497
|
+
const sentSigs = [];
|
|
6498
|
+
const anyLanded = async () => {
|
|
6499
|
+
for (const s of sentSigs) {
|
|
6500
|
+
if (await alreadyLanded(s)) return s;
|
|
6501
|
+
}
|
|
6502
|
+
return void 0;
|
|
6503
|
+
};
|
|
4569
6504
|
for (let blockhashRetry = 0; blockhashRetry <= maxBlockhashRetries; blockhashRetry++) {
|
|
4570
6505
|
if (blockhashRetry > 0) {
|
|
4571
6506
|
({ blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash());
|
|
@@ -4575,12 +6510,12 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
|
|
|
4575
6510
|
}
|
|
4576
6511
|
let transportAttempt = 0;
|
|
4577
6512
|
while (true) {
|
|
4578
|
-
const messageV0 = new
|
|
6513
|
+
const messageV0 = new import_web37.TransactionMessage({
|
|
4579
6514
|
payerKey: depositor.publicKey,
|
|
4580
6515
|
recentBlockhash: blockhash,
|
|
4581
6516
|
instructions: ixs
|
|
4582
6517
|
}).compileToV0Message(addressLookupTableAccounts);
|
|
4583
|
-
const versionedTx = new
|
|
6518
|
+
const versionedTx = new import_web37.VersionedTransaction(messageV0);
|
|
4584
6519
|
try {
|
|
4585
6520
|
if (depositor.keypair) {
|
|
4586
6521
|
onProgress?.("Signing and sending transaction...");
|
|
@@ -4588,6 +6523,7 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
|
|
|
4588
6523
|
signature = await connection.sendRawTransaction(versionedTx.serialize(), {
|
|
4589
6524
|
skipPreflight: options?.skipPreflight ?? false
|
|
4590
6525
|
});
|
|
6526
|
+
sentSigs.push(signature);
|
|
4591
6527
|
onProgress?.("Confirming transaction...");
|
|
4592
6528
|
const confirmation = await connection.confirmTransaction({
|
|
4593
6529
|
signature,
|
|
@@ -4604,6 +6540,7 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
|
|
|
4604
6540
|
signature = await connection.sendRawTransaction(signedTx.serialize(), {
|
|
4605
6541
|
skipPreflight: options?.skipPreflight ?? false
|
|
4606
6542
|
});
|
|
6543
|
+
sentSigs.push(signature);
|
|
4607
6544
|
onProgress?.("Confirming transaction...");
|
|
4608
6545
|
const confirmation = await connection.confirmTransaction({
|
|
4609
6546
|
signature,
|
|
@@ -4618,8 +6555,10 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
|
|
|
4618
6555
|
}
|
|
4619
6556
|
return signature;
|
|
4620
6557
|
} catch (error) {
|
|
4621
|
-
if (isBlockhashExpiryError(error)
|
|
4622
|
-
|
|
6558
|
+
if (isBlockhashExpiryError(error)) {
|
|
6559
|
+
const landed = await anyLanded();
|
|
6560
|
+
if (landed) return landed;
|
|
6561
|
+
if (blockhashRetry < maxBlockhashRetries) break;
|
|
4623
6562
|
}
|
|
4624
6563
|
if (isTransientTransportError(error) && transportAttempt < maxTransportRetries) {
|
|
4625
6564
|
transportAttempt++;
|
|
@@ -4631,6 +6570,10 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
|
|
|
4631
6570
|
({ blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash());
|
|
4632
6571
|
continue;
|
|
4633
6572
|
}
|
|
6573
|
+
if (looksLikeDoubleSpend(error)) {
|
|
6574
|
+
const landed = await anyLanded();
|
|
6575
|
+
if (landed) return landed;
|
|
6576
|
+
}
|
|
4634
6577
|
throw error;
|
|
4635
6578
|
}
|
|
4636
6579
|
}
|
|
@@ -4713,7 +6656,7 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
|
|
|
4713
6656
|
let legacyTransportAttempt = 0;
|
|
4714
6657
|
while (true) {
|
|
4715
6658
|
try {
|
|
4716
|
-
const tx = new
|
|
6659
|
+
const tx = new import_web37.Transaction();
|
|
4717
6660
|
for (const ix of instructions) {
|
|
4718
6661
|
tx.add(ix);
|
|
4719
6662
|
}
|
|
@@ -4721,7 +6664,7 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
|
|
|
4721
6664
|
tx.feePayer = depositor.publicKey;
|
|
4722
6665
|
if (depositor.keypair) {
|
|
4723
6666
|
onProgress?.("Signing and sending transaction...");
|
|
4724
|
-
signature = await (0,
|
|
6667
|
+
signature = await (0, import_web37.sendAndConfirmTransaction)(
|
|
4725
6668
|
connection,
|
|
4726
6669
|
tx,
|
|
4727
6670
|
[depositor.keypair],
|
|
@@ -4895,7 +6838,7 @@ async function fetchRiskQuoteInstruction(riskQuoteUrl, wallet, options) {
|
|
|
4895
6838
|
const signerB58 = signerKey;
|
|
4896
6839
|
const signatureBuf = Buffer.from(signatureB64, "base64");
|
|
4897
6840
|
const messageArr = hexToBytes2(messageHex);
|
|
4898
|
-
const publicKeyBytes = new
|
|
6841
|
+
const publicKeyBytes = new import_web37.PublicKey(signerB58).toBytes();
|
|
4899
6842
|
if (signatureBuf.length !== 64 || messageArr.length !== 33 && messageArr.length !== 41 && messageArr.length !== 50 || publicKeyBytes.length !== 32) {
|
|
4900
6843
|
throw new Error(
|
|
4901
6844
|
`Invalid risk quote response: invalid lengths (sig=${signatureBuf.length}, msg=${messageArr.length}, pk=${publicKeyBytes.length})`
|
|
@@ -4904,7 +6847,7 @@ async function fetchRiskQuoteInstruction(riskQuoteUrl, wallet, options) {
|
|
|
4904
6847
|
const signature = new Uint8Array(signatureBuf);
|
|
4905
6848
|
const message = new Uint8Array(messageArr);
|
|
4906
6849
|
const publicKey = new Uint8Array(publicKeyBytes);
|
|
4907
|
-
return
|
|
6850
|
+
return import_web37.Ed25519Program.createInstructionWithPublicKey({
|
|
4908
6851
|
publicKey,
|
|
4909
6852
|
message,
|
|
4910
6853
|
signature
|
|
@@ -4917,10 +6860,10 @@ async function fetchRiskQuoteInstruction(riskQuoteUrl, wallet, options) {
|
|
|
4917
6860
|
`Invalid risk quote response: expected relay format { signature, message, signer_pubkey } or { instruction }. Received keys: ${keys}`
|
|
4918
6861
|
);
|
|
4919
6862
|
}
|
|
4920
|
-
return new
|
|
4921
|
-
programId: new
|
|
6863
|
+
return new import_web37.TransactionInstruction({
|
|
6864
|
+
programId: new import_web37.PublicKey(raw.programId),
|
|
4922
6865
|
keys: raw.keys.map((k) => ({
|
|
4923
|
-
pubkey: new
|
|
6866
|
+
pubkey: new import_web37.PublicKey(k.pubkey),
|
|
4924
6867
|
isSigner: k.isSigner,
|
|
4925
6868
|
isWritable: k.isWritable
|
|
4926
6869
|
})),
|
|
@@ -5290,7 +7233,7 @@ async function transact(params, options) {
|
|
|
5290
7233
|
const outputCommitments = [];
|
|
5291
7234
|
for (let i = 0; i < paddedOutputs.length; i++) {
|
|
5292
7235
|
const utxo = paddedOutputs[i];
|
|
5293
|
-
const commitment = await
|
|
7236
|
+
const commitment = await computeCommitment2(utxo);
|
|
5294
7237
|
outputCommitments.push(commitment);
|
|
5295
7238
|
}
|
|
5296
7239
|
onProgress?.("Computing external data hash...");
|
|
@@ -5371,7 +7314,7 @@ async function transact(params, options) {
|
|
|
5371
7314
|
}
|
|
5372
7315
|
inputNullifiers = [];
|
|
5373
7316
|
for (const utxo of paddedInputs) {
|
|
5374
|
-
inputNullifiers.push(await
|
|
7317
|
+
inputNullifiers.push(await computeNullifier2(utxo));
|
|
5375
7318
|
}
|
|
5376
7319
|
if (nextIndex !== null) {
|
|
5377
7320
|
onProgress?.(`[Submit ${submissionAttempts + 1}/${maxRootRetries + 1}] Root: ${rootHex.substring(0, 16)}..., nextIndex: ${nextIndex}`);
|
|
@@ -5804,12 +7747,12 @@ async function transact(params, options) {
|
|
|
5804
7747
|
if (lastError) {
|
|
5805
7748
|
const classified = classifyRelayError(lastError.message);
|
|
5806
7749
|
const isMerkleClass = !(classified instanceof UtxoAlreadySpentError) && !(classified instanceof SanctionsQuoteError) && !isRootNotFoundError2(lastError);
|
|
5807
|
-
if (!
|
|
7750
|
+
if (!IS_BROWSER2 && isMerkleClass && relayUrl) {
|
|
5808
7751
|
onProgress?.(
|
|
5809
7752
|
"All relay-tree attempts failed. Rebuilding merkle tree from chain as last resort..."
|
|
5810
7753
|
);
|
|
5811
7754
|
try {
|
|
5812
|
-
const [merkleTreePda] =
|
|
7755
|
+
const [merkleTreePda] = import_web37.PublicKey.findProgramAddressSync(
|
|
5813
7756
|
[Buffer.from("merkle_tree"), mint.toBuffer()],
|
|
5814
7757
|
programId
|
|
5815
7758
|
);
|
|
@@ -6453,7 +8396,7 @@ async function swapUtxo(params, options) {
|
|
|
6453
8396
|
let inputNullifiers = [];
|
|
6454
8397
|
const outputCommitments = [];
|
|
6455
8398
|
for (const utxo of paddedOutputs) {
|
|
6456
|
-
outputCommitments.push(await
|
|
8399
|
+
outputCommitments.push(await computeCommitment2(utxo));
|
|
6457
8400
|
}
|
|
6458
8401
|
const chainNoteTimestamp = BigInt(Date.now());
|
|
6459
8402
|
onProgress?.("Computing external data hash...");
|
|
@@ -6533,7 +8476,7 @@ async function swapUtxo(params, options) {
|
|
|
6533
8476
|
}
|
|
6534
8477
|
inputNullifiers = [];
|
|
6535
8478
|
for (const utxo of paddedInputs) {
|
|
6536
|
-
inputNullifiers.push(await
|
|
8479
|
+
inputNullifiers.push(await computeNullifier2(utxo));
|
|
6537
8480
|
}
|
|
6538
8481
|
if (merkleState) {
|
|
6539
8482
|
onProgress?.(`[Attempt ${attempt + 1}/${maxRootRetries + 1}] Root: ${rootHex.substring(0, 16)}..., nextIndex: ${merkleState.nextIndex}`);
|
|
@@ -6877,7 +8820,7 @@ async function swapWithChange(inputUtxos, swapAmount, outputMint, recipientAta,
|
|
|
6877
8820
|
|
|
6878
8821
|
// src/core/scanner.ts
|
|
6879
8822
|
var import_bs582 = __toESM(require("bs58"), 1);
|
|
6880
|
-
var
|
|
8823
|
+
var import_web38 = require("@solana/web3.js");
|
|
6881
8824
|
var import_spl_token2 = require("@solana/spl-token");
|
|
6882
8825
|
var bs582 = import_bs582.default.default || import_bs582.default;
|
|
6883
8826
|
var TRANSACT_TAG2 = 0;
|
|
@@ -6964,7 +8907,7 @@ function parseSwapOutputMint(data) {
|
|
|
6964
8907
|
const mintBytes = data.slice(SWAP_OUTPUT_MINT_OFFSET, end);
|
|
6965
8908
|
if (mintBytes.every((byte) => byte === 0)) return void 0;
|
|
6966
8909
|
try {
|
|
6967
|
-
return new
|
|
8910
|
+
return new import_web38.PublicKey(mintBytes).toBase58();
|
|
6968
8911
|
} catch {
|
|
6969
8912
|
return void 0;
|
|
6970
8913
|
}
|
|
@@ -7054,7 +8997,7 @@ function parseSwapRecipientAta(data) {
|
|
|
7054
8997
|
const recipientAtaBytes = data.slice(recipientAtaStart, recipientAtaEnd);
|
|
7055
8998
|
if (recipientAtaBytes.every((byte) => byte === 0)) return void 0;
|
|
7056
8999
|
try {
|
|
7057
|
-
return new
|
|
9000
|
+
return new import_web38.PublicKey(recipientAtaBytes).toBase58();
|
|
7058
9001
|
} catch {
|
|
7059
9002
|
return void 0;
|
|
7060
9003
|
}
|
|
@@ -7369,8 +9312,8 @@ async function scanTransactions(opts) {
|
|
|
7369
9312
|
if (onChainAta) {
|
|
7370
9313
|
try {
|
|
7371
9314
|
const expectedAta = (0, import_spl_token2.getAssociatedTokenAddressSync)(
|
|
7372
|
-
new
|
|
7373
|
-
new
|
|
9315
|
+
new import_web38.PublicKey(asset.mint),
|
|
9316
|
+
new import_web38.PublicKey(walletPublicKey)
|
|
7374
9317
|
).toBase58();
|
|
7375
9318
|
if (onChainAta === expectedAta) {
|
|
7376
9319
|
isOurs = true;
|
|
@@ -7673,7 +9616,7 @@ function formatComplianceCsv(report) {
|
|
|
7673
9616
|
}
|
|
7674
9617
|
|
|
7675
9618
|
// src/core/wallet.ts
|
|
7676
|
-
var
|
|
9619
|
+
var import_web39 = require("@solana/web3.js");
|
|
7677
9620
|
init_utxo();
|
|
7678
9621
|
var UtxoWallet = class _UtxoWallet {
|
|
7679
9622
|
constructor(viewingKey) {
|
|
@@ -7886,7 +9829,7 @@ var UtxoWallet = class _UtxoWallet {
|
|
|
7886
9829
|
data.viewingKey ? new Uint8Array(data.viewingKey) : void 0
|
|
7887
9830
|
);
|
|
7888
9831
|
for (const w of data.wallets) {
|
|
7889
|
-
const mint = new
|
|
9832
|
+
const mint = new import_web39.PublicKey(w.mint);
|
|
7890
9833
|
for (const u of w.utxos) {
|
|
7891
9834
|
wallet.addUtxo({
|
|
7892
9835
|
amount: BigInt(u.amount),
|
|
@@ -7969,14 +9912,16 @@ var SimpleWallet = class {
|
|
|
7969
9912
|
};
|
|
7970
9913
|
|
|
7971
9914
|
// src/index.ts
|
|
7972
|
-
var VERSION = "0.1.
|
|
9915
|
+
var VERSION = "0.1.5";
|
|
7973
9916
|
var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
7974
9917
|
// Annotate the CommonJS export names for ESM import in node:
|
|
7975
9918
|
0 && (module.exports = {
|
|
7976
9919
|
CLOAK_PROGRAM_ID,
|
|
7977
9920
|
CloakError,
|
|
7978
9921
|
CloakSDK,
|
|
9922
|
+
DEFAULT_CIRCUITS_URL,
|
|
7979
9923
|
DEFAULT_TRANSACTION_CIRCUITS_URL,
|
|
9924
|
+
EXPECTED_CIRCUIT_HASHES,
|
|
7980
9925
|
FIXED_FEE_LAMPORTS,
|
|
7981
9926
|
LAMPORTS_PER_SOL,
|
|
7982
9927
|
LocalStorageAdapter,
|
|
@@ -7999,6 +9944,7 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
|
7999
9944
|
VARIABLE_FEE_NUMERATOR,
|
|
8000
9945
|
VARIABLE_FEE_RATE,
|
|
8001
9946
|
VERSION,
|
|
9947
|
+
areCircuitsAvailable,
|
|
8002
9948
|
bigintToBytes32,
|
|
8003
9949
|
bigintToHex,
|
|
8004
9950
|
buildMerkleTree,
|
|
@@ -8012,15 +9958,30 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
|
8012
9958
|
chainNoteFromBase64,
|
|
8013
9959
|
chainNoteToBase64,
|
|
8014
9960
|
classifyRelayError,
|
|
9961
|
+
cleanupStalePendingOperations,
|
|
9962
|
+
clearPendingDeposits,
|
|
9963
|
+
clearPendingWithdrawals,
|
|
8015
9964
|
computeChainNoteHash,
|
|
9965
|
+
computeCommitment,
|
|
8016
9966
|
computeExtDataHash,
|
|
8017
9967
|
computeMerkleRoot,
|
|
9968
|
+
computeNullifier,
|
|
9969
|
+
computeNullifierAsync,
|
|
9970
|
+
computeNullifierSync,
|
|
9971
|
+
computeOutputsHash,
|
|
9972
|
+
computeOutputsHashAsync,
|
|
9973
|
+
computeOutputsHashSync,
|
|
8018
9974
|
computeProofForLatestDeposit,
|
|
8019
9975
|
computeProofFromChain,
|
|
8020
9976
|
computeSignature,
|
|
9977
|
+
computeSwapOutputsHash,
|
|
9978
|
+
computeSwapOutputsHashAsync,
|
|
9979
|
+
computeSwapOutputsHashSync,
|
|
8021
9980
|
computeUtxoCommitment,
|
|
8022
9981
|
computeUtxoNullifier,
|
|
9982
|
+
copyNoteToClipboard,
|
|
8023
9983
|
createCloakError,
|
|
9984
|
+
createDepositInstruction,
|
|
8024
9985
|
createLogger,
|
|
8025
9986
|
createUtxo,
|
|
8026
9987
|
createZeroUtxo,
|
|
@@ -8040,52 +10001,78 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
|
8040
10001
|
deriveViewingKeyFromUtxoPrivateKey,
|
|
8041
10002
|
deserializeUtxo,
|
|
8042
10003
|
detectNetworkFromRpcUrl,
|
|
10004
|
+
downloadNote,
|
|
10005
|
+
encodeNoteSimple,
|
|
8043
10006
|
encryptCompactChainNote,
|
|
8044
10007
|
encryptNoteForRecipient,
|
|
8045
10008
|
encryptTransactionMetadata,
|
|
8046
10009
|
encryptTransactionMetadataBundle,
|
|
8047
10010
|
expandSpendKey,
|
|
8048
10011
|
exportKeys,
|
|
10012
|
+
exportNote,
|
|
10013
|
+
exportWalletKeys,
|
|
8049
10014
|
fetchCommitments,
|
|
8050
10015
|
fetchRiskQuoteInstruction,
|
|
8051
10016
|
fetchRiskQuoteIx,
|
|
10017
|
+
filterNotesByNetwork,
|
|
10018
|
+
filterWithdrawableNotes,
|
|
10019
|
+
findNoteByCommitment,
|
|
8052
10020
|
formatAmount,
|
|
8053
10021
|
formatComplianceCsv,
|
|
8054
10022
|
formatErrorForLogging,
|
|
8055
10023
|
formatSol,
|
|
8056
10024
|
fullWithdraw,
|
|
8057
10025
|
generateCloakKeys,
|
|
10026
|
+
generateCommitment,
|
|
10027
|
+
generateCommitmentAsync,
|
|
8058
10028
|
generateMasterSeed,
|
|
10029
|
+
generateNote,
|
|
10030
|
+
generateNoteFromWallet,
|
|
8059
10031
|
generateUtxoKeypair,
|
|
8060
10032
|
generateViewingKeyPair,
|
|
10033
|
+
generateWithdrawRegularProof,
|
|
10034
|
+
generateWithdrawSwapProof,
|
|
8061
10035
|
getAddressExplorerUrl,
|
|
8062
10036
|
getCircuitsPath,
|
|
10037
|
+
getDefaultCircuitsPath,
|
|
8063
10038
|
getDistributableAmount,
|
|
8064
10039
|
getExplorerUrl,
|
|
8065
10040
|
getNkFromUtxoPrivateKey,
|
|
8066
10041
|
getNullifierPDA,
|
|
10042
|
+
getPendingOperationsSummary,
|
|
8067
10043
|
getPublicKey,
|
|
10044
|
+
getPublicViewKey,
|
|
10045
|
+
getRecipientAmount,
|
|
8068
10046
|
getRpcUrlForNetwork,
|
|
8069
10047
|
getShieldPoolPDAs,
|
|
8070
10048
|
getSwapStatePDA,
|
|
10049
|
+
getViewKey,
|
|
10050
|
+
hasPendingOperations,
|
|
8071
10051
|
hexToBigint,
|
|
8072
10052
|
hexToBytes,
|
|
8073
10053
|
importKeys,
|
|
10054
|
+
importWalletKeys,
|
|
8074
10055
|
isDebugEnabled,
|
|
8075
10056
|
isRootNotFoundError,
|
|
8076
10057
|
isValidHex,
|
|
8077
10058
|
isValidRpcUrl,
|
|
8078
10059
|
isValidSolanaAddress,
|
|
8079
10060
|
isWithdrawAmountSufficient,
|
|
10061
|
+
isWithdrawable,
|
|
8080
10062
|
keypairToAdapter,
|
|
10063
|
+
loadPendingDeposits,
|
|
10064
|
+
loadPendingWithdrawals,
|
|
8081
10065
|
parseAmount,
|
|
8082
10066
|
parseError,
|
|
10067
|
+
parseNote,
|
|
8083
10068
|
parseRelayErrorResponse,
|
|
8084
10069
|
parseTransactionError,
|
|
8085
10070
|
partialWithdraw,
|
|
8086
10071
|
poseidonHash,
|
|
8087
10072
|
preflightCheck,
|
|
8088
10073
|
preflightNullifiers,
|
|
10074
|
+
prepareEncryptedOutput,
|
|
10075
|
+
prepareEncryptedOutputForRecipient,
|
|
8089
10076
|
proofToBytes,
|
|
8090
10077
|
pubkeyToFieldElement,
|
|
8091
10078
|
pubkeyToLimbs,
|
|
@@ -8093,11 +10080,16 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
|
8093
10080
|
randomFieldElement,
|
|
8094
10081
|
readMerkleTreeState,
|
|
8095
10082
|
registerViewingKey,
|
|
10083
|
+
removePendingDeposit,
|
|
10084
|
+
removePendingWithdrawal,
|
|
10085
|
+
savePendingDeposit,
|
|
10086
|
+
savePendingWithdrawal,
|
|
8096
10087
|
scanNotesForWallet,
|
|
8097
10088
|
scanTransactions,
|
|
8098
10089
|
sdkLogger,
|
|
8099
10090
|
selectUtxos,
|
|
8100
10091
|
sendTransaction,
|
|
10092
|
+
serializeNote,
|
|
8101
10093
|
serializeUtxo,
|
|
8102
10094
|
setCircuitsPath,
|
|
8103
10095
|
setDebugMode,
|
|
@@ -8111,12 +10103,21 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
|
8111
10103
|
transfer,
|
|
8112
10104
|
truncate,
|
|
8113
10105
|
tryDecryptNote,
|
|
10106
|
+
updateNoteWithDeposit,
|
|
10107
|
+
updatePendingDeposit,
|
|
10108
|
+
updatePendingWithdrawal,
|
|
8114
10109
|
utxoBigintToBytes32,
|
|
8115
10110
|
utxoEquals,
|
|
8116
10111
|
utxoHexToBigint,
|
|
10112
|
+
validateDepositParams,
|
|
10113
|
+
validateNote,
|
|
8117
10114
|
validateOutputsSum,
|
|
8118
10115
|
validateRoot,
|
|
10116
|
+
validateTransfers,
|
|
8119
10117
|
validateWalletConnected,
|
|
10118
|
+
validateWithdrawableNote,
|
|
10119
|
+
verifyAllCircuits,
|
|
10120
|
+
verifyCircuitIntegrity,
|
|
8120
10121
|
verifyUtxos,
|
|
8121
10122
|
waitForRoot,
|
|
8122
10123
|
withTiming
|