@cloak.dev/sdk 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +173 -0
- package/README.md +183 -96
- package/dist/index.cjs +472 -2258
- package/dist/index.d.cts +1283 -2218
- package/dist/index.d.ts +1283 -2218
- package/dist/index.js +429 -2171
- package/package.json +2 -1
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: () => computeCommitment,
|
|
40
|
+
computeNullifier: () => computeNullifier,
|
|
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 computeCommitment(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 computeCommitment(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 computeCommitment(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 computeNullifier(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 computeCommitment(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_web3.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 computeCommitment(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 computeCommitment(a);
|
|
251
|
+
const commitmentB = b.commitment ?? await computeCommitment(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_web3, 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_web3 = 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_web3.PublicKey("So11111111111111111111111111111111111111112");
|
|
283
283
|
poseidonInstance = null;
|
|
284
284
|
}
|
|
285
285
|
});
|
|
@@ -290,9 +290,7 @@ __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,
|
|
294
293
|
DEFAULT_TRANSACTION_CIRCUITS_URL: () => DEFAULT_TRANSACTION_CIRCUITS_URL,
|
|
295
|
-
EXPECTED_CIRCUIT_HASHES: () => EXPECTED_CIRCUIT_HASHES,
|
|
296
294
|
FIXED_FEE_LAMPORTS: () => FIXED_FEE_LAMPORTS,
|
|
297
295
|
LAMPORTS_PER_SOL: () => LAMPORTS_PER_SOL,
|
|
298
296
|
LocalStorageAdapter: () => LocalStorageAdapter,
|
|
@@ -301,18 +299,20 @@ __export(index_exports, {
|
|
|
301
299
|
MemoryStorageAdapter: () => MemoryStorageAdapter,
|
|
302
300
|
MerkleTree: () => MerkleTree,
|
|
303
301
|
NATIVE_SOL_MINT: () => NATIVE_SOL_MINT,
|
|
302
|
+
RelayInternalError: () => RelayInternalError,
|
|
304
303
|
RelayService: () => RelayService,
|
|
305
304
|
RootNotFoundError: () => RootNotFoundError,
|
|
306
305
|
SCANNER_SUPPORTS_TRANSACT_SWAP: () => SCANNER_SUPPORTS_TRANSACT_SWAP,
|
|
307
306
|
SIGN_IN_MESSAGE: () => SIGN_IN_MESSAGE,
|
|
307
|
+
SanctionsQuoteError: () => SanctionsQuoteError,
|
|
308
308
|
ShieldPoolErrors: () => ShieldPoolErrors,
|
|
309
309
|
SimpleWallet: () => SimpleWallet,
|
|
310
|
+
UtxoAlreadySpentError: () => UtxoAlreadySpentError,
|
|
310
311
|
UtxoWallet: () => UtxoWallet,
|
|
311
312
|
VARIABLE_FEE_DENOMINATOR: () => VARIABLE_FEE_DENOMINATOR,
|
|
312
313
|
VARIABLE_FEE_NUMERATOR: () => VARIABLE_FEE_NUMERATOR,
|
|
313
314
|
VARIABLE_FEE_RATE: () => VARIABLE_FEE_RATE,
|
|
314
315
|
VERSION: () => VERSION,
|
|
315
|
-
areCircuitsAvailable: () => areCircuitsAvailable,
|
|
316
316
|
bigintToBytes32: () => bigintToBytes32,
|
|
317
317
|
bigintToHex: () => bigintToHex,
|
|
318
318
|
buildMerkleTree: () => buildMerkleTree,
|
|
@@ -325,30 +325,16 @@ __export(index_exports, {
|
|
|
325
325
|
calculateRelayFee: () => calculateRelayFee,
|
|
326
326
|
chainNoteFromBase64: () => chainNoteFromBase64,
|
|
327
327
|
chainNoteToBase64: () => chainNoteToBase64,
|
|
328
|
-
|
|
329
|
-
clearPendingDeposits: () => clearPendingDeposits,
|
|
330
|
-
clearPendingWithdrawals: () => clearPendingWithdrawals,
|
|
328
|
+
classifyRelayError: () => classifyRelayError,
|
|
331
329
|
computeChainNoteHash: () => computeChainNoteHash,
|
|
332
|
-
computeCommitment: () => computeCommitment,
|
|
333
330
|
computeExtDataHash: () => computeExtDataHash,
|
|
334
331
|
computeMerkleRoot: () => computeMerkleRoot,
|
|
335
|
-
computeNullifier: () => computeNullifier,
|
|
336
|
-
computeNullifierAsync: () => computeNullifierAsync,
|
|
337
|
-
computeNullifierSync: () => computeNullifierSync,
|
|
338
|
-
computeOutputsHash: () => computeOutputsHash,
|
|
339
|
-
computeOutputsHashAsync: () => computeOutputsHashAsync,
|
|
340
|
-
computeOutputsHashSync: () => computeOutputsHashSync,
|
|
341
332
|
computeProofForLatestDeposit: () => computeProofForLatestDeposit,
|
|
342
333
|
computeProofFromChain: () => computeProofFromChain,
|
|
343
334
|
computeSignature: () => computeSignature,
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
computeSwapOutputsHashSync: () => computeSwapOutputsHashSync,
|
|
347
|
-
computeUtxoCommitment: () => computeCommitment2,
|
|
348
|
-
computeUtxoNullifier: () => computeNullifier2,
|
|
349
|
-
copyNoteToClipboard: () => copyNoteToClipboard,
|
|
335
|
+
computeUtxoCommitment: () => computeCommitment,
|
|
336
|
+
computeUtxoNullifier: () => computeNullifier,
|
|
350
337
|
createCloakError: () => createCloakError,
|
|
351
|
-
createDepositInstruction: () => createDepositInstruction,
|
|
352
338
|
createLogger: () => createLogger,
|
|
353
339
|
createUtxo: () => createUtxo,
|
|
354
340
|
createZeroUtxo: () => createZeroUtxo,
|
|
@@ -368,77 +354,52 @@ __export(index_exports, {
|
|
|
368
354
|
deriveViewingKeyFromUtxoPrivateKey: () => deriveViewingKeyFromUtxoPrivateKey,
|
|
369
355
|
deserializeUtxo: () => deserializeUtxo,
|
|
370
356
|
detectNetworkFromRpcUrl: () => detectNetworkFromRpcUrl,
|
|
371
|
-
downloadNote: () => downloadNote,
|
|
372
|
-
encodeNoteSimple: () => encodeNoteSimple,
|
|
373
357
|
encryptCompactChainNote: () => encryptCompactChainNote,
|
|
374
358
|
encryptNoteForRecipient: () => encryptNoteForRecipient,
|
|
375
359
|
encryptTransactionMetadata: () => encryptTransactionMetadata,
|
|
376
360
|
encryptTransactionMetadataBundle: () => encryptTransactionMetadataBundle,
|
|
377
361
|
expandSpendKey: () => expandSpendKey,
|
|
378
362
|
exportKeys: () => exportKeys,
|
|
379
|
-
exportNote: () => exportNote,
|
|
380
|
-
exportWalletKeys: () => exportWalletKeys,
|
|
381
363
|
fetchCommitments: () => fetchCommitments,
|
|
382
364
|
fetchRiskQuoteInstruction: () => fetchRiskQuoteInstruction,
|
|
383
365
|
fetchRiskQuoteIx: () => fetchRiskQuoteIx,
|
|
384
|
-
filterNotesByNetwork: () => filterNotesByNetwork,
|
|
385
|
-
filterWithdrawableNotes: () => filterWithdrawableNotes,
|
|
386
|
-
findNoteByCommitment: () => findNoteByCommitment,
|
|
387
366
|
formatAmount: () => formatAmount,
|
|
388
367
|
formatComplianceCsv: () => formatComplianceCsv,
|
|
389
368
|
formatErrorForLogging: () => formatErrorForLogging,
|
|
390
369
|
formatSol: () => formatSol,
|
|
391
370
|
fullWithdraw: () => fullWithdraw,
|
|
392
371
|
generateCloakKeys: () => generateCloakKeys,
|
|
393
|
-
generateCommitment: () => generateCommitment,
|
|
394
|
-
generateCommitmentAsync: () => generateCommitmentAsync,
|
|
395
372
|
generateMasterSeed: () => generateMasterSeed,
|
|
396
|
-
generateNote: () => generateNote,
|
|
397
|
-
generateNoteFromWallet: () => generateNoteFromWallet,
|
|
398
373
|
generateUtxoKeypair: () => generateUtxoKeypair,
|
|
399
374
|
generateViewingKeyPair: () => generateViewingKeyPair,
|
|
400
|
-
generateWithdrawRegularProof: () => generateWithdrawRegularProof,
|
|
401
|
-
generateWithdrawSwapProof: () => generateWithdrawSwapProof,
|
|
402
375
|
getAddressExplorerUrl: () => getAddressExplorerUrl,
|
|
403
376
|
getCircuitsPath: () => getCircuitsPath,
|
|
404
|
-
getDefaultCircuitsPath: () => getDefaultCircuitsPath,
|
|
405
377
|
getDistributableAmount: () => getDistributableAmount,
|
|
406
378
|
getExplorerUrl: () => getExplorerUrl,
|
|
407
379
|
getNkFromUtxoPrivateKey: () => getNkFromUtxoPrivateKey,
|
|
408
380
|
getNullifierPDA: () => getNullifierPDA,
|
|
409
|
-
getPendingOperationsSummary: () => getPendingOperationsSummary,
|
|
410
381
|
getPublicKey: () => getPublicKey,
|
|
411
|
-
getPublicViewKey: () => getPublicViewKey,
|
|
412
|
-
getRecipientAmount: () => getRecipientAmount,
|
|
413
382
|
getRpcUrlForNetwork: () => getRpcUrlForNetwork,
|
|
414
383
|
getShieldPoolPDAs: () => getShieldPoolPDAs,
|
|
415
384
|
getSwapStatePDA: () => getSwapStatePDA,
|
|
416
|
-
getViewKey: () => getViewKey,
|
|
417
|
-
hasPendingOperations: () => hasPendingOperations,
|
|
418
385
|
hexToBigint: () => hexToBigint,
|
|
419
386
|
hexToBytes: () => hexToBytes,
|
|
420
387
|
importKeys: () => importKeys,
|
|
421
|
-
importWalletKeys: () => importWalletKeys,
|
|
422
388
|
isDebugEnabled: () => isDebugEnabled,
|
|
423
389
|
isRootNotFoundError: () => isRootNotFoundError,
|
|
424
390
|
isValidHex: () => isValidHex,
|
|
425
391
|
isValidRpcUrl: () => isValidRpcUrl,
|
|
426
392
|
isValidSolanaAddress: () => isValidSolanaAddress,
|
|
427
393
|
isWithdrawAmountSufficient: () => isWithdrawAmountSufficient,
|
|
428
|
-
isWithdrawable: () => isWithdrawable,
|
|
429
394
|
keypairToAdapter: () => keypairToAdapter,
|
|
430
|
-
loadPendingDeposits: () => loadPendingDeposits,
|
|
431
|
-
loadPendingWithdrawals: () => loadPendingWithdrawals,
|
|
432
395
|
parseAmount: () => parseAmount,
|
|
433
396
|
parseError: () => parseError,
|
|
434
|
-
parseNote: () => parseNote,
|
|
435
397
|
parseRelayErrorResponse: () => parseRelayErrorResponse,
|
|
436
398
|
parseTransactionError: () => parseTransactionError,
|
|
437
399
|
partialWithdraw: () => partialWithdraw,
|
|
438
400
|
poseidonHash: () => poseidonHash,
|
|
439
401
|
preflightCheck: () => preflightCheck,
|
|
440
|
-
|
|
441
|
-
prepareEncryptedOutputForRecipient: () => prepareEncryptedOutputForRecipient,
|
|
402
|
+
preflightNullifiers: () => preflightNullifiers,
|
|
442
403
|
proofToBytes: () => proofToBytes,
|
|
443
404
|
pubkeyToFieldElement: () => pubkeyToFieldElement,
|
|
444
405
|
pubkeyToLimbs: () => pubkeyToLimbs,
|
|
@@ -446,16 +407,11 @@ __export(index_exports, {
|
|
|
446
407
|
randomFieldElement: () => randomFieldElement,
|
|
447
408
|
readMerkleTreeState: () => readMerkleTreeState,
|
|
448
409
|
registerViewingKey: () => registerViewingKey,
|
|
449
|
-
removePendingDeposit: () => removePendingDeposit,
|
|
450
|
-
removePendingWithdrawal: () => removePendingWithdrawal,
|
|
451
|
-
savePendingDeposit: () => savePendingDeposit,
|
|
452
|
-
savePendingWithdrawal: () => savePendingWithdrawal,
|
|
453
410
|
scanNotesForWallet: () => scanNotesForWallet,
|
|
454
411
|
scanTransactions: () => scanTransactions,
|
|
455
412
|
sdkLogger: () => sdkLogger,
|
|
456
413
|
selectUtxos: () => selectUtxos,
|
|
457
414
|
sendTransaction: () => sendTransaction,
|
|
458
|
-
serializeNote: () => serializeNote,
|
|
459
415
|
serializeUtxo: () => serializeUtxo,
|
|
460
416
|
setCircuitsPath: () => setCircuitsPath,
|
|
461
417
|
setDebugMode: () => setDebugMode,
|
|
@@ -469,28 +425,20 @@ __export(index_exports, {
|
|
|
469
425
|
transfer: () => transfer,
|
|
470
426
|
truncate: () => truncate,
|
|
471
427
|
tryDecryptNote: () => tryDecryptNote,
|
|
472
|
-
updateNoteWithDeposit: () => updateNoteWithDeposit,
|
|
473
|
-
updatePendingDeposit: () => updatePendingDeposit,
|
|
474
|
-
updatePendingWithdrawal: () => updatePendingWithdrawal,
|
|
475
428
|
utxoBigintToBytes32: () => bigintToBytes322,
|
|
476
429
|
utxoEquals: () => utxoEquals,
|
|
477
430
|
utxoHexToBigint: () => hexToBigint2,
|
|
478
|
-
validateDepositParams: () => validateDepositParams,
|
|
479
|
-
validateNote: () => validateNote,
|
|
480
431
|
validateOutputsSum: () => validateOutputsSum,
|
|
481
432
|
validateRoot: () => validateRoot,
|
|
482
|
-
validateTransfers: () => validateTransfers,
|
|
483
433
|
validateWalletConnected: () => validateWalletConnected,
|
|
484
|
-
|
|
485
|
-
verifyAllCircuits: () => verifyAllCircuits,
|
|
486
|
-
verifyCircuitIntegrity: () => verifyCircuitIntegrity,
|
|
434
|
+
verifyUtxos: () => verifyUtxos,
|
|
487
435
|
waitForRoot: () => waitForRoot,
|
|
488
436
|
withTiming: () => withTiming
|
|
489
437
|
});
|
|
490
438
|
module.exports = __toCommonJS(index_exports);
|
|
491
439
|
|
|
492
440
|
// src/core/CloakSDK.ts
|
|
493
|
-
var
|
|
441
|
+
var import_web33 = require("@solana/web3.js");
|
|
494
442
|
|
|
495
443
|
// src/core/types.ts
|
|
496
444
|
var CloakError = class extends Error {
|
|
@@ -547,79 +495,6 @@ function hexToBigint(hex) {
|
|
|
547
495
|
const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
548
496
|
return BigInt("0x" + cleanHex);
|
|
549
497
|
}
|
|
550
|
-
async function computeCommitment(amount, r, sk_spend) {
|
|
551
|
-
const [sk0, sk1] = splitTo2Limbs(sk_spend);
|
|
552
|
-
const [r0, r1] = splitTo2Limbs(r);
|
|
553
|
-
const pk_spend = await poseidonHash([sk0, sk1]);
|
|
554
|
-
return await poseidonHash([amount, r0, r1, pk_spend]);
|
|
555
|
-
}
|
|
556
|
-
async function generateCommitmentAsync(amountLamports, r, skSpend) {
|
|
557
|
-
const amount = BigInt(amountLamports);
|
|
558
|
-
const rValue = hexToBigint(bytesToHex(r));
|
|
559
|
-
const skValue = hexToBigint(bytesToHex(skSpend));
|
|
560
|
-
return await computeCommitment(amount, rValue, skValue);
|
|
561
|
-
}
|
|
562
|
-
function generateCommitment(_amountLamports, _r, _skSpend) {
|
|
563
|
-
throw new Error("generateCommitment is deprecated. Use generateCommitmentAsync instead.");
|
|
564
|
-
}
|
|
565
|
-
async function computeNullifier(sk_spend, leafIndex) {
|
|
566
|
-
const [sk0, sk1] = splitTo2Limbs(sk_spend);
|
|
567
|
-
return await poseidonHash([sk0, sk1, leafIndex]);
|
|
568
|
-
}
|
|
569
|
-
async function computeNullifierAsync(skSpend, leafIndex) {
|
|
570
|
-
const skValue = typeof skSpend === "string" ? hexToBigint(skSpend) : hexToBigint(bytesToHex(skSpend));
|
|
571
|
-
return await computeNullifier(skValue, BigInt(leafIndex));
|
|
572
|
-
}
|
|
573
|
-
function computeNullifierSync(_skSpend, _leafIndex) {
|
|
574
|
-
throw new Error("computeNullifierSync is deprecated. Use computeNullifierAsync instead.");
|
|
575
|
-
}
|
|
576
|
-
async function computeOutputsHashAsync(outputs) {
|
|
577
|
-
let hash = 0n;
|
|
578
|
-
for (const output of outputs) {
|
|
579
|
-
const [lo, hi] = pubkeyToLimbs(output.recipient);
|
|
580
|
-
hash = await poseidonHash([hash, lo, hi, BigInt(output.amount)]);
|
|
581
|
-
}
|
|
582
|
-
return hash;
|
|
583
|
-
}
|
|
584
|
-
async function computeOutputsHash(outAddr, outAmount, outFlags) {
|
|
585
|
-
let hash = 0n;
|
|
586
|
-
for (let i = 0; i < 5; i++) {
|
|
587
|
-
if (outFlags[i] === 1) {
|
|
588
|
-
hash = await poseidonHash([hash, outAddr[i][0], outAddr[i][1], outAmount[i]]);
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
return hash;
|
|
592
|
-
}
|
|
593
|
-
function computeOutputsHashSync(_outputs) {
|
|
594
|
-
throw new Error("computeOutputsHashSync is deprecated. Use computeOutputsHashAsync instead.");
|
|
595
|
-
}
|
|
596
|
-
async function computeSwapOutputsHash(inputMintLimbs, outputMintLimbs, recipientAtaLimbs, minOutputAmount, publicAmount) {
|
|
597
|
-
return await poseidonHash([
|
|
598
|
-
inputMintLimbs[0],
|
|
599
|
-
inputMintLimbs[1],
|
|
600
|
-
outputMintLimbs[0],
|
|
601
|
-
outputMintLimbs[1],
|
|
602
|
-
recipientAtaLimbs[0],
|
|
603
|
-
recipientAtaLimbs[1],
|
|
604
|
-
minOutputAmount,
|
|
605
|
-
publicAmount
|
|
606
|
-
]);
|
|
607
|
-
}
|
|
608
|
-
async function computeSwapOutputsHashAsync(inputMint, outputMint, recipientAta, minOutputAmount, amount) {
|
|
609
|
-
const inputMintLimbs = pubkeyToLimbs(inputMint);
|
|
610
|
-
const outputMintLimbs = pubkeyToLimbs(outputMint);
|
|
611
|
-
const recipientAtaLimbs = pubkeyToLimbs(recipientAta);
|
|
612
|
-
return await computeSwapOutputsHash(
|
|
613
|
-
inputMintLimbs,
|
|
614
|
-
outputMintLimbs,
|
|
615
|
-
recipientAtaLimbs,
|
|
616
|
-
BigInt(minOutputAmount),
|
|
617
|
-
BigInt(amount)
|
|
618
|
-
);
|
|
619
|
-
}
|
|
620
|
-
function computeSwapOutputsHashSync(_outputMint, _recipientAta, _minOutputAmount, _amount) {
|
|
621
|
-
throw new Error("computeSwapOutputsHashSync is deprecated. Use computeSwapOutputsHashAsync instead.");
|
|
622
|
-
}
|
|
623
498
|
function bigintToBytes32(n) {
|
|
624
499
|
const hex = n.toString(16).padStart(64, "0");
|
|
625
500
|
const bytes = new Uint8Array(32);
|
|
@@ -892,223 +767,11 @@ function importKeys(exported) {
|
|
|
892
767
|
return generateCloakKeys(masterSeed);
|
|
893
768
|
}
|
|
894
769
|
|
|
895
|
-
// src/utils/network.ts
|
|
896
|
-
function detectNetworkFromRpcUrl(rpcUrl) {
|
|
897
|
-
const url = rpcUrl || process.env.NEXT_PUBLIC_SOLANA_RPC_URL || "";
|
|
898
|
-
const lowerUrl = url.toLowerCase();
|
|
899
|
-
if (lowerUrl.includes("mainnet") || lowerUrl.includes("api.mainnet-beta") || lowerUrl.includes("mainnet-beta")) {
|
|
900
|
-
return "mainnet";
|
|
901
|
-
}
|
|
902
|
-
if (lowerUrl.includes("testnet") || lowerUrl.includes("api.testnet")) {
|
|
903
|
-
return "testnet";
|
|
904
|
-
}
|
|
905
|
-
if (lowerUrl.includes("devnet") || lowerUrl.includes("api.devnet")) {
|
|
906
|
-
return "devnet";
|
|
907
|
-
}
|
|
908
|
-
if (lowerUrl.includes("localhost") || lowerUrl.includes("127.0.0.1") || lowerUrl.includes("local")) {
|
|
909
|
-
return "localnet";
|
|
910
|
-
}
|
|
911
|
-
return "mainnet";
|
|
912
|
-
}
|
|
913
|
-
function getRpcUrlForNetwork(network) {
|
|
914
|
-
switch (network) {
|
|
915
|
-
case "mainnet":
|
|
916
|
-
return "https://api.mainnet-beta.solana.com";
|
|
917
|
-
case "devnet":
|
|
918
|
-
return "https://api.devnet.solana.com";
|
|
919
|
-
case "localnet":
|
|
920
|
-
return "http://localhost:8899";
|
|
921
|
-
default:
|
|
922
|
-
return "https://api.mainnet-beta.solana.com";
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
function isValidRpcUrl(url) {
|
|
926
|
-
try {
|
|
927
|
-
const parsed = new URL(url);
|
|
928
|
-
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
929
|
-
} catch {
|
|
930
|
-
return false;
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
function getExplorerUrl(signature, network = "devnet") {
|
|
934
|
-
const cluster = network === "mainnet" ? "" : `?cluster=${network}`;
|
|
935
|
-
return `https://explorer.solana.com/tx/${signature}${cluster}`;
|
|
936
|
-
}
|
|
937
|
-
function getAddressExplorerUrl(address, network = "devnet") {
|
|
938
|
-
const cluster = network === "mainnet" ? "" : `?cluster=${network}`;
|
|
939
|
-
return `https://explorer.solana.com/address/${address}${cluster}`;
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
// src/utils/fees.ts
|
|
943
|
-
var LAMPORTS_PER_SOL = 1e9;
|
|
944
|
-
var FIXED_FEE_LAMPORTS = 5e6;
|
|
945
|
-
var VARIABLE_FEE_NUMERATOR = 3;
|
|
946
|
-
var VARIABLE_FEE_DENOMINATOR = 1e3;
|
|
947
|
-
var VARIABLE_FEE_RATE = VARIABLE_FEE_NUMERATOR / VARIABLE_FEE_DENOMINATOR;
|
|
948
|
-
var MIN_DEPOSIT_LAMPORTS = 1e7;
|
|
949
|
-
function calculateFee(amountLamports) {
|
|
950
|
-
const variableFee = Math.floor(amountLamports * VARIABLE_FEE_NUMERATOR / VARIABLE_FEE_DENOMINATOR);
|
|
951
|
-
return FIXED_FEE_LAMPORTS + variableFee;
|
|
952
|
-
}
|
|
953
|
-
function calculateFeeBigint(amountLamports) {
|
|
954
|
-
const fixed = BigInt(FIXED_FEE_LAMPORTS);
|
|
955
|
-
const variable = amountLamports * BigInt(VARIABLE_FEE_NUMERATOR) / BigInt(VARIABLE_FEE_DENOMINATOR);
|
|
956
|
-
return fixed + variable;
|
|
957
|
-
}
|
|
958
|
-
function isWithdrawAmountSufficient(amountLamports) {
|
|
959
|
-
return amountLamports > calculateFeeBigint(amountLamports);
|
|
960
|
-
}
|
|
961
|
-
function getDistributableAmount(amountLamports) {
|
|
962
|
-
return amountLamports - calculateFee(amountLamports);
|
|
963
|
-
}
|
|
964
|
-
function formatAmount(lamports, decimals = 9) {
|
|
965
|
-
return (lamports / LAMPORTS_PER_SOL).toFixed(decimals);
|
|
966
|
-
}
|
|
967
|
-
function parseAmount(sol) {
|
|
968
|
-
const num = parseFloat(sol);
|
|
969
|
-
if (isNaN(num) || num < 0) {
|
|
970
|
-
throw new Error(`Invalid SOL amount: ${sol}`);
|
|
971
|
-
}
|
|
972
|
-
return Math.floor(num * LAMPORTS_PER_SOL);
|
|
973
|
-
}
|
|
974
|
-
function validateOutputsSum(outputs, expectedTotal) {
|
|
975
|
-
const sum = outputs.reduce((acc, out) => acc + out.amount, 0);
|
|
976
|
-
return sum === expectedTotal;
|
|
977
|
-
}
|
|
978
|
-
function calculateRelayFee(amountLamports, feeBps) {
|
|
979
|
-
if (feeBps < 0 || feeBps > 1e4) {
|
|
980
|
-
throw new Error("Fee basis points must be between 0 and 10000");
|
|
981
|
-
}
|
|
982
|
-
return Math.floor(amountLamports * feeBps / 1e4);
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
// src/core/note-manager.ts
|
|
986
|
-
async function generateNote(amountLamports, network) {
|
|
987
|
-
const actualNetwork = network || detectNetworkFromRpcUrl();
|
|
988
|
-
const skSpend = randomBytes(32);
|
|
989
|
-
const rBytes = randomBytes(32);
|
|
990
|
-
const commitmentBigint = await generateCommitmentAsync(amountLamports, rBytes, skSpend);
|
|
991
|
-
const commitmentHex = commitmentBigint.toString(16).padStart(64, "0");
|
|
992
|
-
const skSpendHex = bytesToHex(skSpend);
|
|
993
|
-
const rHex = bytesToHex(rBytes);
|
|
994
|
-
return {
|
|
995
|
-
version: "1.0",
|
|
996
|
-
amount: amountLamports,
|
|
997
|
-
commitment: commitmentHex,
|
|
998
|
-
sk_spend: skSpendHex,
|
|
999
|
-
r: rHex,
|
|
1000
|
-
timestamp: Date.now(),
|
|
1001
|
-
network: actualNetwork
|
|
1002
|
-
};
|
|
1003
|
-
}
|
|
1004
|
-
async function generateNoteFromWallet(amountLamports, keys, network) {
|
|
1005
|
-
const actualNetwork = network || detectNetworkFromRpcUrl();
|
|
1006
|
-
const rBytes = randomBytes(32);
|
|
1007
|
-
const sk_spend = hexToBytes(keys.spend.sk_spend_hex);
|
|
1008
|
-
const commitmentBigint = await generateCommitmentAsync(amountLamports, rBytes, sk_spend);
|
|
1009
|
-
const commitmentHex = commitmentBigint.toString(16).padStart(64, "0");
|
|
1010
|
-
return {
|
|
1011
|
-
version: "2.0",
|
|
1012
|
-
amount: amountLamports,
|
|
1013
|
-
commitment: commitmentHex,
|
|
1014
|
-
sk_spend: keys.spend.sk_spend_hex,
|
|
1015
|
-
r: bytesToHex(rBytes),
|
|
1016
|
-
timestamp: Date.now(),
|
|
1017
|
-
network: actualNetwork
|
|
1018
|
-
};
|
|
1019
|
-
}
|
|
1020
|
-
function parseNote(jsonString) {
|
|
1021
|
-
const note = JSON.parse(jsonString);
|
|
1022
|
-
if (!note.version || !note.amount || !note.commitment || !note.sk_spend || !note.r) {
|
|
1023
|
-
throw new CloakError(
|
|
1024
|
-
"Invalid note format: missing required fields",
|
|
1025
|
-
"validation",
|
|
1026
|
-
false
|
|
1027
|
-
);
|
|
1028
|
-
}
|
|
1029
|
-
if (!/^[0-9a-f]{64}$/i.test(note.commitment)) {
|
|
1030
|
-
throw new CloakError("Invalid commitment format", "validation", false);
|
|
1031
|
-
}
|
|
1032
|
-
if (!/^[0-9a-f]{64}$/i.test(note.sk_spend)) {
|
|
1033
|
-
throw new CloakError("Invalid sk_spend format", "validation", false);
|
|
1034
|
-
}
|
|
1035
|
-
if (!/^[0-9a-f]{64}$/i.test(note.r)) {
|
|
1036
|
-
throw new CloakError("Invalid r format", "validation", false);
|
|
1037
|
-
}
|
|
1038
|
-
return note;
|
|
1039
|
-
}
|
|
1040
|
-
function exportNote(note, pretty = false) {
|
|
1041
|
-
return pretty ? JSON.stringify(note, null, 2) : JSON.stringify(note);
|
|
1042
|
-
}
|
|
1043
|
-
function isWithdrawable(note) {
|
|
1044
|
-
return !!(note.depositSignature && note.leafIndex !== void 0 && note.root);
|
|
1045
|
-
}
|
|
1046
|
-
function updateNoteWithDeposit(note, depositInfo) {
|
|
1047
|
-
const updated = {
|
|
1048
|
-
...note,
|
|
1049
|
-
depositSignature: depositInfo.signature,
|
|
1050
|
-
depositSlot: depositInfo.slot,
|
|
1051
|
-
leafIndex: depositInfo.leafIndex,
|
|
1052
|
-
root: depositInfo.root
|
|
1053
|
-
};
|
|
1054
|
-
if (depositInfo.merkleProof) {
|
|
1055
|
-
updated.merkleProof = depositInfo.merkleProof;
|
|
1056
|
-
}
|
|
1057
|
-
return updated;
|
|
1058
|
-
}
|
|
1059
|
-
function findNoteByCommitment(notes, commitment) {
|
|
1060
|
-
return notes.find((n) => n.commitment === commitment);
|
|
1061
|
-
}
|
|
1062
|
-
function filterNotesByNetwork(notes, network) {
|
|
1063
|
-
return notes.filter((n) => n.network === network);
|
|
1064
|
-
}
|
|
1065
|
-
function filterWithdrawableNotes(notes) {
|
|
1066
|
-
return notes.filter(isWithdrawable);
|
|
1067
|
-
}
|
|
1068
|
-
function exportWalletKeys(keys) {
|
|
1069
|
-
return exportKeys(keys);
|
|
1070
|
-
}
|
|
1071
|
-
function importWalletKeys(keysJson) {
|
|
1072
|
-
return importKeys(keysJson);
|
|
1073
|
-
}
|
|
1074
|
-
function getPublicViewKey(keys) {
|
|
1075
|
-
return keys.view.pvk_hex;
|
|
1076
|
-
}
|
|
1077
|
-
function getViewKey(keys) {
|
|
1078
|
-
return keys.view;
|
|
1079
|
-
}
|
|
1080
|
-
var calculateFee2 = calculateFee;
|
|
1081
|
-
function getDistributableAmount2(amountLamports) {
|
|
1082
|
-
return amountLamports - calculateFee2(amountLamports);
|
|
1083
|
-
}
|
|
1084
|
-
function getRecipientAmount(amountLamports) {
|
|
1085
|
-
return getDistributableAmount2(amountLamports);
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
770
|
// src/core/storage.ts
|
|
1089
771
|
var MemoryStorageAdapter = class {
|
|
1090
772
|
constructor() {
|
|
1091
|
-
this.notes = /* @__PURE__ */ new Map();
|
|
1092
773
|
this.keys = null;
|
|
1093
774
|
}
|
|
1094
|
-
saveNote(note) {
|
|
1095
|
-
this.notes.set(note.commitment, note);
|
|
1096
|
-
}
|
|
1097
|
-
loadAllNotes() {
|
|
1098
|
-
return Array.from(this.notes.values());
|
|
1099
|
-
}
|
|
1100
|
-
updateNote(commitment, updates) {
|
|
1101
|
-
const existing = this.notes.get(commitment);
|
|
1102
|
-
if (existing) {
|
|
1103
|
-
this.notes.set(commitment, { ...existing, ...updates });
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
deleteNote(commitment) {
|
|
1107
|
-
this.notes.delete(commitment);
|
|
1108
|
-
}
|
|
1109
|
-
clearAllNotes() {
|
|
1110
|
-
this.notes.clear();
|
|
1111
|
-
}
|
|
1112
775
|
saveKeys(keys) {
|
|
1113
776
|
this.keys = keys;
|
|
1114
777
|
}
|
|
@@ -1119,10 +782,10 @@ var MemoryStorageAdapter = class {
|
|
|
1119
782
|
this.keys = null;
|
|
1120
783
|
}
|
|
1121
784
|
};
|
|
1122
|
-
var
|
|
1123
|
-
constructor(
|
|
1124
|
-
this.notesKey = notesKey;
|
|
785
|
+
var _LocalStorageAdapter = class _LocalStorageAdapter {
|
|
786
|
+
constructor(keysKey = "cloak_wallet_keys") {
|
|
1125
787
|
this.keysKey = keysKey;
|
|
788
|
+
_LocalStorageAdapter.runLegacyPurgeOnce();
|
|
1126
789
|
}
|
|
1127
790
|
getStorage() {
|
|
1128
791
|
if (typeof globalThis !== "undefined" && globalThis.localStorage) {
|
|
@@ -1130,47 +793,6 @@ var LocalStorageAdapter = class {
|
|
|
1130
793
|
}
|
|
1131
794
|
return null;
|
|
1132
795
|
}
|
|
1133
|
-
saveNote(note) {
|
|
1134
|
-
const storage = this.getStorage();
|
|
1135
|
-
if (!storage) throw new Error("localStorage not available");
|
|
1136
|
-
const notes = this.loadAllNotes();
|
|
1137
|
-
notes.push(note);
|
|
1138
|
-
storage.setItem(this.notesKey, JSON.stringify(notes));
|
|
1139
|
-
}
|
|
1140
|
-
loadAllNotes() {
|
|
1141
|
-
const storage = this.getStorage();
|
|
1142
|
-
if (!storage) return [];
|
|
1143
|
-
const stored = storage.getItem(this.notesKey);
|
|
1144
|
-
if (!stored) return [];
|
|
1145
|
-
try {
|
|
1146
|
-
return JSON.parse(stored);
|
|
1147
|
-
} catch {
|
|
1148
|
-
return [];
|
|
1149
|
-
}
|
|
1150
|
-
}
|
|
1151
|
-
updateNote(commitment, updates) {
|
|
1152
|
-
const storage = this.getStorage();
|
|
1153
|
-
if (!storage) return;
|
|
1154
|
-
const notes = this.loadAllNotes();
|
|
1155
|
-
const index = notes.findIndex((n) => n.commitment === commitment);
|
|
1156
|
-
if (index !== -1) {
|
|
1157
|
-
notes[index] = { ...notes[index], ...updates };
|
|
1158
|
-
storage.setItem(this.notesKey, JSON.stringify(notes));
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
deleteNote(commitment) {
|
|
1162
|
-
const storage = this.getStorage();
|
|
1163
|
-
if (!storage) return;
|
|
1164
|
-
const notes = this.loadAllNotes();
|
|
1165
|
-
const filtered = notes.filter((n) => n.commitment !== commitment);
|
|
1166
|
-
storage.setItem(this.notesKey, JSON.stringify(filtered));
|
|
1167
|
-
}
|
|
1168
|
-
clearAllNotes() {
|
|
1169
|
-
const storage = this.getStorage();
|
|
1170
|
-
if (storage) {
|
|
1171
|
-
storage.removeItem(this.notesKey);
|
|
1172
|
-
}
|
|
1173
|
-
}
|
|
1174
796
|
saveKeys(keys) {
|
|
1175
797
|
const storage = this.getStorage();
|
|
1176
798
|
if (!storage) throw new Error("localStorage not available");
|
|
@@ -1193,126 +815,64 @@ var LocalStorageAdapter = class {
|
|
|
1193
815
|
storage.removeItem(this.keysKey);
|
|
1194
816
|
}
|
|
1195
817
|
}
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
return false;
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
function validateNote(note) {
|
|
1209
|
-
if (!note || typeof note !== "object") {
|
|
1210
|
-
throw new Error("Note must be an object");
|
|
1211
|
-
}
|
|
1212
|
-
const requiredFields = ["version", "amount", "commitment", "sk_spend", "r", "timestamp", "network"];
|
|
1213
|
-
for (const field of requiredFields) {
|
|
1214
|
-
if (!(field in note)) {
|
|
1215
|
-
throw new Error(`Missing required field: ${field}`);
|
|
1216
|
-
}
|
|
1217
|
-
}
|
|
1218
|
-
if (typeof note.version !== "string") {
|
|
1219
|
-
throw new Error("Version must be a string");
|
|
1220
|
-
}
|
|
1221
|
-
if (typeof note.amount !== "number" || note.amount <= 0) {
|
|
1222
|
-
throw new Error("Amount must be a positive number");
|
|
1223
|
-
}
|
|
1224
|
-
if (!isValidHex(note.commitment, 32)) {
|
|
1225
|
-
throw new Error("Invalid commitment format (expected 64 hex characters)");
|
|
1226
|
-
}
|
|
1227
|
-
if (!isValidHex(note.sk_spend, 32)) {
|
|
1228
|
-
throw new Error("Invalid sk_spend format (expected 64 hex characters)");
|
|
1229
|
-
}
|
|
1230
|
-
if (!isValidHex(note.r, 32)) {
|
|
1231
|
-
throw new Error("Invalid r format (expected 64 hex characters)");
|
|
1232
|
-
}
|
|
1233
|
-
if (typeof note.timestamp !== "number" || note.timestamp <= 0) {
|
|
1234
|
-
throw new Error("Timestamp must be a positive number");
|
|
1235
|
-
}
|
|
1236
|
-
if (!["localnet", "devnet", "testnet", "mainnet"].includes(note.network)) {
|
|
1237
|
-
throw new Error("Network must be localnet, devnet, testnet, or mainnet");
|
|
1238
|
-
}
|
|
1239
|
-
if (note.depositSignature !== void 0 && typeof note.depositSignature !== "string") {
|
|
1240
|
-
throw new Error("Deposit signature must be a string");
|
|
1241
|
-
}
|
|
1242
|
-
if (note.depositSlot !== void 0 && typeof note.depositSlot !== "number") {
|
|
1243
|
-
throw new Error("Deposit slot must be a number");
|
|
1244
|
-
}
|
|
1245
|
-
if (note.leafIndex !== void 0) {
|
|
1246
|
-
if (typeof note.leafIndex !== "number" || note.leafIndex < 0) {
|
|
1247
|
-
throw new Error("Leaf index must be a non-negative number");
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
if (note.root !== void 0 && !isValidHex(note.root, 32)) {
|
|
1251
|
-
throw new Error("Invalid root format (expected 64 hex characters)");
|
|
1252
|
-
}
|
|
1253
|
-
if (note.merkleProof !== void 0) {
|
|
1254
|
-
if (!Array.isArray(note.merkleProof.pathElements)) {
|
|
1255
|
-
throw new Error("Merkle proof pathElements must be an array");
|
|
818
|
+
/**
|
|
819
|
+
* Runs the legacy-purge exactly once per browser origin. Invoked
|
|
820
|
+
* automatically by the constructor; exposed as a static for tests and
|
|
821
|
+
* for callers who want to force the check before the first adapter
|
|
822
|
+
* instantiation.
|
|
823
|
+
*/
|
|
824
|
+
static runLegacyPurgeOnce() {
|
|
825
|
+
if (typeof globalThis === "undefined" || !globalThis.localStorage) {
|
|
826
|
+
return;
|
|
1256
827
|
}
|
|
1257
|
-
|
|
1258
|
-
|
|
828
|
+
const storage = globalThis.localStorage;
|
|
829
|
+
if (storage.getItem(_LocalStorageAdapter.LEGACY_PURGE_SENTINEL_KEY)) {
|
|
830
|
+
return;
|
|
1259
831
|
}
|
|
1260
|
-
|
|
1261
|
-
|
|
832
|
+
storage.removeItem(_LocalStorageAdapter.LEGACY_NOTES_KEY);
|
|
833
|
+
try {
|
|
834
|
+
storage.setItem(_LocalStorageAdapter.LEGACY_PURGE_SENTINEL_KEY, "1");
|
|
835
|
+
} catch {
|
|
1262
836
|
}
|
|
1263
837
|
}
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
}
|
|
1285
|
-
if (!note.merkleProof) {
|
|
1286
|
-
throw new Error("Note must have Merkle proof for withdrawal");
|
|
1287
|
-
}
|
|
1288
|
-
if (note.merkleProof.pathElements.length === 0) {
|
|
1289
|
-
throw new Error("Merkle proof is empty");
|
|
1290
|
-
}
|
|
1291
|
-
}
|
|
1292
|
-
function validateTransfers(recipients, totalAmount) {
|
|
1293
|
-
if (recipients.length === 0) {
|
|
1294
|
-
throw new Error("At least one recipient is required");
|
|
1295
|
-
}
|
|
1296
|
-
if (recipients.length > 5) {
|
|
1297
|
-
throw new Error("Maximum 5 recipients allowed");
|
|
1298
|
-
}
|
|
1299
|
-
for (let i = 0; i < recipients.length; i++) {
|
|
1300
|
-
const transfer2 = recipients[i];
|
|
1301
|
-
const isPublicKeyLike = transfer2.recipient && typeof transfer2.recipient.toBase58 === "function" && typeof transfer2.recipient.toBuffer === "function";
|
|
1302
|
-
if (!isPublicKeyLike) {
|
|
1303
|
-
throw new Error(`Recipient ${i} must be a PublicKey (got ${typeof transfer2.recipient})`);
|
|
1304
|
-
}
|
|
1305
|
-
if (typeof transfer2.amount !== "number" || transfer2.amount <= 0) {
|
|
1306
|
-
throw new Error(`Recipient ${i} amount must be a positive number`);
|
|
838
|
+
/**
|
|
839
|
+
* Manual cleanup helper for callers that stored notes under a non-default
|
|
840
|
+
* key. The auto-purge in the constructor only handles the canonical
|
|
841
|
+
* `cloak_notes` slot; pass the custom key here.
|
|
842
|
+
*
|
|
843
|
+
* Pre-0.1.6 versions stored the user's `CloakNote[]` (including
|
|
844
|
+
* `r` randomness and `sk_spend` secret-key hex) as JSON plaintext.
|
|
845
|
+
* Those notes are no longer usable (the OLD `withdraw_regular.circom`
|
|
846
|
+
* flow is incompatible with the deployed UTXO program) but the plaintext
|
|
847
|
+
* would otherwise linger indefinitely.
|
|
848
|
+
*
|
|
849
|
+
* Idempotent; safe to call when the key doesn't exist or when running
|
|
850
|
+
* outside a browser (no-op). Does NOT update the auto-purge sentinel.
|
|
851
|
+
*
|
|
852
|
+
* @param notesKey - localStorage key to remove (default `"cloak_notes"`,
|
|
853
|
+
* matching the pre-0.1.6 default).
|
|
854
|
+
*/
|
|
855
|
+
static purgeLegacyNoteStorage(notesKey = _LocalStorageAdapter.LEGACY_NOTES_KEY) {
|
|
856
|
+
if (typeof globalThis === "undefined" || !globalThis.localStorage) {
|
|
857
|
+
return;
|
|
1307
858
|
}
|
|
859
|
+
globalThis.localStorage.removeItem(notesKey);
|
|
1308
860
|
}
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
861
|
+
};
|
|
862
|
+
/**
|
|
863
|
+
* Sentinel key set after the auto-purge runs. Persists across browser
|
|
864
|
+
* sessions so the purge never re-runs on the same origin.
|
|
865
|
+
*/
|
|
866
|
+
_LocalStorageAdapter.LEGACY_PURGE_SENTINEL_KEY = "cloak_purged_v0_1_5";
|
|
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");
|
|
1316
876
|
|
|
1317
877
|
// src/utils/errors.ts
|
|
1318
878
|
var RootNotFoundError = class extends Error {
|
|
@@ -1339,6 +899,71 @@ function parseRelayErrorResponse(responseText) {
|
|
|
1339
899
|
return { isRootNotFound: false };
|
|
1340
900
|
}
|
|
1341
901
|
}
|
|
902
|
+
var UtxoAlreadySpentError = class extends Error {
|
|
903
|
+
constructor(message, spentUtxoCommitments = []) {
|
|
904
|
+
super(message);
|
|
905
|
+
this.spentUtxoCommitments = spentUtxoCommitments;
|
|
906
|
+
this.name = "UtxoAlreadySpentError";
|
|
907
|
+
}
|
|
908
|
+
};
|
|
909
|
+
var SanctionsQuoteError = class extends Error {
|
|
910
|
+
constructor(message, onChainCode, subKind) {
|
|
911
|
+
super(message);
|
|
912
|
+
this.onChainCode = onChainCode;
|
|
913
|
+
this.subKind = subKind;
|
|
914
|
+
this.name = "SanctionsQuoteError";
|
|
915
|
+
}
|
|
916
|
+
};
|
|
917
|
+
var RelayInternalError = class extends Error {
|
|
918
|
+
constructor(message, relayMessage, httpStatus, cachedTreeFromChain) {
|
|
919
|
+
super(message);
|
|
920
|
+
this.relayMessage = relayMessage;
|
|
921
|
+
this.httpStatus = httpStatus;
|
|
922
|
+
this.cachedTreeFromChain = cachedTreeFromChain;
|
|
923
|
+
this.name = "RelayInternalError";
|
|
924
|
+
}
|
|
925
|
+
};
|
|
926
|
+
function classifyRelayError(responseText, httpStatus) {
|
|
927
|
+
let inner = "";
|
|
928
|
+
try {
|
|
929
|
+
const parsed = JSON.parse(responseText);
|
|
930
|
+
inner = parsed?.message ?? "";
|
|
931
|
+
} catch {
|
|
932
|
+
inner = responseText;
|
|
933
|
+
}
|
|
934
|
+
const haystack = inner.toLowerCase();
|
|
935
|
+
if (haystack.includes("0x1020") || haystack.includes("doublespend")) {
|
|
936
|
+
return new UtxoAlreadySpentError(
|
|
937
|
+
"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
|
+
);
|
|
939
|
+
}
|
|
940
|
+
if (haystack.includes("0x10b0") || haystack.includes("rangequoteexpired")) {
|
|
941
|
+
return new SanctionsQuoteError(
|
|
942
|
+
"Range sanctions quote expired before the on-chain program processed the transaction. The relay's quote-expiry window is too short relative to proof-gen + confirmation latency, or on-chain clock drifted. Retry the send; the relay will issue a fresh quote.",
|
|
943
|
+
4272,
|
|
944
|
+
"expired"
|
|
945
|
+
);
|
|
946
|
+
}
|
|
947
|
+
if (haystack.includes("0x10b2") || haystack.includes("rangequotewalletmismatch")) {
|
|
948
|
+
return new SanctionsQuoteError(
|
|
949
|
+
"Range sanctions quote's wallet doesn't match the on-chain expected wallet. Indicates a relay config drift \u2014 the sender used for quote signing doesn't match the program's pool.range_signer / fee payer. Relay operators must fix; caller retry will not help.",
|
|
950
|
+
4274,
|
|
951
|
+
"wallet_mismatch"
|
|
952
|
+
);
|
|
953
|
+
}
|
|
954
|
+
if (haystack.includes("0x10b3") || haystack.includes("rangequotemissinged25519")) {
|
|
955
|
+
return new SanctionsQuoteError(
|
|
956
|
+
"Transaction reached the on-chain program without the required Ed25519 sanctions quote instruction. This is a relay bug \u2014 the relay constructed the tx without attaching the signed quote. Caller retry will not help.",
|
|
957
|
+
4275,
|
|
958
|
+
"missing_ix"
|
|
959
|
+
);
|
|
960
|
+
}
|
|
961
|
+
return new RelayInternalError(
|
|
962
|
+
`Relay returned ${httpStatus ?? "an error"}: ${inner || responseText}`,
|
|
963
|
+
inner || responseText,
|
|
964
|
+
httpStatus
|
|
965
|
+
);
|
|
966
|
+
}
|
|
1342
967
|
var ShieldPoolErrors = {
|
|
1343
968
|
// Root management errors
|
|
1344
969
|
4096: "Invalid Merkle root",
|
|
@@ -2143,7 +1768,6 @@ function formatErrorForLogging(error) {
|
|
|
2143
1768
|
}
|
|
2144
1769
|
|
|
2145
1770
|
// src/services/RelayService.ts
|
|
2146
|
-
var import_sha2 = require("@noble/hashes/sha2");
|
|
2147
1771
|
var RelayService = class {
|
|
2148
1772
|
/**
|
|
2149
1773
|
* Create a new Relay Service client
|
|
@@ -2512,88 +2136,24 @@ var RelayService = class {
|
|
|
2512
2136
|
}
|
|
2513
2137
|
};
|
|
2514
2138
|
|
|
2515
|
-
// src/
|
|
2139
|
+
// src/utils/pda.ts
|
|
2516
2140
|
var import_web32 = require("@solana/web3.js");
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
)
|
|
2522
|
-
|
|
2523
|
-
if (params.amount <= 0) {
|
|
2524
|
-
throw new Error("Amount must be positive");
|
|
2525
|
-
}
|
|
2526
|
-
const discriminant = new Uint8Array([1]);
|
|
2527
|
-
const amountBytes = new Uint8Array(8);
|
|
2528
|
-
new DataView(amountBytes.buffer).setBigUint64(
|
|
2529
|
-
0,
|
|
2530
|
-
BigInt(params.amount),
|
|
2531
|
-
true
|
|
2532
|
-
// little-endian
|
|
2141
|
+
init_utxo();
|
|
2142
|
+
function getShieldPoolPDAs(programId, mint = NATIVE_SOL_MINT) {
|
|
2143
|
+
const pid = programId || CLOAK_PROGRAM_ID;
|
|
2144
|
+
const [pool] = import_web32.PublicKey.findProgramAddressSync(
|
|
2145
|
+
[Buffer.from("pool"), mint.toBuffer()],
|
|
2146
|
+
pid
|
|
2533
2147
|
);
|
|
2534
|
-
const
|
|
2535
|
-
data.set(discriminant, 0);
|
|
2536
|
-
data.set(amountBytes, 1);
|
|
2537
|
-
data.set(params.commitment, 9);
|
|
2538
|
-
return new import_web32.TransactionInstruction({
|
|
2539
|
-
programId: params.programId,
|
|
2540
|
-
keys: [
|
|
2541
|
-
// Account 0: Payer (signer, writable) - pays for transaction
|
|
2542
|
-
{ pubkey: params.payer, isSigner: true, isWritable: true },
|
|
2543
|
-
// Account 1: Pool (writable) - receives SOL
|
|
2544
|
-
{ pubkey: params.pool, isSigner: false, isWritable: true },
|
|
2545
|
-
// Account 2: System Program (readonly) - for transfers
|
|
2546
|
-
{ pubkey: import_web32.SystemProgram.programId, isSigner: false, isWritable: false },
|
|
2547
|
-
// Account 3: Merkle Tree (writable) - stores on-chain Merkle tree
|
|
2548
|
-
{ pubkey: params.merkleTree, isSigner: false, isWritable: true }
|
|
2549
|
-
],
|
|
2550
|
-
data: Buffer.from(data)
|
|
2551
|
-
});
|
|
2552
|
-
}
|
|
2553
|
-
function validateDepositParams(params) {
|
|
2554
|
-
if (!(params.programId instanceof import_web32.PublicKey)) {
|
|
2555
|
-
throw new Error("programId must be a PublicKey");
|
|
2556
|
-
}
|
|
2557
|
-
if (!(params.payer instanceof import_web32.PublicKey)) {
|
|
2558
|
-
throw new Error("payer must be a PublicKey");
|
|
2559
|
-
}
|
|
2560
|
-
if (!(params.pool instanceof import_web32.PublicKey)) {
|
|
2561
|
-
throw new Error("pool must be a PublicKey");
|
|
2562
|
-
}
|
|
2563
|
-
if (!(params.merkleTree instanceof import_web32.PublicKey)) {
|
|
2564
|
-
throw new Error("merkleTree must be a PublicKey");
|
|
2565
|
-
}
|
|
2566
|
-
if (typeof params.amount !== "number" || params.amount <= 0) {
|
|
2567
|
-
throw new Error("amount must be a positive number");
|
|
2568
|
-
}
|
|
2569
|
-
if (!(params.commitment instanceof Uint8Array)) {
|
|
2570
|
-
throw new Error("commitment must be a Uint8Array");
|
|
2571
|
-
}
|
|
2572
|
-
if (params.commitment.length !== 32) {
|
|
2573
|
-
throw new Error(
|
|
2574
|
-
`commitment must be 32 bytes (got ${params.commitment.length})`
|
|
2575
|
-
);
|
|
2576
|
-
}
|
|
2577
|
-
}
|
|
2578
|
-
|
|
2579
|
-
// src/utils/pda.ts
|
|
2580
|
-
var import_web34 = require("@solana/web3.js");
|
|
2581
|
-
init_utxo();
|
|
2582
|
-
function getShieldPoolPDAs(programId, mint = NATIVE_SOL_MINT) {
|
|
2583
|
-
const pid = programId || CLOAK_PROGRAM_ID;
|
|
2584
|
-
const [pool] = import_web34.PublicKey.findProgramAddressSync(
|
|
2585
|
-
[Buffer.from("pool"), mint.toBuffer()],
|
|
2586
|
-
pid
|
|
2587
|
-
);
|
|
2588
|
-
const [merkleTree] = import_web34.PublicKey.findProgramAddressSync(
|
|
2148
|
+
const [merkleTree] = import_web32.PublicKey.findProgramAddressSync(
|
|
2589
2149
|
[Buffer.from("merkle_tree"), mint.toBuffer()],
|
|
2590
2150
|
pid
|
|
2591
2151
|
);
|
|
2592
|
-
const [treasury] =
|
|
2152
|
+
const [treasury] = import_web32.PublicKey.findProgramAddressSync(
|
|
2593
2153
|
[Buffer.from("treasury"), mint.toBuffer()],
|
|
2594
2154
|
pid
|
|
2595
2155
|
);
|
|
2596
|
-
const [vaultAuthority] =
|
|
2156
|
+
const [vaultAuthority] = import_web32.PublicKey.findProgramAddressSync(
|
|
2597
2157
|
[Buffer.from("vault_authority"), mint.toBuffer()],
|
|
2598
2158
|
pid
|
|
2599
2159
|
);
|
|
@@ -2609,7 +2169,7 @@ function getNullifierPDA(poolPubkey, nullifier, programId) {
|
|
|
2609
2169
|
if (nullifier.length !== 32) {
|
|
2610
2170
|
throw new Error(`Nullifier must be 32 bytes, got ${nullifier.length}`);
|
|
2611
2171
|
}
|
|
2612
|
-
return
|
|
2172
|
+
return import_web32.PublicKey.findProgramAddressSync(
|
|
2613
2173
|
[Buffer.from("nullifier"), poolPubkey.toBuffer(), Buffer.from(nullifier)],
|
|
2614
2174
|
pid
|
|
2615
2175
|
);
|
|
@@ -2619,256 +2179,12 @@ function getSwapStatePDA(poolPubkey, nullifier, programId) {
|
|
|
2619
2179
|
if (nullifier.length !== 32) {
|
|
2620
2180
|
throw new Error(`Nullifier must be 32 bytes, got ${nullifier.length}`);
|
|
2621
2181
|
}
|
|
2622
|
-
return
|
|
2182
|
+
return import_web32.PublicKey.findProgramAddressSync(
|
|
2623
2183
|
[Buffer.from("swap_state"), poolPubkey.toBuffer(), Buffer.from(nullifier)],
|
|
2624
2184
|
pid
|
|
2625
2185
|
);
|
|
2626
2186
|
}
|
|
2627
2187
|
|
|
2628
|
-
// src/utils/proof-generation.ts
|
|
2629
|
-
var snarkjs = __toESM(require("snarkjs"), 1);
|
|
2630
|
-
var IS_REACT_NATIVE = typeof navigator !== "undefined" && navigator.product === "ReactNative";
|
|
2631
|
-
var IS_BROWSER = IS_REACT_NATIVE || typeof window !== "undefined" || typeof globalThis !== "undefined" && typeof globalThis.document !== "undefined";
|
|
2632
|
-
var DEFAULT_CIRCUITS_URL = "https://cloak-circuits.s3.us-east-1.amazonaws.com/circuits/0.1.0";
|
|
2633
|
-
var SKIP_CIRCUIT_VERIFY_ENV = typeof process !== "undefined" && process.env && process.env.CLOAK_SKIP_CIRCUIT_INTEGRITY_CHECK === "1";
|
|
2634
|
-
function isUrl(path) {
|
|
2635
|
-
return path.startsWith("http://") || path.startsWith("https://");
|
|
2636
|
-
}
|
|
2637
|
-
function resolveCircuitsUrl(circuitsPath2) {
|
|
2638
|
-
if (circuitsPath2.trim() !== "" && circuitsPath2.trim() !== DEFAULT_CIRCUITS_URL) {
|
|
2639
|
-
return DEFAULT_CIRCUITS_URL;
|
|
2640
|
-
}
|
|
2641
|
-
return DEFAULT_CIRCUITS_URL;
|
|
2642
|
-
}
|
|
2643
|
-
async function fetchFileAsBuffer(url) {
|
|
2644
|
-
const response = await fetch(url);
|
|
2645
|
-
if (!response.ok) {
|
|
2646
|
-
throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
|
|
2647
|
-
}
|
|
2648
|
-
const arrayBuffer = await response.arrayBuffer();
|
|
2649
|
-
return new Uint8Array(arrayBuffer);
|
|
2650
|
-
}
|
|
2651
|
-
async function sha256Hex(data) {
|
|
2652
|
-
if (IS_BROWSER) {
|
|
2653
|
-
const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
2654
|
-
const buffer = bytes.buffer.slice(
|
|
2655
|
-
bytes.byteOffset,
|
|
2656
|
-
bytes.byteOffset + bytes.byteLength
|
|
2657
|
-
);
|
|
2658
|
-
const hashBuffer = await crypto.subtle.digest("SHA-256", buffer);
|
|
2659
|
-
return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2660
|
-
}
|
|
2661
|
-
const nodeCrypto = await getNodeCrypto();
|
|
2662
|
-
return nodeCrypto.createHash("sha256").update(data).digest("hex");
|
|
2663
|
-
}
|
|
2664
|
-
var _nodeCrypto = null;
|
|
2665
|
-
function nodeRequire(moduleName) {
|
|
2666
|
-
if (IS_BROWSER) {
|
|
2667
|
-
throw new Error(`Node.js ${moduleName} module not available in browser/React Native`);
|
|
2668
|
-
}
|
|
2669
|
-
try {
|
|
2670
|
-
const requireFunc = new Function("moduleName", "return require(moduleName)");
|
|
2671
|
-
return requireFunc(moduleName);
|
|
2672
|
-
} catch {
|
|
2673
|
-
throw new Error(`Cannot load Node.js module ${moduleName} synchronously in ESM.`);
|
|
2674
|
-
}
|
|
2675
|
-
}
|
|
2676
|
-
async function getNodeCrypto() {
|
|
2677
|
-
if (_nodeCrypto) return _nodeCrypto;
|
|
2678
|
-
_nodeCrypto = nodeRequire("crypto");
|
|
2679
|
-
return _nodeCrypto;
|
|
2680
|
-
}
|
|
2681
|
-
async function generateWithdrawRegularProof(inputs, circuitsPath2) {
|
|
2682
|
-
const circuitsUrl = resolveCircuitsUrl(circuitsPath2);
|
|
2683
|
-
let wasmInput;
|
|
2684
|
-
let zkeyInput;
|
|
2685
|
-
if (IS_BROWSER) {
|
|
2686
|
-
wasmInput = `${circuitsUrl}/withdraw_regular_js/withdraw_regular.wasm`;
|
|
2687
|
-
zkeyInput = `${circuitsUrl}/withdraw_regular_final.zkey`;
|
|
2688
|
-
} else {
|
|
2689
|
-
const wasmUrl = `${circuitsUrl}/withdraw_regular_js/withdraw_regular.wasm`;
|
|
2690
|
-
const zkeyUrl = `${circuitsUrl}/withdraw_regular_final.zkey`;
|
|
2691
|
-
const [wasmData, zkeyData] = await Promise.all([
|
|
2692
|
-
fetchFileAsBuffer(wasmUrl),
|
|
2693
|
-
fetchFileAsBuffer(zkeyUrl)
|
|
2694
|
-
]);
|
|
2695
|
-
wasmInput = wasmData;
|
|
2696
|
-
zkeyInput = zkeyData;
|
|
2697
|
-
}
|
|
2698
|
-
const verification = await verifyCircuitIntegrity(circuitsUrl, "withdraw_regular");
|
|
2699
|
-
if (!verification.valid) {
|
|
2700
|
-
throw new Error(
|
|
2701
|
-
`withdraw_regular circuit integrity verification failed: ${verification.error ?? "hash mismatch"}`
|
|
2702
|
-
);
|
|
2703
|
-
}
|
|
2704
|
-
const circuitInputs = {
|
|
2705
|
-
// Public signals
|
|
2706
|
-
root: inputs.root.toString(),
|
|
2707
|
-
nullifier: inputs.nullifier.toString(),
|
|
2708
|
-
outputs_hash: inputs.outputs_hash.toString(),
|
|
2709
|
-
public_amount: inputs.public_amount.toString(),
|
|
2710
|
-
// Private common inputs
|
|
2711
|
-
amount: inputs.amount.toString(),
|
|
2712
|
-
leaf_index: inputs.leaf_index.toString(),
|
|
2713
|
-
sk: inputs.sk.map((x) => x.toString()),
|
|
2714
|
-
r: inputs.r.map((x) => x.toString()),
|
|
2715
|
-
pathElements: inputs.pathElements.map((x) => x.toString()),
|
|
2716
|
-
pathIndices: inputs.pathIndices,
|
|
2717
|
-
// Outputs
|
|
2718
|
-
num_outputs: inputs.num_outputs,
|
|
2719
|
-
out_addr: inputs.out_addr.map((addr) => addr.map((x) => x.toString())),
|
|
2720
|
-
out_amount: inputs.out_amount.map((x) => x.toString()),
|
|
2721
|
-
out_flags: inputs.out_flags,
|
|
2722
|
-
// Fee calculation
|
|
2723
|
-
var_fee: inputs.var_fee.toString(),
|
|
2724
|
-
rem: inputs.rem.toString()
|
|
2725
|
-
};
|
|
2726
|
-
const { proof, publicSignals } = await snarkjs.groth16.fullProve(circuitInputs, wasmInput, zkeyInput);
|
|
2727
|
-
const proofBytes = proofToBytes(proof);
|
|
2728
|
-
return {
|
|
2729
|
-
proof,
|
|
2730
|
-
publicSignals,
|
|
2731
|
-
proofBytes,
|
|
2732
|
-
publicInputsBytes: new Uint8Array(0)
|
|
2733
|
-
// Not used, kept for compatibility
|
|
2734
|
-
};
|
|
2735
|
-
}
|
|
2736
|
-
async function generateWithdrawSwapProof(inputs, circuitsPath2) {
|
|
2737
|
-
const circuitsUrl = resolveCircuitsUrl(circuitsPath2);
|
|
2738
|
-
let wasmInput;
|
|
2739
|
-
let zkeyInput;
|
|
2740
|
-
if (IS_BROWSER) {
|
|
2741
|
-
wasmInput = `${circuitsUrl}/withdraw_swap_js/withdraw_swap.wasm`;
|
|
2742
|
-
zkeyInput = `${circuitsUrl}/withdraw_swap_final.zkey`;
|
|
2743
|
-
} else {
|
|
2744
|
-
const wasmUrl = `${circuitsUrl}/withdraw_swap_js/withdraw_swap.wasm`;
|
|
2745
|
-
const zkeyUrl = `${circuitsUrl}/withdraw_swap_final.zkey`;
|
|
2746
|
-
const [wasmData, zkeyData] = await Promise.all([
|
|
2747
|
-
fetchFileAsBuffer(wasmUrl),
|
|
2748
|
-
fetchFileAsBuffer(zkeyUrl)
|
|
2749
|
-
]);
|
|
2750
|
-
wasmInput = wasmData;
|
|
2751
|
-
zkeyInput = zkeyData;
|
|
2752
|
-
}
|
|
2753
|
-
const verification = await verifyCircuitIntegrity(circuitsUrl, "withdraw_swap");
|
|
2754
|
-
if (!verification.valid) {
|
|
2755
|
-
throw new Error(
|
|
2756
|
-
`withdraw_swap circuit integrity verification failed: ${verification.error ?? "hash mismatch"}`
|
|
2757
|
-
);
|
|
2758
|
-
}
|
|
2759
|
-
const sk = splitTo2Limbs(inputs.sk_spend);
|
|
2760
|
-
const r = splitTo2Limbs(inputs.r);
|
|
2761
|
-
const circuitInputs = {
|
|
2762
|
-
// Public signals (must be provided for witness generation)
|
|
2763
|
-
root: inputs.root.toString(),
|
|
2764
|
-
nullifier: inputs.nullifier.toString(),
|
|
2765
|
-
outputs_hash: inputs.outputs_hash.toString(),
|
|
2766
|
-
public_amount: inputs.public_amount.toString(),
|
|
2767
|
-
// Private inputs
|
|
2768
|
-
sk: sk.map((x) => x.toString()),
|
|
2769
|
-
r: r.map((x) => x.toString()),
|
|
2770
|
-
amount: inputs.amount.toString(),
|
|
2771
|
-
leaf_index: inputs.leaf_index.toString(),
|
|
2772
|
-
pathElements: inputs.path_elements.map((x) => x.toString()),
|
|
2773
|
-
pathIndices: inputs.path_indices,
|
|
2774
|
-
input_mint: inputs.input_mint.map((x) => x.toString()),
|
|
2775
|
-
output_mint: inputs.output_mint.map((x) => x.toString()),
|
|
2776
|
-
recipient_ata: inputs.recipient_ata.map((x) => x.toString()),
|
|
2777
|
-
min_output_amount: inputs.min_output_amount.toString(),
|
|
2778
|
-
// Note: fee is computed off-chain and validated on-chain:
|
|
2779
|
-
// - fixed_fee is 5000000 (0.005 SOL)
|
|
2780
|
-
// - var_fee and rem are computed from amount * 3 / 1000 (0.3%)
|
|
2781
|
-
var_fee: inputs.var_fee.toString(),
|
|
2782
|
-
rem: inputs.rem.toString()
|
|
2783
|
-
};
|
|
2784
|
-
const { proof, publicSignals } = await snarkjs.groth16.fullProve(
|
|
2785
|
-
circuitInputs,
|
|
2786
|
-
wasmInput,
|
|
2787
|
-
zkeyInput
|
|
2788
|
-
);
|
|
2789
|
-
const proofBytes = proofToBytes(proof);
|
|
2790
|
-
return {
|
|
2791
|
-
proof,
|
|
2792
|
-
publicSignals,
|
|
2793
|
-
proofBytes,
|
|
2794
|
-
publicInputsBytes: new Uint8Array(0)
|
|
2795
|
-
// Not used, kept for compatibility
|
|
2796
|
-
};
|
|
2797
|
-
}
|
|
2798
|
-
async function areCircuitsAvailable(circuitsPath2) {
|
|
2799
|
-
if (!circuitsPath2 || circuitsPath2 === "") {
|
|
2800
|
-
return false;
|
|
2801
|
-
}
|
|
2802
|
-
return isUrl(resolveCircuitsUrl(circuitsPath2));
|
|
2803
|
-
}
|
|
2804
|
-
async function getDefaultCircuitsPath() {
|
|
2805
|
-
return DEFAULT_CIRCUITS_URL;
|
|
2806
|
-
}
|
|
2807
|
-
var EXPECTED_CIRCUIT_HASHES = {
|
|
2808
|
-
withdraw_regular_wasm: "b80c364c926332111945ef437b07a71720fae774e08206cb11b6171f2a2420a3",
|
|
2809
|
-
withdraw_regular_zkey: "6dbca2612f7cc257b897d93384ae9dd09c1617fd1668f8287efb1b124488d144",
|
|
2810
|
-
withdraw_swap_wasm: "a296badc0047c211a4724c96a4e64a77cd75fbde7cfa4cf23af3640a723e5686",
|
|
2811
|
-
withdraw_swap_zkey: "95251954feaac80396651d12a0df3288630fa75db9410aebecb8aaee17a4f6ea"
|
|
2812
|
-
};
|
|
2813
|
-
async function verifyCircuitIntegrity(circuitsPath2, circuit) {
|
|
2814
|
-
const circuitsUrl = resolveCircuitsUrl(circuitsPath2);
|
|
2815
|
-
if (SKIP_CIRCUIT_VERIFY_ENV) {
|
|
2816
|
-
return {
|
|
2817
|
-
valid: true,
|
|
2818
|
-
circuit,
|
|
2819
|
-
error: "Verification skipped (CLOAK_SKIP_CIRCUIT_INTEGRITY_CHECK=1)"
|
|
2820
|
-
};
|
|
2821
|
-
}
|
|
2822
|
-
const expected = circuit === "withdraw_regular" ? {
|
|
2823
|
-
wasm: EXPECTED_CIRCUIT_HASHES.withdraw_regular_wasm,
|
|
2824
|
-
zkey: EXPECTED_CIRCUIT_HASHES.withdraw_regular_zkey
|
|
2825
|
-
} : {
|
|
2826
|
-
wasm: EXPECTED_CIRCUIT_HASHES.withdraw_swap_wasm,
|
|
2827
|
-
zkey: EXPECTED_CIRCUIT_HASHES.withdraw_swap_zkey
|
|
2828
|
-
};
|
|
2829
|
-
const wasmPath = `${circuitsUrl}/${circuit}_js/${circuit}.wasm`;
|
|
2830
|
-
const zkeyPath = `${circuitsUrl}/${circuit}_final.zkey`;
|
|
2831
|
-
try {
|
|
2832
|
-
const [wasmBytes, zkeyBytes] = await Promise.all([
|
|
2833
|
-
fetchFileAsBuffer(wasmPath),
|
|
2834
|
-
fetchFileAsBuffer(zkeyPath)
|
|
2835
|
-
]);
|
|
2836
|
-
const computed = {
|
|
2837
|
-
wasm: await sha256Hex(wasmBytes),
|
|
2838
|
-
zkey: await sha256Hex(zkeyBytes)
|
|
2839
|
-
};
|
|
2840
|
-
if (computed.wasm === expected.wasm && computed.zkey === expected.zkey) {
|
|
2841
|
-
return {
|
|
2842
|
-
valid: true,
|
|
2843
|
-
circuit,
|
|
2844
|
-
computed,
|
|
2845
|
-
expected
|
|
2846
|
-
};
|
|
2847
|
-
}
|
|
2848
|
-
return {
|
|
2849
|
-
valid: false,
|
|
2850
|
-
circuit,
|
|
2851
|
-
error: `Circuit artifact hash mismatch for ${circuit}`,
|
|
2852
|
-
computed,
|
|
2853
|
-
expected
|
|
2854
|
-
};
|
|
2855
|
-
} catch (error) {
|
|
2856
|
-
return {
|
|
2857
|
-
valid: false,
|
|
2858
|
-
circuit,
|
|
2859
|
-
error: `Circuit verification failed: ${error instanceof Error ? error.message : String(error)}`
|
|
2860
|
-
};
|
|
2861
|
-
}
|
|
2862
|
-
}
|
|
2863
|
-
async function verifyAllCircuits(circuitsPath2) {
|
|
2864
|
-
const circuitsUrl = resolveCircuitsUrl(circuitsPath2);
|
|
2865
|
-
const results = await Promise.all([
|
|
2866
|
-
verifyCircuitIntegrity(circuitsUrl, "withdraw_regular"),
|
|
2867
|
-
verifyCircuitIntegrity(circuitsUrl, "withdraw_swap")
|
|
2868
|
-
]);
|
|
2869
|
-
return results;
|
|
2870
|
-
}
|
|
2871
|
-
|
|
2872
2188
|
// src/core/CloakSDK.ts
|
|
2873
2189
|
init_utxo();
|
|
2874
2190
|
|
|
@@ -3088,50 +2404,22 @@ async function computeProofInternal(leafIndex, _nextIndex, subtrees) {
|
|
|
3088
2404
|
}
|
|
3089
2405
|
|
|
3090
2406
|
// src/core/CloakSDK.ts
|
|
3091
|
-
var
|
|
3092
|
-
function createSetLoadedAccountsDataSizeLimitInstruction(bytes) {
|
|
3093
|
-
const data = Buffer.alloc(5);
|
|
3094
|
-
data.writeUInt8(4, 0);
|
|
3095
|
-
data.writeUInt32LE(bytes, 1);
|
|
3096
|
-
return new import_web35.TransactionInstruction({
|
|
3097
|
-
keys: [],
|
|
3098
|
-
programId: COMPUTE_BUDGET_PROGRAM_ID,
|
|
3099
|
-
data
|
|
3100
|
-
});
|
|
3101
|
-
}
|
|
3102
|
-
var CLOAK_PROGRAM_ID = new import_web35.PublicKey("zh1eLd6rSphLejbFfJEneUwzHRfMKxgzrgkfwA6qRkW");
|
|
2407
|
+
var CLOAK_PROGRAM_ID = new import_web33.PublicKey("zh1eLd6rSphLejbFfJEneUwzHRfMKxgzrgkfwA6qRkW");
|
|
3103
2408
|
var CLOAK_API_URL = "https://api.cloak.ag";
|
|
3104
2409
|
var CloakSDK = class {
|
|
3105
|
-
/**
|
|
3106
|
-
* Create a new Cloak SDK client
|
|
3107
|
-
*
|
|
3108
|
-
* @param config - Client configuration
|
|
3109
|
-
*
|
|
3110
|
-
* @example Node.js mode (with keypair)
|
|
3111
|
-
* ```typescript
|
|
3112
|
-
* const sdk = new CloakSDK({
|
|
3113
|
-
* keypairBytes: keypair.secretKey,
|
|
3114
|
-
* network: "devnet"
|
|
3115
|
-
* });
|
|
3116
|
-
* ```
|
|
3117
|
-
*
|
|
3118
|
-
* @example Browser mode (with wallet adapter)
|
|
3119
|
-
* ```typescript
|
|
3120
|
-
* const sdk = new CloakSDK({
|
|
3121
|
-
* wallet: walletAdapter,
|
|
3122
|
-
* network: "devnet"
|
|
3123
|
-
* });
|
|
3124
|
-
* ```
|
|
3125
|
-
*/
|
|
3126
2410
|
constructor(config) {
|
|
3127
2411
|
if (!config.keypairBytes && !config.wallet) {
|
|
3128
|
-
throw new
|
|
2412
|
+
throw new CloakError(
|
|
2413
|
+
"Must provide either keypairBytes (Node.js) or wallet (Browser)",
|
|
2414
|
+
"validation",
|
|
2415
|
+
false
|
|
2416
|
+
);
|
|
3129
2417
|
}
|
|
3130
2418
|
if (config.debug) {
|
|
3131
2419
|
setDebugMode(true);
|
|
3132
2420
|
}
|
|
3133
2421
|
if (config.keypairBytes) {
|
|
3134
|
-
this.keypair =
|
|
2422
|
+
this.keypair = import_web33.Keypair.fromSecretKey(config.keypairBytes);
|
|
3135
2423
|
}
|
|
3136
2424
|
this.wallet = config.wallet;
|
|
3137
2425
|
this.cloakKeys = config.cloakKeys;
|
|
@@ -3145,13 +2433,9 @@ var CloakSDK = class {
|
|
|
3145
2433
|
}
|
|
3146
2434
|
}
|
|
3147
2435
|
const programId = config.programId || CLOAK_PROGRAM_ID;
|
|
3148
|
-
const { pool, merkleTree, treasury } = getShieldPoolPDAs(
|
|
3149
|
-
programId,
|
|
3150
|
-
NATIVE_SOL_MINT
|
|
3151
|
-
);
|
|
2436
|
+
const { pool, merkleTree, treasury } = getShieldPoolPDAs(programId, NATIVE_SOL_MINT);
|
|
3152
2437
|
this.config = {
|
|
3153
2438
|
network: config.network || "mainnet",
|
|
3154
|
-
keypairBytes: config.keypairBytes,
|
|
3155
2439
|
cloakKeys: config.cloakKeys,
|
|
3156
2440
|
programId,
|
|
3157
2441
|
poolAddress: pool,
|
|
@@ -3160,9 +2444,7 @@ var CloakSDK = class {
|
|
|
3160
2444
|
debug: config.debug
|
|
3161
2445
|
};
|
|
3162
2446
|
}
|
|
3163
|
-
/**
|
|
3164
|
-
* Get the public key for deposits (from keypair or wallet)
|
|
3165
|
-
*/
|
|
2447
|
+
/** Public key of the configured signer (keypair or wallet). */
|
|
3166
2448
|
getPublicKey() {
|
|
3167
2449
|
if (this.keypair) {
|
|
3168
2450
|
return this.keypair.publicKey;
|
|
@@ -3170,877 +2452,23 @@ var CloakSDK = class {
|
|
|
3170
2452
|
if (this.wallet?.publicKey) {
|
|
3171
2453
|
return this.wallet.publicKey;
|
|
3172
2454
|
}
|
|
3173
|
-
throw new
|
|
2455
|
+
throw new CloakError(
|
|
2456
|
+
"No public key available - wallet not connected and no keypair provided",
|
|
2457
|
+
"wallet",
|
|
2458
|
+
false
|
|
2459
|
+
);
|
|
3174
2460
|
}
|
|
3175
|
-
/**
|
|
3176
|
-
* Check if the SDK is using a wallet adapter
|
|
3177
|
-
*/
|
|
2461
|
+
/** True when the SDK was constructed with a wallet adapter (browser). */
|
|
3178
2462
|
isWalletMode() {
|
|
3179
2463
|
return !!this.wallet && !this.keypair;
|
|
3180
2464
|
}
|
|
3181
2465
|
/**
|
|
3182
|
-
*
|
|
3183
|
-
*
|
|
3184
|
-
* Creates a new note (or uses a provided one), submits a deposit transaction,
|
|
3185
|
-
* and registers with the indexer.
|
|
3186
|
-
*
|
|
3187
|
-
* @param connection - Solana connection
|
|
3188
|
-
* @param payer - Payer wallet with sendTransaction method
|
|
3189
|
-
* @param amountOrNote - Amount in lamports OR an existing note to deposit
|
|
3190
|
-
* @param options - Optional configuration
|
|
3191
|
-
* @returns Deposit result with note and transaction info
|
|
3192
|
-
*
|
|
3193
|
-
* @example
|
|
3194
|
-
* ```typescript
|
|
3195
|
-
* // Generate and deposit in one step
|
|
3196
|
-
* const result = await client.deposit(
|
|
3197
|
-
* connection,
|
|
3198
|
-
* wallet,
|
|
3199
|
-
* 1_000_000_000,
|
|
3200
|
-
* {
|
|
3201
|
-
* onProgress: (status) => console.log(status)
|
|
3202
|
-
* }
|
|
3203
|
-
* );
|
|
3204
|
-
*
|
|
3205
|
-
* // Or deposit a pre-generated note
|
|
3206
|
-
* const note = client.generateNote(1_000_000_000);
|
|
3207
|
-
* const result = await client.deposit(connection, wallet, note);
|
|
3208
|
-
* ```
|
|
3209
|
-
*/
|
|
3210
|
-
async deposit(connection, amountOrNote, options) {
|
|
3211
|
-
try {
|
|
3212
|
-
let note;
|
|
3213
|
-
let isNewNote = false;
|
|
3214
|
-
if (typeof amountOrNote === "number") {
|
|
3215
|
-
options?.onProgress?.("generating_note", { message: "Generating note with secrets..." });
|
|
3216
|
-
note = await generateNote(amountOrNote, this.config.network);
|
|
3217
|
-
isNewNote = true;
|
|
3218
|
-
} else {
|
|
3219
|
-
note = amountOrNote;
|
|
3220
|
-
if (note.depositSignature) {
|
|
3221
|
-
throw new Error("Note has already been deposited");
|
|
3222
|
-
}
|
|
3223
|
-
}
|
|
3224
|
-
if (isNewNote) {
|
|
3225
|
-
await this.storage.saveNote(note);
|
|
3226
|
-
if (options?.onNoteGenerated) {
|
|
3227
|
-
options?.onProgress?.("awaiting_note_acknowledgment", {
|
|
3228
|
-
message: "Waiting for note to be saved before proceeding with deposit..."
|
|
3229
|
-
});
|
|
3230
|
-
try {
|
|
3231
|
-
await options.onNoteGenerated(note);
|
|
3232
|
-
} catch (error) {
|
|
3233
|
-
throw new Error(
|
|
3234
|
-
`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.`
|
|
3235
|
-
);
|
|
3236
|
-
}
|
|
3237
|
-
}
|
|
3238
|
-
options?.onProgress?.("note_saved", {
|
|
3239
|
-
message: "Note saved. Proceeding with on-chain deposit..."
|
|
3240
|
-
});
|
|
3241
|
-
}
|
|
3242
|
-
const payerPubkey = this.getPublicKey();
|
|
3243
|
-
const balance = await connection.getBalance(payerPubkey);
|
|
3244
|
-
const requiredAmount = note.amount + 5e3;
|
|
3245
|
-
if (balance < requiredAmount) {
|
|
3246
|
-
throw new Error(
|
|
3247
|
-
`Insufficient balance. Required: ${requiredAmount} lamports (${note.amount} + fees), Available: ${balance} lamports`
|
|
3248
|
-
);
|
|
3249
|
-
}
|
|
3250
|
-
const commitmentBytes = hexToBytes(note.commitment);
|
|
3251
|
-
const programId = this.config.programId || CLOAK_PROGRAM_ID;
|
|
3252
|
-
const depositIx = createDepositInstruction({
|
|
3253
|
-
programId,
|
|
3254
|
-
payer: payerPubkey,
|
|
3255
|
-
pool: this.config.poolAddress,
|
|
3256
|
-
merkleTree: this.config.merkleTreeAddress,
|
|
3257
|
-
amount: note.amount,
|
|
3258
|
-
commitment: commitmentBytes
|
|
3259
|
-
});
|
|
3260
|
-
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
|
|
3261
|
-
const priorityFee = options?.priorityFee ?? 1e4;
|
|
3262
|
-
const cuPriceIx = import_web35.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priorityFee });
|
|
3263
|
-
const dataSizeLimit = options?.loadedAccountsDataSizeLimit ?? 256 * 1024;
|
|
3264
|
-
const dataSizeLimitIx = dataSizeLimit > 0 ? createSetLoadedAccountsDataSizeLimitInstruction(dataSizeLimit) : null;
|
|
3265
|
-
let computeUnits;
|
|
3266
|
-
if (options?.optimizeCU && this.keypair) {
|
|
3267
|
-
options?.onProgress?.("simulating", { message: "Simulating transaction for optimal CU..." });
|
|
3268
|
-
const simCuLimitIx = import_web35.ComputeBudgetProgram.setComputeUnitLimit({ units: 2e5 });
|
|
3269
|
-
const simTransaction = new import_web35.Transaction({
|
|
3270
|
-
feePayer: payerPubkey,
|
|
3271
|
-
recentBlockhash: blockhash
|
|
3272
|
-
}).add(simCuLimitIx).add(cuPriceIx);
|
|
3273
|
-
if (dataSizeLimitIx) {
|
|
3274
|
-
simTransaction.add(dataSizeLimitIx);
|
|
3275
|
-
}
|
|
3276
|
-
simTransaction.add(depositIx);
|
|
3277
|
-
try {
|
|
3278
|
-
const simulation = await connection.simulateTransaction(simTransaction, [this.keypair]);
|
|
3279
|
-
if (simulation.value.err) {
|
|
3280
|
-
console.warn("Simulation failed, using default CU:", simulation.value.err);
|
|
3281
|
-
computeUnits = 4e4;
|
|
3282
|
-
} else {
|
|
3283
|
-
const simulatedCU = simulation.value.unitsConsumed ?? 3e4;
|
|
3284
|
-
computeUnits = Math.ceil(simulatedCU * 1.2);
|
|
3285
|
-
console.log(`CU optimization: ${simulatedCU} simulated \u2192 ${computeUnits} limit`);
|
|
3286
|
-
}
|
|
3287
|
-
} catch (simError) {
|
|
3288
|
-
console.warn("Simulation RPC error, using default CU:", simError);
|
|
3289
|
-
computeUnits = 4e4;
|
|
3290
|
-
}
|
|
3291
|
-
} else if (options?.optimizeCU && this.wallet) {
|
|
3292
|
-
console.warn("CU optimization via simulation not available in wallet mode (would require double signing). Using default.");
|
|
3293
|
-
computeUnits = options?.computeUnits ?? 4e4;
|
|
3294
|
-
} else {
|
|
3295
|
-
computeUnits = options?.computeUnits ?? 4e4;
|
|
3296
|
-
}
|
|
3297
|
-
const cuLimitIx = import_web35.ComputeBudgetProgram.setComputeUnitLimit({ units: computeUnits });
|
|
3298
|
-
const transaction = new import_web35.Transaction({
|
|
3299
|
-
feePayer: payerPubkey,
|
|
3300
|
-
recentBlockhash: blockhash
|
|
3301
|
-
}).add(cuLimitIx).add(cuPriceIx);
|
|
3302
|
-
if (dataSizeLimitIx) {
|
|
3303
|
-
transaction.add(dataSizeLimitIx);
|
|
3304
|
-
}
|
|
3305
|
-
transaction.add(depositIx);
|
|
3306
|
-
if (!transaction.feePayer) {
|
|
3307
|
-
throw new Error("Transaction feePayer is not set");
|
|
3308
|
-
}
|
|
3309
|
-
if (!transaction.recentBlockhash) {
|
|
3310
|
-
throw new Error("Transaction recentBlockhash is not set");
|
|
3311
|
-
}
|
|
3312
|
-
let signature;
|
|
3313
|
-
if (this.wallet) {
|
|
3314
|
-
if (!this.wallet.publicKey) {
|
|
3315
|
-
throw new Error("Wallet not connected - publicKey is null");
|
|
3316
|
-
}
|
|
3317
|
-
if (this.wallet.signTransaction) {
|
|
3318
|
-
try {
|
|
3319
|
-
const signedTransaction = await this.wallet.signTransaction(transaction);
|
|
3320
|
-
const rawTransaction = signedTransaction.serialize();
|
|
3321
|
-
signature = await connection.sendRawTransaction(rawTransaction, {
|
|
3322
|
-
skipPreflight: options?.skipPreflight || false,
|
|
3323
|
-
preflightCommitment: "confirmed",
|
|
3324
|
-
maxRetries: 3
|
|
3325
|
-
});
|
|
3326
|
-
} catch (signError) {
|
|
3327
|
-
if (this.wallet.sendTransaction) {
|
|
3328
|
-
signature = await this.wallet.sendTransaction(transaction, connection, {
|
|
3329
|
-
skipPreflight: options?.skipPreflight || false,
|
|
3330
|
-
preflightCommitment: "confirmed",
|
|
3331
|
-
maxRetries: 3
|
|
3332
|
-
});
|
|
3333
|
-
} else {
|
|
3334
|
-
throw signError;
|
|
3335
|
-
}
|
|
3336
|
-
}
|
|
3337
|
-
} else if (this.wallet.sendTransaction) {
|
|
3338
|
-
signature = await this.wallet.sendTransaction(transaction, connection, {
|
|
3339
|
-
skipPreflight: options?.skipPreflight || false,
|
|
3340
|
-
preflightCommitment: "confirmed",
|
|
3341
|
-
maxRetries: 3
|
|
3342
|
-
});
|
|
3343
|
-
} else {
|
|
3344
|
-
throw new Error("Wallet adapter must provide either sendTransaction or signTransaction");
|
|
3345
|
-
}
|
|
3346
|
-
} else if (this.keypair) {
|
|
3347
|
-
signature = await connection.sendTransaction(transaction, [this.keypair], {
|
|
3348
|
-
skipPreflight: options?.skipPreflight || false,
|
|
3349
|
-
preflightCommitment: "confirmed",
|
|
3350
|
-
maxRetries: 3
|
|
3351
|
-
});
|
|
3352
|
-
} else {
|
|
3353
|
-
throw new Error("No signing method available - provide keypair or wallet");
|
|
3354
|
-
}
|
|
3355
|
-
const confirmation = await connection.confirmTransaction({
|
|
3356
|
-
signature,
|
|
3357
|
-
blockhash,
|
|
3358
|
-
lastValidBlockHeight
|
|
3359
|
-
});
|
|
3360
|
-
if (confirmation.value.err) {
|
|
3361
|
-
throw new Error(
|
|
3362
|
-
`Transaction failed: ${JSON.stringify(confirmation.value.err)}`
|
|
3363
|
-
);
|
|
3364
|
-
}
|
|
3365
|
-
const txDetails = await connection.getTransaction(signature, {
|
|
3366
|
-
commitment: "confirmed",
|
|
3367
|
-
maxSupportedTransactionVersion: 0
|
|
3368
|
-
});
|
|
3369
|
-
const depositSlot = txDetails?.slot ?? 0;
|
|
3370
|
-
let leafIndex = null;
|
|
3371
|
-
let root = null;
|
|
3372
|
-
try {
|
|
3373
|
-
if (txDetails?.meta?.logMessages) {
|
|
3374
|
-
for (const log of txDetails.meta.logMessages) {
|
|
3375
|
-
if (log.includes("Program data:")) {
|
|
3376
|
-
try {
|
|
3377
|
-
const parts = log.split("Program data:");
|
|
3378
|
-
if (parts.length > 1) {
|
|
3379
|
-
const base64Data = parts[1].trim();
|
|
3380
|
-
const eventData = Buffer.from(base64Data, "base64");
|
|
3381
|
-
if (eventData.length >= 81 && eventData[0] === 1) {
|
|
3382
|
-
const parsedLeafIndex = Number(eventData.readBigUInt64LE(1));
|
|
3383
|
-
const loggedCommitment = eventData.slice(9, 41);
|
|
3384
|
-
if (Buffer.compare(loggedCommitment, Buffer.from(commitmentBytes)) === 0) {
|
|
3385
|
-
leafIndex = parsedLeafIndex;
|
|
3386
|
-
const loggedRoot = eventData.slice(49, 81);
|
|
3387
|
-
root = Buffer.from(loggedRoot).toString("hex");
|
|
3388
|
-
break;
|
|
3389
|
-
}
|
|
3390
|
-
}
|
|
3391
|
-
}
|
|
3392
|
-
} catch (e) {
|
|
3393
|
-
continue;
|
|
3394
|
-
}
|
|
3395
|
-
}
|
|
3396
|
-
}
|
|
3397
|
-
}
|
|
3398
|
-
} catch (e) {
|
|
3399
|
-
}
|
|
3400
|
-
if (leafIndex === null) {
|
|
3401
|
-
const programId2 = this.config.programId || CLOAK_PROGRAM_ID;
|
|
3402
|
-
const mintForSOL = NATIVE_SOL_MINT;
|
|
3403
|
-
const { merkleTree } = getShieldPoolPDAs(programId2, mintForSOL);
|
|
3404
|
-
const merkleTreeAccount = await connection.getAccountInfo(merkleTree);
|
|
3405
|
-
if (!merkleTreeAccount) {
|
|
3406
|
-
throw new Error("Failed to fetch merkle tree account");
|
|
3407
|
-
}
|
|
3408
|
-
const nextIndex = Number(merkleTreeAccount.data.readBigUInt64LE(32));
|
|
3409
|
-
leafIndex = nextIndex - 1;
|
|
3410
|
-
const rootBytes = merkleTreeAccount.data.slice(1064, 1096);
|
|
3411
|
-
root = Buffer.from(rootBytes).toString("hex");
|
|
3412
|
-
}
|
|
3413
|
-
if (leafIndex === null || root === null) {
|
|
3414
|
-
throw new Error("Failed to get leaf index and root from transaction logs or on-chain state");
|
|
3415
|
-
}
|
|
3416
|
-
let merkleProof;
|
|
3417
|
-
try {
|
|
3418
|
-
const programId2 = this.config.programId || CLOAK_PROGRAM_ID;
|
|
3419
|
-
const mintForSOL = NATIVE_SOL_MINT;
|
|
3420
|
-
const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId2, mintForSOL);
|
|
3421
|
-
const chainProof = await computeProofFromChain(connection, merkleTreePDA, leafIndex);
|
|
3422
|
-
merkleProof = {
|
|
3423
|
-
pathElements: chainProof.pathElements,
|
|
3424
|
-
pathIndices: chainProof.pathIndices
|
|
3425
|
-
};
|
|
3426
|
-
root = chainProof.root;
|
|
3427
|
-
} catch (proofError) {
|
|
3428
|
-
console.warn("[SDK] Could not compute proof from chain, will fetch at withdrawal:", proofError);
|
|
3429
|
-
}
|
|
3430
|
-
const updatedNote = updateNoteWithDeposit(note, {
|
|
3431
|
-
signature,
|
|
3432
|
-
slot: depositSlot,
|
|
3433
|
-
leafIndex,
|
|
3434
|
-
root,
|
|
3435
|
-
merkleProof
|
|
3436
|
-
});
|
|
3437
|
-
await this.storage.updateNote(note.commitment, {
|
|
3438
|
-
depositSignature: signature,
|
|
3439
|
-
depositSlot,
|
|
3440
|
-
leafIndex,
|
|
3441
|
-
root,
|
|
3442
|
-
merkleProof
|
|
3443
|
-
});
|
|
3444
|
-
return {
|
|
3445
|
-
note: updatedNote,
|
|
3446
|
-
signature,
|
|
3447
|
-
leafIndex,
|
|
3448
|
-
root
|
|
3449
|
-
};
|
|
3450
|
-
} catch (error) {
|
|
3451
|
-
throw this.wrapError(error, "Deposit failed");
|
|
3452
|
-
}
|
|
3453
|
-
}
|
|
3454
|
-
/**
|
|
3455
|
-
* Private transfer with up to 5 recipients
|
|
3456
|
-
*
|
|
3457
|
-
* Handles the complete private transfer flow:
|
|
3458
|
-
* 1. If note is not deposited, deposits it first and waits for confirmation
|
|
3459
|
-
* 2. Generates a zero-knowledge proof
|
|
3460
|
-
* 3. Submits the withdrawal via relay service to recipients
|
|
3461
|
-
*
|
|
3462
|
-
* This is the main method for performing private transfers - it handles everything!
|
|
3463
|
-
*
|
|
3464
|
-
* @param connection - Solana connection (required for deposit if not already deposited)
|
|
3465
|
-
* @param payer - Payer wallet (required for deposit if not already deposited)
|
|
3466
|
-
* @param note - Note to spend (can be deposited or not)
|
|
3467
|
-
* @param recipients - Array of 1-5 recipients with amounts
|
|
3468
|
-
* @param options - Optional configuration
|
|
3469
|
-
* @returns Transfer result with signature and outputs
|
|
3470
|
-
*
|
|
3471
|
-
* @example
|
|
3472
|
-
* ```typescript
|
|
3473
|
-
* // Create a note (not deposited yet)
|
|
3474
|
-
* const note = client.generateNote(1_000_000_000);
|
|
3475
|
-
*
|
|
3476
|
-
* // privateTransfer handles the full flow: deposit + withdraw
|
|
3477
|
-
* const result = await client.privateTransfer(
|
|
3478
|
-
* connection,
|
|
3479
|
-
* wallet,
|
|
3480
|
-
* note,
|
|
3481
|
-
* [
|
|
3482
|
-
* { recipient: new PublicKey("..."), amount: 500_000_000 },
|
|
3483
|
-
* { recipient: new PublicKey("..."), amount: 492_500_000 }
|
|
3484
|
-
* ],
|
|
3485
|
-
* {
|
|
3486
|
-
* relayFeeBps: 50, // 0.5%
|
|
3487
|
-
* onProgress: (status) => console.log(status),
|
|
3488
|
-
* onProofProgress: (pct) => console.log(`Proof: ${pct}%`)
|
|
3489
|
-
* }
|
|
3490
|
-
* );
|
|
3491
|
-
* console.log(`Success! TX: ${result.signature}`);
|
|
3492
|
-
* ```
|
|
3493
|
-
*/
|
|
3494
|
-
async privateTransfer(connection, note, recipients, options) {
|
|
3495
|
-
if (!isWithdrawable(note)) {
|
|
3496
|
-
const depositResult = await this.deposit(connection, note, {
|
|
3497
|
-
skipPreflight: false
|
|
3498
|
-
});
|
|
3499
|
-
note = depositResult.note;
|
|
3500
|
-
}
|
|
3501
|
-
const protocolFee = note.amount - getDistributableAmount(note.amount);
|
|
3502
|
-
const feeBps = Math.ceil(protocolFee * 1e4 / note.amount);
|
|
3503
|
-
const distributableAmount = getDistributableAmount(note.amount);
|
|
3504
|
-
validateTransfers(recipients, distributableAmount);
|
|
3505
|
-
if (!note.leafIndex && note.leafIndex !== 0) {
|
|
3506
|
-
throw new Error("Note must have a leaf index (note must be deposited)");
|
|
3507
|
-
}
|
|
3508
|
-
if (!isValidHex(note.r, 32)) {
|
|
3509
|
-
throw new Error("Note r must be 64 hex characters (32 bytes)");
|
|
3510
|
-
}
|
|
3511
|
-
if (!isValidHex(note.sk_spend, 32)) {
|
|
3512
|
-
throw new Error("Note sk_spend must be 64 hex characters (32 bytes)");
|
|
3513
|
-
}
|
|
3514
|
-
const nullifier = await computeNullifierAsync(note.sk_spend, note.leafIndex);
|
|
3515
|
-
const nullifierHex = nullifier.toString(16).padStart(64, "0");
|
|
3516
|
-
const outputsHash = await computeOutputsHashAsync(recipients);
|
|
3517
|
-
const outputsHashHex = outputsHash.toString(16).padStart(64, "0");
|
|
3518
|
-
const circuitsPath2 = await getDefaultCircuitsPath();
|
|
3519
|
-
const useDirectProof = await areCircuitsAvailable(circuitsPath2);
|
|
3520
|
-
if (!useDirectProof) {
|
|
3521
|
-
throw new Error(
|
|
3522
|
-
`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.`
|
|
3523
|
-
);
|
|
3524
|
-
}
|
|
3525
|
-
const MAX_PROOF_RETRIES = 3;
|
|
3526
|
-
let lastError = null;
|
|
3527
|
-
for (let attempt = 0; attempt < MAX_PROOF_RETRIES; attempt++) {
|
|
3528
|
-
try {
|
|
3529
|
-
if (attempt > 0) {
|
|
3530
|
-
options?.onProgress?.(`proof_refresh_${attempt + 1}`);
|
|
3531
|
-
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
3532
|
-
}
|
|
3533
|
-
let merkleProof;
|
|
3534
|
-
let merkleRoot;
|
|
3535
|
-
const hasStoredProof = note.merkleProof && note.root && note.merkleProof.pathElements && note.merkleProof.pathElements.length > 0;
|
|
3536
|
-
if (attempt === 0 && hasStoredProof) {
|
|
3537
|
-
merkleProof = note.merkleProof;
|
|
3538
|
-
merkleRoot = note.root;
|
|
3539
|
-
} else {
|
|
3540
|
-
const programId = this.config.programId || CLOAK_PROGRAM_ID;
|
|
3541
|
-
const mintForSOL = NATIVE_SOL_MINT;
|
|
3542
|
-
const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId, mintForSOL);
|
|
3543
|
-
const chainProof = await computeProofFromChain(
|
|
3544
|
-
connection,
|
|
3545
|
-
merkleTreePDA,
|
|
3546
|
-
note.leafIndex
|
|
3547
|
-
);
|
|
3548
|
-
merkleProof = {
|
|
3549
|
-
pathElements: chainProof.pathElements,
|
|
3550
|
-
pathIndices: chainProof.pathIndices,
|
|
3551
|
-
root: chainProof.root
|
|
3552
|
-
};
|
|
3553
|
-
merkleRoot = chainProof.root;
|
|
3554
|
-
note.merkleProof = merkleProof;
|
|
3555
|
-
note.root = merkleRoot;
|
|
3556
|
-
}
|
|
3557
|
-
if (!merkleProof || !merkleRoot) {
|
|
3558
|
-
throw new Error("Failed to get Merkle proof from chain");
|
|
3559
|
-
}
|
|
3560
|
-
if (!merkleProof.pathElements || merkleProof.pathElements.length === 0) {
|
|
3561
|
-
throw new Error("Merkle proof is invalid: missing path elements");
|
|
3562
|
-
}
|
|
3563
|
-
if (merkleProof.pathElements.length !== merkleProof.pathIndices.length) {
|
|
3564
|
-
throw new Error("Merkle proof is invalid: path elements and indices length mismatch");
|
|
3565
|
-
}
|
|
3566
|
-
for (let i = 0; i < merkleProof.pathIndices.length; i++) {
|
|
3567
|
-
const idx = merkleProof.pathIndices[i];
|
|
3568
|
-
if (idx !== 0 && idx !== 1) {
|
|
3569
|
-
throw new Error(`Merkle proof path index at position ${i} must be 0 or 1, got ${idx}`);
|
|
3570
|
-
}
|
|
3571
|
-
}
|
|
3572
|
-
if (!isValidHex(merkleRoot, 32)) {
|
|
3573
|
-
throw new Error("Merkle root must be 64 hex characters (32 bytes)");
|
|
3574
|
-
}
|
|
3575
|
-
for (let i = 0; i < merkleProof.pathElements.length; i++) {
|
|
3576
|
-
const element = merkleProof.pathElements[i];
|
|
3577
|
-
if (typeof element !== "string" || !isValidHex(element, 32)) {
|
|
3578
|
-
throw new Error(`Merkle proof path element at position ${i} must be 64 hex characters (32 bytes)`);
|
|
3579
|
-
}
|
|
3580
|
-
}
|
|
3581
|
-
const sk_spend_bigint = BigInt("0x" + note.sk_spend);
|
|
3582
|
-
const r_bigint = BigInt("0x" + note.r);
|
|
3583
|
-
const root_bigint = BigInt("0x" + merkleRoot);
|
|
3584
|
-
const nullifier_bigint = BigInt("0x" + nullifierHex);
|
|
3585
|
-
const outputs_hash_bigint = BigInt("0x" + outputsHashHex);
|
|
3586
|
-
const amount_bigint = BigInt(note.amount);
|
|
3587
|
-
const pathElements = merkleProof.pathElements.map((p) => BigInt("0x" + p));
|
|
3588
|
-
const outAddr = [];
|
|
3589
|
-
for (let i = 0; i < 5; i++) {
|
|
3590
|
-
if (i < recipients.length) {
|
|
3591
|
-
const [lo, hi] = pubkeyToLimbs(recipients[i].recipient.toBytes());
|
|
3592
|
-
outAddr.push([lo, hi]);
|
|
3593
|
-
} else {
|
|
3594
|
-
outAddr.push([0n, 0n]);
|
|
3595
|
-
}
|
|
3596
|
-
}
|
|
3597
|
-
const t = amount_bigint * 3n;
|
|
3598
|
-
const varFee = t / 1000n;
|
|
3599
|
-
const rem = t % 1000n;
|
|
3600
|
-
const outAmount = [];
|
|
3601
|
-
const outFlags = [];
|
|
3602
|
-
for (let i = 0; i < 5; i++) {
|
|
3603
|
-
if (i < recipients.length) {
|
|
3604
|
-
outAmount.push(BigInt(recipients[i].amount));
|
|
3605
|
-
outFlags.push(1);
|
|
3606
|
-
} else {
|
|
3607
|
-
outAmount.push(0n);
|
|
3608
|
-
outFlags.push(0);
|
|
3609
|
-
}
|
|
3610
|
-
}
|
|
3611
|
-
const sk = splitTo2Limbs(sk_spend_bigint);
|
|
3612
|
-
const r = splitTo2Limbs(r_bigint);
|
|
3613
|
-
if (attempt === 0) {
|
|
3614
|
-
const computedCommitment = await computeCommitment(amount_bigint, r_bigint, sk_spend_bigint);
|
|
3615
|
-
const noteCommitment = BigInt("0x" + note.commitment);
|
|
3616
|
-
if (computedCommitment !== noteCommitment) {
|
|
3617
|
-
throw new Error(
|
|
3618
|
-
`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.`
|
|
3619
|
-
);
|
|
3620
|
-
}
|
|
3621
|
-
}
|
|
3622
|
-
const proofInputs = {
|
|
3623
|
-
root: root_bigint,
|
|
3624
|
-
nullifier: nullifier_bigint,
|
|
3625
|
-
outputs_hash: outputs_hash_bigint,
|
|
3626
|
-
public_amount: amount_bigint,
|
|
3627
|
-
amount: amount_bigint,
|
|
3628
|
-
leaf_index: BigInt(note.leafIndex),
|
|
3629
|
-
sk,
|
|
3630
|
-
r,
|
|
3631
|
-
pathElements,
|
|
3632
|
-
pathIndices: merkleProof.pathIndices,
|
|
3633
|
-
num_outputs: recipients.length,
|
|
3634
|
-
out_addr: outAddr,
|
|
3635
|
-
out_amount: outAmount,
|
|
3636
|
-
out_flags: outFlags,
|
|
3637
|
-
var_fee: varFee,
|
|
3638
|
-
rem
|
|
3639
|
-
};
|
|
3640
|
-
options?.onProgress?.("proof_generating");
|
|
3641
|
-
const proofResult = await generateWithdrawRegularProof(proofInputs, circuitsPath2);
|
|
3642
|
-
options?.onProgress?.("proof_complete");
|
|
3643
|
-
const proofHex = Buffer.from(proofResult.proofBytes).toString("hex");
|
|
3644
|
-
const finalPublicInputs = {
|
|
3645
|
-
root: merkleRoot,
|
|
3646
|
-
nf: nullifierHex,
|
|
3647
|
-
outputs_hash: outputsHashHex,
|
|
3648
|
-
amount: note.amount
|
|
3649
|
-
};
|
|
3650
|
-
const metadataBundle = options?.metadataBundle;
|
|
3651
|
-
const signature = await this.relay.submitWithdraw(
|
|
3652
|
-
{
|
|
3653
|
-
proof: proofHex,
|
|
3654
|
-
publicInputs: finalPublicInputs,
|
|
3655
|
-
outputs: recipients.map((r2) => ({
|
|
3656
|
-
recipient: r2.recipient.toBase58(),
|
|
3657
|
-
amount: r2.amount
|
|
3658
|
-
})),
|
|
3659
|
-
feeBps,
|
|
3660
|
-
// Use calculated protocol fee BPS
|
|
3661
|
-
metadataBundle
|
|
3662
|
-
},
|
|
3663
|
-
options?.onProgress
|
|
3664
|
-
);
|
|
3665
|
-
return {
|
|
3666
|
-
signature,
|
|
3667
|
-
outputs: recipients.map((r2) => ({
|
|
3668
|
-
recipient: r2.recipient.toBase58(),
|
|
3669
|
-
amount: r2.amount
|
|
3670
|
-
})),
|
|
3671
|
-
nullifier: nullifierHex,
|
|
3672
|
-
root: merkleRoot
|
|
3673
|
-
};
|
|
3674
|
-
} catch (error) {
|
|
3675
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
3676
|
-
if (error instanceof RootNotFoundError) {
|
|
3677
|
-
console.warn(
|
|
3678
|
-
`[Cloak SDK] Merkle root expired (attempt ${attempt + 1}/${MAX_PROOF_RETRIES}). Regenerating proof with fresh root...`
|
|
3679
|
-
);
|
|
3680
|
-
continue;
|
|
3681
|
-
}
|
|
3682
|
-
throw error;
|
|
3683
|
-
}
|
|
3684
|
-
}
|
|
3685
|
-
throw new Error(
|
|
3686
|
-
`Withdrawal failed after ${MAX_PROOF_RETRIES} proof refresh attempts. The Merkle tree is updating too rapidly. Last error: ${lastError?.message}`
|
|
3687
|
-
);
|
|
3688
|
-
}
|
|
3689
|
-
/**
|
|
3690
|
-
* Withdraw to a single recipient
|
|
3691
|
-
*
|
|
3692
|
-
* Convenience method for withdrawing to one address.
|
|
3693
|
-
* Handles the complete flow: deposits if needed, then withdraws.
|
|
3694
|
-
*
|
|
3695
|
-
* @param connection - Solana connection
|
|
3696
|
-
* @param payer - Payer wallet
|
|
3697
|
-
* @param note - Note to spend
|
|
3698
|
-
* @param recipient - Recipient address
|
|
3699
|
-
* @param options - Optional configuration
|
|
3700
|
-
* @returns Transfer result
|
|
3701
|
-
*
|
|
3702
|
-
* @example
|
|
3703
|
-
* ```typescript
|
|
3704
|
-
* const note = client.generateNote(1_000_000_000);
|
|
3705
|
-
* const result = await client.withdraw(
|
|
3706
|
-
* connection,
|
|
3707
|
-
* wallet,
|
|
3708
|
-
* note,
|
|
3709
|
-
* new PublicKey("..."),
|
|
3710
|
-
* { withdrawAll: true }
|
|
3711
|
-
* );
|
|
3712
|
-
* ```
|
|
3713
|
-
*/
|
|
3714
|
-
async withdraw(connection, note, recipient, options) {
|
|
3715
|
-
const withdrawAll = options?.withdrawAll ?? true;
|
|
3716
|
-
const amount = withdrawAll ? getDistributableAmount(note.amount) : options?.amount || note.amount;
|
|
3717
|
-
if (!withdrawAll && !options?.amount) {
|
|
3718
|
-
throw new Error("Must specify amount or set withdrawAll: true");
|
|
3719
|
-
}
|
|
3720
|
-
return this.privateTransfer(
|
|
3721
|
-
connection,
|
|
3722
|
-
note,
|
|
3723
|
-
[{ recipient, amount }],
|
|
3724
|
-
options
|
|
3725
|
-
);
|
|
3726
|
-
}
|
|
3727
|
-
/**
|
|
3728
|
-
* Send SOL privately to multiple recipients
|
|
3729
|
-
*
|
|
3730
|
-
* Convenience method that wraps privateTransfer with a simpler API.
|
|
3731
|
-
* Handles the complete flow: deposits if needed, then sends to recipients.
|
|
3732
|
-
*
|
|
3733
|
-
* @param connection - Solana connection
|
|
3734
|
-
* @param note - Note to spend
|
|
3735
|
-
* @param recipients - Array of 1-5 recipients with amounts
|
|
3736
|
-
* @param options - Optional configuration
|
|
3737
|
-
* @returns Transfer result
|
|
3738
|
-
*
|
|
3739
|
-
* @example
|
|
3740
|
-
* ```typescript
|
|
3741
|
-
* const note = client.generateNote(1_000_000_000);
|
|
3742
|
-
* const result = await client.send(
|
|
3743
|
-
* connection,
|
|
3744
|
-
* note,
|
|
3745
|
-
* [
|
|
3746
|
-
* { recipient: new PublicKey("..."), amount: 500_000_000 },
|
|
3747
|
-
* { recipient: new PublicKey("..."), amount: 492_500_000 }
|
|
3748
|
-
* ]
|
|
3749
|
-
* );
|
|
3750
|
-
* ```
|
|
3751
|
-
*/
|
|
3752
|
-
async send(connection, note, recipients, options) {
|
|
3753
|
-
return this.privateTransfer(connection, note, recipients, options);
|
|
3754
|
-
}
|
|
3755
|
-
/**
|
|
3756
|
-
* Swap SOL for tokens privately
|
|
3757
|
-
*
|
|
3758
|
-
* Withdraws SOL from a note and swaps it for tokens via the relay service.
|
|
3759
|
-
* Handles the complete flow: deposits if needed, generates proof, and submits swap.
|
|
3760
|
-
*
|
|
3761
|
-
* @param connection - Solana connection
|
|
3762
|
-
* @param note - Note to spend
|
|
3763
|
-
* @param recipient - Recipient address (will receive tokens)
|
|
3764
|
-
* @param options - Swap configuration
|
|
3765
|
-
* @returns Swap result with transaction signature
|
|
3766
|
-
*
|
|
3767
|
-
* @example
|
|
3768
|
-
* ```typescript
|
|
3769
|
-
* const note = client.generateNote(1_000_000_000);
|
|
3770
|
-
* const result = await client.swap(
|
|
3771
|
-
* connection,
|
|
3772
|
-
* note,
|
|
3773
|
-
* new PublicKey("..."), // recipient
|
|
3774
|
-
* {
|
|
3775
|
-
* outputMint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC
|
|
3776
|
-
* slippageBps: 100, // 1%
|
|
3777
|
-
* getQuote: async (amount, mint, slippage) => {
|
|
3778
|
-
* // Fetch quote from your swap API
|
|
3779
|
-
* const quote = await fetchSwapQuote(amount, mint, slippage);
|
|
3780
|
-
* return {
|
|
3781
|
-
* outAmount: quote.outAmount,
|
|
3782
|
-
* minOutputAmount: quote.minOutputAmount
|
|
3783
|
-
* };
|
|
3784
|
-
* }
|
|
3785
|
-
* }
|
|
3786
|
-
* );
|
|
3787
|
-
* ```
|
|
3788
|
-
*/
|
|
3789
|
-
async swap(connection, note, recipient, options) {
|
|
3790
|
-
try {
|
|
3791
|
-
if (!isWithdrawable(note)) {
|
|
3792
|
-
const depositResult = await this.deposit(connection, note, {
|
|
3793
|
-
skipPreflight: false
|
|
3794
|
-
});
|
|
3795
|
-
note = depositResult.note;
|
|
3796
|
-
}
|
|
3797
|
-
const variableFee = Math.floor(note.amount * 3 / 1e3);
|
|
3798
|
-
const feeBps = note.amount === 0 ? 0 : Math.min(Math.floor((variableFee * 1e4 + note.amount - 1) / note.amount), 65535);
|
|
3799
|
-
const withdrawAmountLamports = getDistributableAmount(note.amount);
|
|
3800
|
-
if (withdrawAmountLamports <= 0) {
|
|
3801
|
-
throw new Error("Amount too small after fees");
|
|
3802
|
-
}
|
|
3803
|
-
let minOutputAmount;
|
|
3804
|
-
if (options.minOutputAmount !== void 0) {
|
|
3805
|
-
minOutputAmount = options.minOutputAmount;
|
|
3806
|
-
} else if (options.getQuote) {
|
|
3807
|
-
const quote = await options.getQuote(
|
|
3808
|
-
withdrawAmountLamports,
|
|
3809
|
-
options.outputMint,
|
|
3810
|
-
options.slippageBps || 100
|
|
3811
|
-
);
|
|
3812
|
-
minOutputAmount = quote.minOutputAmount;
|
|
3813
|
-
} else {
|
|
3814
|
-
throw new Error(
|
|
3815
|
-
"Must provide either minOutputAmount or getQuote function"
|
|
3816
|
-
);
|
|
3817
|
-
}
|
|
3818
|
-
let recipientAta;
|
|
3819
|
-
if (options.recipientAta) {
|
|
3820
|
-
recipientAta = new import_web35.PublicKey(options.recipientAta);
|
|
3821
|
-
} else {
|
|
3822
|
-
try {
|
|
3823
|
-
const splTokenModule = await import("@solana/spl-token");
|
|
3824
|
-
const getAssociatedTokenAddress = splTokenModule.getAssociatedTokenAddress;
|
|
3825
|
-
if (!getAssociatedTokenAddress) {
|
|
3826
|
-
throw new Error("getAssociatedTokenAddress not found");
|
|
3827
|
-
}
|
|
3828
|
-
const outputMint2 = new import_web35.PublicKey(options.outputMint);
|
|
3829
|
-
recipientAta = await getAssociatedTokenAddress(outputMint2, recipient);
|
|
3830
|
-
} catch (error) {
|
|
3831
|
-
throw new Error(
|
|
3832
|
-
`Failed to get associated token account: ${error instanceof Error ? error.message : String(error)}. Please install @solana/spl-token or provide recipientAta in options.`
|
|
3833
|
-
);
|
|
3834
|
-
}
|
|
3835
|
-
}
|
|
3836
|
-
if (!note.leafIndex && note.leafIndex !== 0) {
|
|
3837
|
-
throw new Error("Note must have a leaf index (note must be deposited)");
|
|
3838
|
-
}
|
|
3839
|
-
const nullifier = await computeNullifierAsync(note.sk_spend, note.leafIndex);
|
|
3840
|
-
const nullifierHex = nullifier.toString(16).padStart(64, "0");
|
|
3841
|
-
const inputMint = new import_web35.PublicKey("11111111111111111111111111111111");
|
|
3842
|
-
const outputMint = new import_web35.PublicKey(options.outputMint);
|
|
3843
|
-
const outputsHash = await computeSwapOutputsHashAsync(
|
|
3844
|
-
inputMint,
|
|
3845
|
-
outputMint,
|
|
3846
|
-
recipientAta,
|
|
3847
|
-
minOutputAmount,
|
|
3848
|
-
note.amount
|
|
3849
|
-
);
|
|
3850
|
-
const outputsHashHex = outputsHash.toString(16).padStart(64, "0");
|
|
3851
|
-
const circuitsPath2 = await getDefaultCircuitsPath();
|
|
3852
|
-
const useDirectProof = await areCircuitsAvailable(circuitsPath2);
|
|
3853
|
-
if (!useDirectProof) {
|
|
3854
|
-
throw new Error(
|
|
3855
|
-
`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.`
|
|
3856
|
-
);
|
|
3857
|
-
}
|
|
3858
|
-
const MAX_PROOF_RETRIES = 3;
|
|
3859
|
-
let lastError = null;
|
|
3860
|
-
for (let attempt = 0; attempt < MAX_PROOF_RETRIES; attempt++) {
|
|
3861
|
-
try {
|
|
3862
|
-
if (attempt > 0) {
|
|
3863
|
-
options?.onProgress?.(`proof_refresh_${attempt + 1}`);
|
|
3864
|
-
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
3865
|
-
}
|
|
3866
|
-
const programId = this.config.programId || CLOAK_PROGRAM_ID;
|
|
3867
|
-
const mintForSOL = NATIVE_SOL_MINT;
|
|
3868
|
-
const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId, mintForSOL);
|
|
3869
|
-
const chainProof = await computeProofFromChain(connection, merkleTreePDA, note.leafIndex);
|
|
3870
|
-
const merkleProof = {
|
|
3871
|
-
pathElements: chainProof.pathElements,
|
|
3872
|
-
pathIndices: chainProof.pathIndices
|
|
3873
|
-
};
|
|
3874
|
-
const merkleRoot = chainProof.root;
|
|
3875
|
-
if (!merkleRoot) {
|
|
3876
|
-
throw new Error("Failed to get Merkle root from chain");
|
|
3877
|
-
}
|
|
3878
|
-
if (!merkleProof.pathElements || merkleProof.pathElements.length === 0) {
|
|
3879
|
-
throw new Error("Merkle proof is invalid: missing path elements");
|
|
3880
|
-
}
|
|
3881
|
-
const sk_spend_bigint = BigInt("0x" + note.sk_spend);
|
|
3882
|
-
const r_bigint = BigInt("0x" + note.r);
|
|
3883
|
-
const root_bigint = BigInt("0x" + merkleRoot);
|
|
3884
|
-
const nullifier_bigint = BigInt("0x" + nullifierHex);
|
|
3885
|
-
const outputs_hash_bigint = BigInt("0x" + outputsHashHex);
|
|
3886
|
-
const amount_bigint = BigInt(note.amount);
|
|
3887
|
-
const pathElements = merkleProof.pathElements.map((p) => BigInt("0x" + p));
|
|
3888
|
-
const inputMintLimbs = pubkeyToLimbs(inputMint.toBytes());
|
|
3889
|
-
const outputMintLimbs = pubkeyToLimbs(outputMint.toBytes());
|
|
3890
|
-
const recipientAtaLimbs = pubkeyToLimbs(recipientAta.toBytes());
|
|
3891
|
-
if (attempt === 0) {
|
|
3892
|
-
const computedCommitment = await computeCommitment(amount_bigint, r_bigint, sk_spend_bigint);
|
|
3893
|
-
const noteCommitment = BigInt("0x" + note.commitment);
|
|
3894
|
-
if (computedCommitment !== noteCommitment) {
|
|
3895
|
-
throw new Error(
|
|
3896
|
-
`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.`
|
|
3897
|
-
);
|
|
3898
|
-
}
|
|
3899
|
-
}
|
|
3900
|
-
const t = amount_bigint * 3n;
|
|
3901
|
-
const varFee = t / 1000n;
|
|
3902
|
-
const rem = t % 1000n;
|
|
3903
|
-
const proofInputs = {
|
|
3904
|
-
sk_spend: sk_spend_bigint,
|
|
3905
|
-
r: r_bigint,
|
|
3906
|
-
amount: amount_bigint,
|
|
3907
|
-
leaf_index: BigInt(note.leafIndex),
|
|
3908
|
-
path_elements: pathElements,
|
|
3909
|
-
path_indices: merkleProof.pathIndices,
|
|
3910
|
-
root: root_bigint,
|
|
3911
|
-
nullifier: nullifier_bigint,
|
|
3912
|
-
outputs_hash: outputs_hash_bigint,
|
|
3913
|
-
public_amount: amount_bigint,
|
|
3914
|
-
input_mint: inputMintLimbs,
|
|
3915
|
-
output_mint: outputMintLimbs,
|
|
3916
|
-
recipient_ata: recipientAtaLimbs,
|
|
3917
|
-
min_output_amount: BigInt(minOutputAmount),
|
|
3918
|
-
var_fee: varFee,
|
|
3919
|
-
rem
|
|
3920
|
-
};
|
|
3921
|
-
options?.onProgress?.("proof_generating");
|
|
3922
|
-
const proofResult = await generateWithdrawSwapProof(proofInputs, circuitsPath2);
|
|
3923
|
-
options?.onProgress?.("proof_complete");
|
|
3924
|
-
const proofHex = Buffer.from(proofResult.proofBytes).toString("hex");
|
|
3925
|
-
const finalPublicInputs = {
|
|
3926
|
-
root: merkleRoot,
|
|
3927
|
-
nf: nullifierHex,
|
|
3928
|
-
outputs_hash: outputsHashHex,
|
|
3929
|
-
amount: note.amount
|
|
3930
|
-
};
|
|
3931
|
-
const metadataBundle = options?.metadataBundle;
|
|
3932
|
-
const signature = await this.relay.submitSwap(
|
|
3933
|
-
{
|
|
3934
|
-
proof: proofHex,
|
|
3935
|
-
publicInputs: finalPublicInputs,
|
|
3936
|
-
outputs: [
|
|
3937
|
-
{
|
|
3938
|
-
recipient: recipient.toBase58(),
|
|
3939
|
-
amount: withdrawAmountLamports
|
|
3940
|
-
}
|
|
3941
|
-
],
|
|
3942
|
-
feeBps,
|
|
3943
|
-
swap: {
|
|
3944
|
-
output_mint: options.outputMint,
|
|
3945
|
-
slippage_bps: options.slippageBps || 100,
|
|
3946
|
-
min_output_amount: minOutputAmount
|
|
3947
|
-
},
|
|
3948
|
-
metadataBundle
|
|
3949
|
-
},
|
|
3950
|
-
options.onProgress
|
|
3951
|
-
);
|
|
3952
|
-
return {
|
|
3953
|
-
signature,
|
|
3954
|
-
outputs: [
|
|
3955
|
-
{
|
|
3956
|
-
recipient: recipient.toBase58(),
|
|
3957
|
-
amount: withdrawAmountLamports
|
|
3958
|
-
}
|
|
3959
|
-
],
|
|
3960
|
-
nullifier: nullifierHex,
|
|
3961
|
-
root: merkleRoot,
|
|
3962
|
-
outputMint: options.outputMint,
|
|
3963
|
-
minOutputAmount
|
|
3964
|
-
};
|
|
3965
|
-
} catch (error) {
|
|
3966
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
3967
|
-
if (error instanceof RootNotFoundError) {
|
|
3968
|
-
console.warn(
|
|
3969
|
-
`[Cloak SDK] Merkle root expired (attempt ${attempt + 1}/${MAX_PROOF_RETRIES}). Regenerating swap proof with fresh root...`
|
|
3970
|
-
);
|
|
3971
|
-
continue;
|
|
3972
|
-
}
|
|
3973
|
-
throw error;
|
|
3974
|
-
}
|
|
3975
|
-
}
|
|
3976
|
-
throw new Error(
|
|
3977
|
-
`Swap failed after ${MAX_PROOF_RETRIES} proof refresh attempts. The Merkle tree is updating too rapidly. Last error: ${lastError?.message}`
|
|
3978
|
-
);
|
|
3979
|
-
} catch (error) {
|
|
3980
|
-
throw this.wrapError(error, "Swap failed");
|
|
3981
|
-
}
|
|
3982
|
-
}
|
|
3983
|
-
/**
|
|
3984
|
-
* Generate a new note without depositing
|
|
3985
|
-
*
|
|
3986
|
-
* @param amountLamports - Amount for the note
|
|
3987
|
-
* @param useWalletKeys - Whether to use wallet keys (v2.0 recommended)
|
|
3988
|
-
* @returns New note (not yet deposited)
|
|
3989
|
-
*/
|
|
3990
|
-
async generateNote(amountLamports, useWalletKeys = false) {
|
|
3991
|
-
if (useWalletKeys && this.cloakKeys) {
|
|
3992
|
-
return await generateNoteFromWallet(amountLamports, this.cloakKeys, this.config.network);
|
|
3993
|
-
} else if (useWalletKeys) {
|
|
3994
|
-
const keys = generateCloakKeys();
|
|
3995
|
-
this.cloakKeys = keys;
|
|
3996
|
-
const result = this.storage.saveKeys(keys);
|
|
3997
|
-
if (result instanceof Promise) {
|
|
3998
|
-
result.catch(() => {
|
|
3999
|
-
});
|
|
4000
|
-
}
|
|
4001
|
-
return await generateNoteFromWallet(amountLamports, keys, this.config.network);
|
|
4002
|
-
}
|
|
4003
|
-
return await generateNote(amountLamports, this.config.network);
|
|
4004
|
-
}
|
|
4005
|
-
/**
|
|
4006
|
-
* Parse a note from JSON string
|
|
4007
|
-
*
|
|
4008
|
-
* @param jsonString - JSON representation
|
|
4009
|
-
* @returns Parsed note
|
|
4010
|
-
*/
|
|
4011
|
-
parseNote(jsonString) {
|
|
4012
|
-
return parseNote2(jsonString);
|
|
4013
|
-
}
|
|
4014
|
-
/**
|
|
4015
|
-
* Export a note to JSON string
|
|
4016
|
-
*
|
|
4017
|
-
* @param note - Note to export
|
|
4018
|
-
* @param pretty - Format with indentation
|
|
4019
|
-
* @returns JSON string
|
|
4020
|
-
*/
|
|
4021
|
-
exportNote(note, pretty = false) {
|
|
4022
|
-
return pretty ? JSON.stringify(note, null, 2) : JSON.stringify(note);
|
|
4023
|
-
}
|
|
4024
|
-
/**
|
|
4025
|
-
* Check if a note is ready for withdrawal
|
|
4026
|
-
*
|
|
4027
|
-
* @param note - Note to check
|
|
4028
|
-
* @returns True if withdrawable
|
|
4029
|
-
*/
|
|
4030
|
-
isWithdrawable(note) {
|
|
4031
|
-
return isWithdrawable(note);
|
|
4032
|
-
}
|
|
4033
|
-
/**
|
|
4034
|
-
* Get Merkle proof for a leaf index directly from on-chain state
|
|
4035
|
-
*
|
|
4036
|
-
* @param connection - Solana connection
|
|
4037
|
-
* @param leafIndex - Leaf index in tree
|
|
4038
|
-
* @returns Merkle proof computed from on-chain data
|
|
2466
|
+
* Compute a Merkle proof for `leafIndex` from on-chain state. No relay /
|
|
2467
|
+
* indexer round-trip required.
|
|
4039
2468
|
*/
|
|
4040
2469
|
async getMerkleProof(connection, leafIndex) {
|
|
4041
2470
|
const programId = this.config.programId || CLOAK_PROGRAM_ID;
|
|
4042
|
-
const
|
|
4043
|
-
const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId, mintForSOL);
|
|
2471
|
+
const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId, NATIVE_SOL_MINT);
|
|
4044
2472
|
const chainProof = await computeProofFromChain(connection, merkleTreePDA, leafIndex);
|
|
4045
2473
|
return {
|
|
4046
2474
|
pathElements: chainProof.pathElements,
|
|
@@ -4048,72 +2476,20 @@ var CloakSDK = class {
|
|
|
4048
2476
|
root: chainProof.root
|
|
4049
2477
|
};
|
|
4050
2478
|
}
|
|
4051
|
-
/**
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
async getCurrentRoot(connection) {
|
|
4058
|
-
const programId = this.config.programId || CLOAK_PROGRAM_ID;
|
|
4059
|
-
const mintForSOL = NATIVE_SOL_MINT;
|
|
4060
|
-
const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId, mintForSOL);
|
|
4061
|
-
const state = await readMerkleTreeState(connection, merkleTreePDA);
|
|
4062
|
-
return state.root;
|
|
4063
|
-
}
|
|
4064
|
-
/**
|
|
4065
|
-
* Get transaction status from relay service
|
|
4066
|
-
*
|
|
4067
|
-
* @param requestId - Request ID from previous submission
|
|
4068
|
-
* @returns Current status
|
|
4069
|
-
*/
|
|
4070
|
-
async getTransactionStatus(requestId) {
|
|
4071
|
-
return this.relay.getStatus(requestId);
|
|
4072
|
-
}
|
|
4073
|
-
/**
|
|
4074
|
-
* Fetch and decrypt this user's transaction metadata history.
|
|
4075
|
-
* DEPRECATED: This functionality is no longer supported
|
|
4076
|
-
*/
|
|
4077
|
-
async getTransactionMetadata(_options) {
|
|
4078
|
-
throw new Error("getTransactionMetadata is deprecated - use compliance export API instead");
|
|
4079
|
-
}
|
|
4080
|
-
/**
|
|
4081
|
-
* Load all notes from storage
|
|
4082
|
-
*
|
|
4083
|
-
* @returns Array of saved notes
|
|
4084
|
-
*/
|
|
4085
|
-
async loadNotes() {
|
|
4086
|
-
const notes = this.storage.loadAllNotes();
|
|
4087
|
-
return Array.isArray(notes) ? notes : await notes;
|
|
4088
|
-
}
|
|
4089
|
-
/**
|
|
4090
|
-
* Save a note to storage
|
|
4091
|
-
*
|
|
4092
|
-
* @param note - Note to save
|
|
4093
|
-
*/
|
|
4094
|
-
async saveNote(note) {
|
|
4095
|
-
const result = this.storage.saveNote(note);
|
|
4096
|
-
if (result instanceof Promise) {
|
|
4097
|
-
await result;
|
|
4098
|
-
}
|
|
2479
|
+
/** Read the current SOL-pool Merkle root from on-chain state. */
|
|
2480
|
+
async getCurrentRoot(connection) {
|
|
2481
|
+
const programId = this.config.programId || CLOAK_PROGRAM_ID;
|
|
2482
|
+
const { merkleTree: merkleTreePDA } = getShieldPoolPDAs(programId, NATIVE_SOL_MINT);
|
|
2483
|
+
const state = await readMerkleTreeState(connection, merkleTreePDA);
|
|
2484
|
+
return state.root;
|
|
4099
2485
|
}
|
|
4100
|
-
/**
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
* @param commitment - Commitment hash
|
|
4104
|
-
* @returns Note if found
|
|
4105
|
-
*/
|
|
4106
|
-
async findNote(commitment) {
|
|
4107
|
-
const notes = await this.loadNotes();
|
|
4108
|
-
return findNoteByCommitment(notes, commitment);
|
|
2486
|
+
/** Poll relay for the status of a previously-submitted request. */
|
|
2487
|
+
async getTransactionStatus(requestId) {
|
|
2488
|
+
return this.relay.getStatus(requestId);
|
|
4109
2489
|
}
|
|
4110
|
-
/**
|
|
4111
|
-
* Import wallet keys from JSON
|
|
4112
|
-
*
|
|
4113
|
-
* @param keysJson - JSON string containing keys
|
|
4114
|
-
*/
|
|
2490
|
+
/** Import wallet keys from JSON; persists to the configured storage adapter. */
|
|
4115
2491
|
async importWalletKeys(keysJson) {
|
|
4116
|
-
const keys =
|
|
2492
|
+
const keys = importKeys(keysJson);
|
|
4117
2493
|
this.cloakKeys = keys;
|
|
4118
2494
|
const result = this.storage.saveKeys(keys);
|
|
4119
2495
|
if (result instanceof Promise) {
|
|
@@ -4121,134 +2497,32 @@ var CloakSDK = class {
|
|
|
4121
2497
|
}
|
|
4122
2498
|
}
|
|
4123
2499
|
/**
|
|
4124
|
-
* Export wallet keys to JSON
|
|
4125
|
-
*
|
|
4126
|
-
* WARNING:
|
|
4127
|
-
*
|
|
4128
|
-
* @returns JSON string with keys
|
|
2500
|
+
* Export wallet keys to JSON.
|
|
2501
|
+
*
|
|
2502
|
+
* WARNING: this exports secret keys. Store securely.
|
|
4129
2503
|
*/
|
|
4130
2504
|
exportWalletKeys() {
|
|
4131
2505
|
if (!this.cloakKeys) {
|
|
4132
2506
|
throw new CloakError("No wallet keys available", "wallet", false);
|
|
4133
2507
|
}
|
|
4134
|
-
return
|
|
2508
|
+
return exportKeys(this.cloakKeys);
|
|
4135
2509
|
}
|
|
4136
2510
|
/**
|
|
4137
|
-
*
|
|
2511
|
+
* Snapshot of the active SDK configuration.
|
|
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.
|
|
4138
2520
|
*/
|
|
4139
2521
|
getConfig() {
|
|
4140
2522
|
return { ...this.config };
|
|
4141
2523
|
}
|
|
4142
|
-
// Note: scanNotes was removed - it required the indexer to store encrypted outputs.
|
|
4143
|
-
// Users should maintain their own note storage. This is more privacy-preserving.
|
|
4144
|
-
/**
|
|
4145
|
-
* Wrap errors with better categorization and user-friendly messages
|
|
4146
|
-
*
|
|
4147
|
-
* @private
|
|
4148
|
-
*/
|
|
4149
|
-
wrapError(error, context) {
|
|
4150
|
-
if (error instanceof CloakError) {
|
|
4151
|
-
return error;
|
|
4152
|
-
}
|
|
4153
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4154
|
-
if (errorMessage.includes("duplicate key") || errorMessage.includes("already deposited")) {
|
|
4155
|
-
return new CloakError(
|
|
4156
|
-
"This note was already deposited. Generate a new note to continue.",
|
|
4157
|
-
"validation",
|
|
4158
|
-
false,
|
|
4159
|
-
error instanceof Error ? error : void 0
|
|
4160
|
-
);
|
|
4161
|
-
}
|
|
4162
|
-
if (errorMessage.includes("insufficient funds") || errorMessage.includes("insufficient lamports")) {
|
|
4163
|
-
return new CloakError(
|
|
4164
|
-
"Insufficient funds for this transaction. Please check your wallet balance.",
|
|
4165
|
-
"wallet",
|
|
4166
|
-
false,
|
|
4167
|
-
error instanceof Error ? error : void 0
|
|
4168
|
-
);
|
|
4169
|
-
}
|
|
4170
|
-
if (errorMessage.includes("Merkle tree") || errorMessage.includes("merkle")) {
|
|
4171
|
-
return new CloakError(
|
|
4172
|
-
"Failed to read on-chain Merkle tree state. Please try again.",
|
|
4173
|
-
"network",
|
|
4174
|
-
true,
|
|
4175
|
-
error instanceof Error ? error : void 0
|
|
4176
|
-
);
|
|
4177
|
-
}
|
|
4178
|
-
if (errorMessage.includes("timeout") || errorMessage.includes("timed out")) {
|
|
4179
|
-
return new CloakError(
|
|
4180
|
-
"Network timeout. Please check your connection and try again.",
|
|
4181
|
-
"network",
|
|
4182
|
-
true,
|
|
4183
|
-
error instanceof Error ? error : void 0
|
|
4184
|
-
);
|
|
4185
|
-
}
|
|
4186
|
-
if (errorMessage.includes("not connected") || errorMessage.includes("wallet")) {
|
|
4187
|
-
return new CloakError(
|
|
4188
|
-
"Wallet not connected. Please connect your wallet first.",
|
|
4189
|
-
"wallet",
|
|
4190
|
-
false,
|
|
4191
|
-
error instanceof Error ? error : void 0
|
|
4192
|
-
);
|
|
4193
|
-
}
|
|
4194
|
-
if (errorMessage.includes("proof") && (errorMessage.includes("failed") || errorMessage.includes("error"))) {
|
|
4195
|
-
return new CloakError(
|
|
4196
|
-
"Zero-knowledge proof generation failed. This is usually temporary - please try again.",
|
|
4197
|
-
"prover",
|
|
4198
|
-
true,
|
|
4199
|
-
error instanceof Error ? error : void 0
|
|
4200
|
-
);
|
|
4201
|
-
}
|
|
4202
|
-
if (errorMessage.includes("relay") || errorMessage.includes("withdraw")) {
|
|
4203
|
-
return new CloakError(
|
|
4204
|
-
"Relay service error. Please try again later.",
|
|
4205
|
-
"relay",
|
|
4206
|
-
true,
|
|
4207
|
-
error instanceof Error ? error : void 0
|
|
4208
|
-
);
|
|
4209
|
-
}
|
|
4210
|
-
return new CloakError(
|
|
4211
|
-
`${context}: ${errorMessage}`,
|
|
4212
|
-
"network",
|
|
4213
|
-
false,
|
|
4214
|
-
error instanceof Error ? error : void 0
|
|
4215
|
-
);
|
|
4216
|
-
}
|
|
4217
2524
|
};
|
|
4218
2525
|
|
|
4219
|
-
// src/core/note.ts
|
|
4220
|
-
function serializeNote(note, pretty = false) {
|
|
4221
|
-
validateNote(note);
|
|
4222
|
-
return pretty ? JSON.stringify(note, null, 2) : JSON.stringify(note);
|
|
4223
|
-
}
|
|
4224
|
-
function downloadNote(note, filename) {
|
|
4225
|
-
const g = globalThis;
|
|
4226
|
-
const doc = g?.document;
|
|
4227
|
-
const URL_ = g?.URL;
|
|
4228
|
-
const Blob_ = g?.Blob;
|
|
4229
|
-
if (!doc || !URL_ || !Blob_) {
|
|
4230
|
-
throw new Error("downloadNote is only available in browser environments");
|
|
4231
|
-
}
|
|
4232
|
-
const json = serializeNote(note, true);
|
|
4233
|
-
const blob = new Blob_([json], { type: "application/json" });
|
|
4234
|
-
const url = URL_.createObjectURL(blob);
|
|
4235
|
-
const defaultFilename = `cloak-note-${note.commitment.slice(0, 8)}.json`;
|
|
4236
|
-
const link = doc.createElement("a");
|
|
4237
|
-
link.href = url;
|
|
4238
|
-
link.download = filename || defaultFilename;
|
|
4239
|
-
link.click();
|
|
4240
|
-
URL_.revokeObjectURL(url);
|
|
4241
|
-
}
|
|
4242
|
-
async function copyNoteToClipboard(note) {
|
|
4243
|
-
const g = globalThis;
|
|
4244
|
-
const nav = g?.navigator;
|
|
4245
|
-
if (!nav || !nav.clipboard) {
|
|
4246
|
-
throw new Error("Clipboard API not available");
|
|
4247
|
-
}
|
|
4248
|
-
const json = serializeNote(note, true);
|
|
4249
|
-
await nav.clipboard.writeText(json);
|
|
4250
|
-
}
|
|
4251
|
-
|
|
4252
2526
|
// src/core/compliance-keys.ts
|
|
4253
2527
|
var import_tweetnacl2 = __toESM(require("tweetnacl"), 1);
|
|
4254
2528
|
var import_blake33 = require("@noble/hashes/blake3");
|
|
@@ -4635,6 +2909,165 @@ async function decryptComplianceMetadataWithMasterKey(encrypted, masterComplianc
|
|
|
4635
2909
|
return decryptWithSharedSecret(payload, sharedSecret);
|
|
4636
2910
|
}
|
|
4637
2911
|
|
|
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
|
+
// src/utils/verify-utxos.ts
|
|
3014
|
+
init_utxo();
|
|
3015
|
+
async function verifyUtxos(utxos, connection, programId, commitment = "confirmed") {
|
|
3016
|
+
const checkable = [];
|
|
3017
|
+
const skipped = [];
|
|
3018
|
+
for (const utxo of utxos) {
|
|
3019
|
+
if (utxo.commitment === void 0 || utxo.mintAddress === void 0 || utxo.keypair === void 0 || utxo.keypair.privateKey === void 0) {
|
|
3020
|
+
skipped.push(utxo);
|
|
3021
|
+
continue;
|
|
3022
|
+
}
|
|
3023
|
+
try {
|
|
3024
|
+
const nullifierBig = await computeNullifier(utxo);
|
|
3025
|
+
const nullifierHex = nullifierBig.toString(16).padStart(64, "0");
|
|
3026
|
+
const nullifierBytes = Buffer.from(nullifierHex, "hex");
|
|
3027
|
+
const { pool } = getShieldPoolPDAs(programId, utxo.mintAddress);
|
|
3028
|
+
const [pda] = getNullifierPDA(pool, nullifierBytes, programId);
|
|
3029
|
+
checkable.push({ utxo, pda });
|
|
3030
|
+
} catch {
|
|
3031
|
+
skipped.push(utxo);
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
if (checkable.length === 0) {
|
|
3035
|
+
return { spent: [], unspent: [], skipped };
|
|
3036
|
+
}
|
|
3037
|
+
const CHUNK = 100;
|
|
3038
|
+
const infos = [];
|
|
3039
|
+
for (let i = 0; i < checkable.length; i += CHUNK) {
|
|
3040
|
+
const page = checkable.slice(i, i + CHUNK).map((e) => e.pda);
|
|
3041
|
+
const pageInfos = await connection.getMultipleAccountsInfo(page, commitment);
|
|
3042
|
+
infos.push(...pageInfos);
|
|
3043
|
+
}
|
|
3044
|
+
const spent = [];
|
|
3045
|
+
const unspent = [];
|
|
3046
|
+
for (let i = 0; i < checkable.length; i++) {
|
|
3047
|
+
if (infos[i] !== null) {
|
|
3048
|
+
spent.push(checkable[i].utxo);
|
|
3049
|
+
} else {
|
|
3050
|
+
unspent.push(checkable[i].utxo);
|
|
3051
|
+
}
|
|
3052
|
+
}
|
|
3053
|
+
return { spent, unspent, skipped };
|
|
3054
|
+
}
|
|
3055
|
+
async function preflightNullifiers(utxos, connection, programId, commitment = "confirmed") {
|
|
3056
|
+
if (utxos.length === 0) return;
|
|
3057
|
+
const result = await verifyUtxos(utxos, connection, programId, commitment);
|
|
3058
|
+
if (result.spent.length > 0) {
|
|
3059
|
+
const allCommitments = result.spent.map(
|
|
3060
|
+
(u) => u.commitment !== void 0 ? u.commitment.toString(16).padStart(64, "0") : "<unknown>"
|
|
3061
|
+
);
|
|
3062
|
+
const preview = allCommitments.slice(0, 5);
|
|
3063
|
+
const extra = allCommitments.length > preview.length ? ` (+${allCommitments.length - preview.length} more)` : "";
|
|
3064
|
+
throw new UtxoAlreadySpentError(
|
|
3065
|
+
`Pre-flight detected ${allCommitments.length} already-spent UTXO(s). First spent commitment(s): ${preview.join(", ")}${extra}. Call verifyUtxos() and remove these from local state before retrying.`,
|
|
3066
|
+
allCommitments
|
|
3067
|
+
);
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
|
|
4638
3071
|
// src/services/risk-oracle.ts
|
|
4639
3072
|
function buildRangeRiskScoreJob(OracleJob, walletAddress) {
|
|
4640
3073
|
return OracleJob.fromObject({
|
|
@@ -4706,49 +3139,10 @@ async function fetchRiskQuoteIx(connection, wallet, rangeApiKey) {
|
|
|
4706
3139
|
};
|
|
4707
3140
|
}
|
|
4708
3141
|
|
|
4709
|
-
// src/helpers/encrypted-output.ts
|
|
4710
|
-
function prepareEncryptedOutput(note, cloakKeys) {
|
|
4711
|
-
const noteData = {
|
|
4712
|
-
amount: note.amount,
|
|
4713
|
-
r: note.r,
|
|
4714
|
-
sk_spend: note.sk_spend,
|
|
4715
|
-
commitment: note.commitment
|
|
4716
|
-
};
|
|
4717
|
-
const encrypted = encryptNoteForRecipient(noteData, cloakKeys.view.pvk);
|
|
4718
|
-
return btoa(JSON.stringify(encrypted));
|
|
4719
|
-
}
|
|
4720
|
-
function prepareEncryptedOutputForRecipient(note, recipientPvkHex) {
|
|
4721
|
-
const noteData = {
|
|
4722
|
-
amount: note.amount,
|
|
4723
|
-
r: note.r,
|
|
4724
|
-
sk_spend: note.sk_spend,
|
|
4725
|
-
commitment: note.commitment
|
|
4726
|
-
};
|
|
4727
|
-
const recipientPvk = hexToBytes(recipientPvkHex);
|
|
4728
|
-
const encrypted = encryptNoteForRecipient(noteData, recipientPvk);
|
|
4729
|
-
return btoa(JSON.stringify(encrypted));
|
|
4730
|
-
}
|
|
4731
|
-
function encodeNoteSimple(note) {
|
|
4732
|
-
const data = {
|
|
4733
|
-
amount: note.amount,
|
|
4734
|
-
r: note.r,
|
|
4735
|
-
sk_spend: note.sk_spend,
|
|
4736
|
-
commitment: note.commitment
|
|
4737
|
-
};
|
|
4738
|
-
const json = JSON.stringify(data);
|
|
4739
|
-
if (typeof Buffer !== "undefined") {
|
|
4740
|
-
return Buffer.from(json).toString("base64");
|
|
4741
|
-
} else if (typeof btoa !== "undefined") {
|
|
4742
|
-
return btoa(json);
|
|
4743
|
-
} else {
|
|
4744
|
-
throw new Error("No base64 encoding method available");
|
|
4745
|
-
}
|
|
4746
|
-
}
|
|
4747
|
-
|
|
4748
3142
|
// src/helpers/wallet-integration.ts
|
|
4749
|
-
var
|
|
3143
|
+
var import_web35 = require("@solana/web3.js");
|
|
4750
3144
|
function validateWalletConnected(wallet) {
|
|
4751
|
-
if (wallet instanceof
|
|
3145
|
+
if (wallet instanceof import_web35.Keypair) {
|
|
4752
3146
|
return;
|
|
4753
3147
|
}
|
|
4754
3148
|
if (!wallet.publicKey) {
|
|
@@ -4760,7 +3154,7 @@ function validateWalletConnected(wallet) {
|
|
|
4760
3154
|
}
|
|
4761
3155
|
}
|
|
4762
3156
|
function getPublicKey(wallet) {
|
|
4763
|
-
if (wallet instanceof
|
|
3157
|
+
if (wallet instanceof import_web35.Keypair) {
|
|
4764
3158
|
return wallet.publicKey;
|
|
4765
3159
|
}
|
|
4766
3160
|
if (!wallet.publicKey) {
|
|
@@ -4773,7 +3167,7 @@ function getPublicKey(wallet) {
|
|
|
4773
3167
|
return wallet.publicKey;
|
|
4774
3168
|
}
|
|
4775
3169
|
async function sendTransaction(transaction, wallet, connection, options) {
|
|
4776
|
-
if (wallet instanceof
|
|
3170
|
+
if (wallet instanceof import_web35.Keypair) {
|
|
4777
3171
|
return await connection.sendTransaction(transaction, [wallet], options);
|
|
4778
3172
|
}
|
|
4779
3173
|
if (wallet.sendTransaction) {
|
|
@@ -4790,7 +3184,7 @@ async function sendTransaction(transaction, wallet, connection, options) {
|
|
|
4790
3184
|
}
|
|
4791
3185
|
}
|
|
4792
3186
|
async function signTransaction(transaction, wallet) {
|
|
4793
|
-
if (wallet instanceof
|
|
3187
|
+
if (wallet instanceof import_web35.Keypair) {
|
|
4794
3188
|
transaction.sign(wallet);
|
|
4795
3189
|
return transaction;
|
|
4796
3190
|
}
|
|
@@ -4981,10 +3375,15 @@ var import_bs58 = __toESM(require("bs58"), 1);
|
|
|
4981
3375
|
var bs58 = import_bs58.default.default || import_bs58.default;
|
|
4982
3376
|
var TRANSACT_TAG = 0;
|
|
4983
3377
|
var TRANSACT_SWAP_TAG = 1;
|
|
4984
|
-
var DEPOSIT_TAG = 1;
|
|
4985
3378
|
var PROOF_LEN = 256;
|
|
4986
3379
|
var PUBLIC_INPUTS_LEN = 264;
|
|
4987
3380
|
var COMMITMENTS_OFFSET = 168;
|
|
3381
|
+
function isAllZero(b) {
|
|
3382
|
+
for (let i = 0; i < b.length; i++) {
|
|
3383
|
+
if (b[i] !== 0) return false;
|
|
3384
|
+
}
|
|
3385
|
+
return true;
|
|
3386
|
+
}
|
|
4988
3387
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
4989
3388
|
var DEFAULT_MAX_RETRIES = 3;
|
|
4990
3389
|
async function fetchWithRetry(url, options = {}) {
|
|
@@ -5122,24 +3521,20 @@ async function buildMerkleTreeFromRelay(relayUrl, options) {
|
|
|
5122
3521
|
function extractCommitmentsFromInstruction(data) {
|
|
5123
3522
|
if (data.length === 0) return [];
|
|
5124
3523
|
const tag = data[0];
|
|
5125
|
-
if (tag
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
if (
|
|
5138
|
-
|
|
5139
|
-
const commitment = data.slice(1 + 8, 1 + 8 + 32);
|
|
5140
|
-
return [commitment];
|
|
5141
|
-
}
|
|
5142
|
-
return [];
|
|
3524
|
+
if (tag !== TRANSACT_TAG && tag !== TRANSACT_SWAP_TAG) return [];
|
|
3525
|
+
const publicInputsStart = 1 + PROOF_LEN;
|
|
3526
|
+
const publicInputsEnd = publicInputsStart + PUBLIC_INPUTS_LEN;
|
|
3527
|
+
if (data.length < publicInputsEnd) return [];
|
|
3528
|
+
const publicInputs = data.slice(publicInputsStart, publicInputsEnd);
|
|
3529
|
+
const c0 = publicInputs.slice(COMMITMENTS_OFFSET, COMMITMENTS_OFFSET + 32);
|
|
3530
|
+
const c1 = publicInputs.slice(
|
|
3531
|
+
COMMITMENTS_OFFSET + 32,
|
|
3532
|
+
COMMITMENTS_OFFSET + 64
|
|
3533
|
+
);
|
|
3534
|
+
const out = [];
|
|
3535
|
+
if (!isAllZero(c0)) out.push(c0);
|
|
3536
|
+
if (!isAllZero(c1)) out.push(c1);
|
|
3537
|
+
return out;
|
|
5143
3538
|
}
|
|
5144
3539
|
function isBrowser() {
|
|
5145
3540
|
return typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
|
|
@@ -5253,180 +3648,13 @@ async function preflightCheck(relayUrl, rootHex) {
|
|
|
5253
3648
|
};
|
|
5254
3649
|
}
|
|
5255
3650
|
|
|
5256
|
-
// src/utils/pending-operations.ts
|
|
5257
|
-
var PENDING_DEPOSITS_KEY = "cloak_pending_deposits";
|
|
5258
|
-
var PENDING_WITHDRAWALS_KEY = "cloak_pending_withdrawals";
|
|
5259
|
-
function getStorage() {
|
|
5260
|
-
if (typeof globalThis !== "undefined" && globalThis.localStorage) {
|
|
5261
|
-
return globalThis.localStorage;
|
|
5262
|
-
}
|
|
5263
|
-
return null;
|
|
5264
|
-
}
|
|
5265
|
-
function savePendingDeposit(deposit) {
|
|
5266
|
-
const storage = getStorage();
|
|
5267
|
-
if (!storage) {
|
|
5268
|
-
console.warn("localStorage not available - pending deposit not persisted");
|
|
5269
|
-
return;
|
|
5270
|
-
}
|
|
5271
|
-
const deposits = loadPendingDeposits();
|
|
5272
|
-
const index = deposits.findIndex((d) => d.note.commitment === deposit.note.commitment);
|
|
5273
|
-
if (index >= 0) {
|
|
5274
|
-
deposits[index] = deposit;
|
|
5275
|
-
} else {
|
|
5276
|
-
deposits.push(deposit);
|
|
5277
|
-
}
|
|
5278
|
-
storage.setItem(PENDING_DEPOSITS_KEY, JSON.stringify(deposits));
|
|
5279
|
-
}
|
|
5280
|
-
function loadPendingDeposits() {
|
|
5281
|
-
const storage = getStorage();
|
|
5282
|
-
if (!storage) return [];
|
|
5283
|
-
const stored = storage.getItem(PENDING_DEPOSITS_KEY);
|
|
5284
|
-
if (!stored) return [];
|
|
5285
|
-
try {
|
|
5286
|
-
return JSON.parse(stored);
|
|
5287
|
-
} catch {
|
|
5288
|
-
return [];
|
|
5289
|
-
}
|
|
5290
|
-
}
|
|
5291
|
-
function updatePendingDeposit(commitment, updates) {
|
|
5292
|
-
const storage = getStorage();
|
|
5293
|
-
if (!storage) return;
|
|
5294
|
-
const deposits = loadPendingDeposits();
|
|
5295
|
-
const index = deposits.findIndex((d) => d.note.commitment === commitment);
|
|
5296
|
-
if (index >= 0) {
|
|
5297
|
-
deposits[index] = { ...deposits[index], ...updates };
|
|
5298
|
-
storage.setItem(PENDING_DEPOSITS_KEY, JSON.stringify(deposits));
|
|
5299
|
-
}
|
|
5300
|
-
}
|
|
5301
|
-
function removePendingDeposit(commitment) {
|
|
5302
|
-
const storage = getStorage();
|
|
5303
|
-
if (!storage) return;
|
|
5304
|
-
const deposits = loadPendingDeposits();
|
|
5305
|
-
const filtered = deposits.filter((d) => d.note.commitment !== commitment);
|
|
5306
|
-
storage.setItem(PENDING_DEPOSITS_KEY, JSON.stringify(filtered));
|
|
5307
|
-
}
|
|
5308
|
-
function clearPendingDeposits() {
|
|
5309
|
-
const storage = getStorage();
|
|
5310
|
-
if (storage) {
|
|
5311
|
-
storage.removeItem(PENDING_DEPOSITS_KEY);
|
|
5312
|
-
}
|
|
5313
|
-
}
|
|
5314
|
-
function savePendingWithdrawal(withdrawal) {
|
|
5315
|
-
const storage = getStorage();
|
|
5316
|
-
if (!storage) {
|
|
5317
|
-
console.warn("localStorage not available - pending withdrawal not persisted");
|
|
5318
|
-
return;
|
|
5319
|
-
}
|
|
5320
|
-
const withdrawals = loadPendingWithdrawals();
|
|
5321
|
-
const index = withdrawals.findIndex((w) => w.requestId === withdrawal.requestId);
|
|
5322
|
-
if (index >= 0) {
|
|
5323
|
-
withdrawals[index] = withdrawal;
|
|
5324
|
-
} else {
|
|
5325
|
-
withdrawals.push(withdrawal);
|
|
5326
|
-
}
|
|
5327
|
-
storage.setItem(PENDING_WITHDRAWALS_KEY, JSON.stringify(withdrawals));
|
|
5328
|
-
}
|
|
5329
|
-
function loadPendingWithdrawals() {
|
|
5330
|
-
const storage = getStorage();
|
|
5331
|
-
if (!storage) return [];
|
|
5332
|
-
const stored = storage.getItem(PENDING_WITHDRAWALS_KEY);
|
|
5333
|
-
if (!stored) return [];
|
|
5334
|
-
try {
|
|
5335
|
-
return JSON.parse(stored);
|
|
5336
|
-
} catch {
|
|
5337
|
-
return [];
|
|
5338
|
-
}
|
|
5339
|
-
}
|
|
5340
|
-
function updatePendingWithdrawal(requestId, updates) {
|
|
5341
|
-
const storage = getStorage();
|
|
5342
|
-
if (!storage) return;
|
|
5343
|
-
const withdrawals = loadPendingWithdrawals();
|
|
5344
|
-
const index = withdrawals.findIndex((w) => w.requestId === requestId);
|
|
5345
|
-
if (index >= 0) {
|
|
5346
|
-
withdrawals[index] = { ...withdrawals[index], ...updates };
|
|
5347
|
-
storage.setItem(PENDING_WITHDRAWALS_KEY, JSON.stringify(withdrawals));
|
|
5348
|
-
}
|
|
5349
|
-
}
|
|
5350
|
-
function removePendingWithdrawal(requestId) {
|
|
5351
|
-
const storage = getStorage();
|
|
5352
|
-
if (!storage) return;
|
|
5353
|
-
const withdrawals = loadPendingWithdrawals();
|
|
5354
|
-
const filtered = withdrawals.filter((w) => w.requestId !== requestId);
|
|
5355
|
-
storage.setItem(PENDING_WITHDRAWALS_KEY, JSON.stringify(filtered));
|
|
5356
|
-
}
|
|
5357
|
-
function clearPendingWithdrawals() {
|
|
5358
|
-
const storage = getStorage();
|
|
5359
|
-
if (storage) {
|
|
5360
|
-
storage.removeItem(PENDING_WITHDRAWALS_KEY);
|
|
5361
|
-
}
|
|
5362
|
-
}
|
|
5363
|
-
function hasPendingOperations() {
|
|
5364
|
-
const deposits = loadPendingDeposits();
|
|
5365
|
-
const withdrawals = loadPendingWithdrawals();
|
|
5366
|
-
const pendingDeposits = deposits.filter(
|
|
5367
|
-
(d) => d.status === "pending" || d.status === "tx_sent"
|
|
5368
|
-
);
|
|
5369
|
-
const pendingWithdrawals = withdrawals.filter(
|
|
5370
|
-
(w) => w.status === "pending" || w.status === "processing"
|
|
5371
|
-
);
|
|
5372
|
-
return pendingDeposits.length > 0 || pendingWithdrawals.length > 0;
|
|
5373
|
-
}
|
|
5374
|
-
function getPendingOperationsSummary() {
|
|
5375
|
-
const deposits = loadPendingDeposits().filter(
|
|
5376
|
-
(d) => d.status === "pending" || d.status === "tx_sent"
|
|
5377
|
-
);
|
|
5378
|
-
const withdrawals = loadPendingWithdrawals().filter(
|
|
5379
|
-
(w) => w.status === "pending" || w.status === "processing"
|
|
5380
|
-
);
|
|
5381
|
-
return {
|
|
5382
|
-
deposits,
|
|
5383
|
-
withdrawals,
|
|
5384
|
-
totalPending: deposits.length + withdrawals.length
|
|
5385
|
-
};
|
|
5386
|
-
}
|
|
5387
|
-
function cleanupStalePendingOperations(maxAgeMs = 24 * 60 * 60 * 1e3) {
|
|
5388
|
-
const now = Date.now();
|
|
5389
|
-
let removedDeposits = 0;
|
|
5390
|
-
let removedWithdrawals = 0;
|
|
5391
|
-
const deposits = loadPendingDeposits();
|
|
5392
|
-
const activeDeposits = deposits.filter((d) => {
|
|
5393
|
-
const age = now - d.startedAt;
|
|
5394
|
-
const isStale = age > maxAgeMs;
|
|
5395
|
-
const isTerminal = d.status === "confirmed" || d.status === "failed";
|
|
5396
|
-
if (isStale || isTerminal) {
|
|
5397
|
-
removedDeposits++;
|
|
5398
|
-
return false;
|
|
5399
|
-
}
|
|
5400
|
-
return true;
|
|
5401
|
-
});
|
|
5402
|
-
const storage = getStorage();
|
|
5403
|
-
if (storage) {
|
|
5404
|
-
storage.setItem(PENDING_DEPOSITS_KEY, JSON.stringify(activeDeposits));
|
|
5405
|
-
}
|
|
5406
|
-
const withdrawals = loadPendingWithdrawals();
|
|
5407
|
-
const activeWithdrawals = withdrawals.filter((w) => {
|
|
5408
|
-
const age = now - w.startedAt;
|
|
5409
|
-
const isStale = age > maxAgeMs;
|
|
5410
|
-
const isTerminal = w.status === "completed" || w.status === "failed";
|
|
5411
|
-
if (isStale || isTerminal) {
|
|
5412
|
-
removedWithdrawals++;
|
|
5413
|
-
return false;
|
|
5414
|
-
}
|
|
5415
|
-
return true;
|
|
5416
|
-
});
|
|
5417
|
-
if (storage) {
|
|
5418
|
-
storage.setItem(PENDING_WITHDRAWALS_KEY, JSON.stringify(activeWithdrawals));
|
|
5419
|
-
}
|
|
5420
|
-
return { removedDeposits, removedWithdrawals };
|
|
5421
|
-
}
|
|
5422
|
-
|
|
5423
3651
|
// src/index.ts
|
|
5424
3652
|
init_utxo();
|
|
5425
3653
|
|
|
5426
3654
|
// src/core/transact.ts
|
|
5427
|
-
var
|
|
3655
|
+
var import_web36 = require("@solana/web3.js");
|
|
5428
3656
|
var import_spl_token = require("@solana/spl-token");
|
|
5429
|
-
var
|
|
3657
|
+
var snarkjs = __toESM(require("snarkjs"), 1);
|
|
5430
3658
|
init_utxo();
|
|
5431
3659
|
|
|
5432
3660
|
// src/core/chain-note.ts
|
|
@@ -5577,7 +3805,7 @@ function chainNoteFromBase64(base64) {
|
|
|
5577
3805
|
// src/core/transact.ts
|
|
5578
3806
|
var import_circomlibjs4 = require("circomlibjs");
|
|
5579
3807
|
var import_tweetnacl4 = __toESM(require("tweetnacl"), 1);
|
|
5580
|
-
var
|
|
3808
|
+
var IS_BROWSER = typeof window !== "undefined" || typeof globalThis.document !== "undefined";
|
|
5581
3809
|
var DEFAULT_TRANSACTION_CIRCUITS_URL = "https://cloak-circuits.s3.us-east-1.amazonaws.com/circuits/0.1.0";
|
|
5582
3810
|
var DEFAULT_CLOAK_RELAY_URL = "https://api.cloak.ag";
|
|
5583
3811
|
function resolveDefaultTransactionCircuitsPath() {
|
|
@@ -5590,7 +3818,7 @@ async function resolveTransactionCircuitFiles() {
|
|
|
5590
3818
|
const baseNorm = base.endsWith("/") ? base : `${base}/`;
|
|
5591
3819
|
const wasmUrl = `${baseNorm}transaction_js/transaction.wasm`;
|
|
5592
3820
|
const zkeyUrl = `${baseNorm}transaction_final.zkey`;
|
|
5593
|
-
if (
|
|
3821
|
+
if (IS_BROWSER) {
|
|
5594
3822
|
return { wasmPath: wasmUrl, zkeyPath: zkeyUrl };
|
|
5595
3823
|
}
|
|
5596
3824
|
if (_remoteTxCircuitsCache?.base === baseNorm) {
|
|
@@ -5727,7 +3955,7 @@ async function resolveAddressLookupTableAccounts(connection, relayUrl, altAddres
|
|
|
5727
3955
|
const fetched = await Promise.all(
|
|
5728
3956
|
altAddresses.map(async (addr) => {
|
|
5729
3957
|
try {
|
|
5730
|
-
const pubkey = new
|
|
3958
|
+
const pubkey = new import_web36.PublicKey(addr);
|
|
5731
3959
|
const result = await connection.getAddressLookupTable(pubkey);
|
|
5732
3960
|
return result.value ?? null;
|
|
5733
3961
|
} catch {
|
|
@@ -5889,7 +4117,7 @@ async function generateTransactionProof(inputs, onProgress) {
|
|
|
5889
4117
|
onProgress?.(10);
|
|
5890
4118
|
const { wasmPath: wasmInput, zkeyPath: zkeyInput } = await resolveTransactionCircuitFiles();
|
|
5891
4119
|
onProgress?.(30);
|
|
5892
|
-
const { proof, publicSignals } = await
|
|
4120
|
+
const { proof, publicSignals } = await snarkjs.groth16.fullProve(
|
|
5893
4121
|
inputs,
|
|
5894
4122
|
wasmInput,
|
|
5895
4123
|
zkeyInput
|
|
@@ -5969,7 +4197,7 @@ function buildPublicInputsBytesFromSignals(publicSignals) {
|
|
|
5969
4197
|
}
|
|
5970
4198
|
var TRANSACT_DISCRIMINATOR = 0;
|
|
5971
4199
|
function deriveNullifierPDA(programId, poolPDA, nullifier) {
|
|
5972
|
-
return
|
|
4200
|
+
return import_web36.PublicKey.findProgramAddressSync(
|
|
5973
4201
|
[Buffer.from("nullifier"), poolPDA.toBuffer(), Buffer.from(nullifier)],
|
|
5974
4202
|
programId
|
|
5975
4203
|
);
|
|
@@ -6012,7 +4240,7 @@ function buildTransactInstruction(programId, payer, poolPDA, treasuryPDA, merkle
|
|
|
6012
4240
|
// 4. nullifier PDA 0 (writable)
|
|
6013
4241
|
{ pubkey: nullifierPDA1, isSigner: false, isWritable: true },
|
|
6014
4242
|
// 5. nullifier PDA 1 (writable)
|
|
6015
|
-
{ pubkey:
|
|
4243
|
+
{ pubkey: import_web36.SystemProgram.programId, isSigner: false, isWritable: false }
|
|
6016
4244
|
// 6. system_program
|
|
6017
4245
|
];
|
|
6018
4246
|
if (splAccounts) {
|
|
@@ -6021,23 +4249,23 @@ function buildTransactInstruction(programId, payer, poolPDA, treasuryPDA, merkle
|
|
|
6021
4249
|
accounts.push({ pubkey: splAccounts.tokenProgram, isSigner: false, isWritable: false });
|
|
6022
4250
|
if (splAccounts.payerAta) {
|
|
6023
4251
|
if (enableRiskCheck) {
|
|
6024
|
-
accounts.push({ pubkey:
|
|
4252
|
+
accounts.push({ pubkey: import_web36.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
|
|
6025
4253
|
}
|
|
6026
4254
|
accounts.push({ pubkey: splAccounts.payerAta, isSigner: false, isWritable: true });
|
|
6027
4255
|
} else if (enableRiskCheck && !recipient) {
|
|
6028
|
-
accounts.push({ pubkey:
|
|
4256
|
+
accounts.push({ pubkey: import_web36.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
|
|
6029
4257
|
}
|
|
6030
4258
|
} else {
|
|
6031
4259
|
if (recipient) {
|
|
6032
4260
|
accounts.push({ pubkey: recipient, isSigner: false, isWritable: true });
|
|
6033
4261
|
if (enableRiskCheck) {
|
|
6034
|
-
accounts.push({ pubkey:
|
|
4262
|
+
accounts.push({ pubkey: import_web36.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
|
|
6035
4263
|
}
|
|
6036
4264
|
} else if (enableRiskCheck) {
|
|
6037
|
-
accounts.push({ pubkey:
|
|
4265
|
+
accounts.push({ pubkey: import_web36.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
|
|
6038
4266
|
}
|
|
6039
4267
|
}
|
|
6040
|
-
return new
|
|
4268
|
+
return new import_web36.TransactionInstruction({
|
|
6041
4269
|
programId,
|
|
6042
4270
|
keys: accounts,
|
|
6043
4271
|
data
|
|
@@ -6045,10 +4273,10 @@ function buildTransactInstruction(programId, payer, poolPDA, treasuryPDA, merkle
|
|
|
6045
4273
|
}
|
|
6046
4274
|
function getCommonALTAddresses() {
|
|
6047
4275
|
return [
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
4276
|
+
import_web36.SystemProgram.programId,
|
|
4277
|
+
import_web36.SYSVAR_SLOT_HASHES_PUBKEY,
|
|
4278
|
+
import_web36.SYSVAR_INSTRUCTIONS_PUBKEY,
|
|
4279
|
+
import_web36.ComputeBudgetProgram.programId
|
|
6052
4280
|
];
|
|
6053
4281
|
}
|
|
6054
4282
|
function dedupePubkeys(addresses) {
|
|
@@ -6098,23 +4326,23 @@ async function createEphemeralALT(connection, depositor, onProgress, additionalA
|
|
|
6098
4326
|
for (let altAttempt = 0; altAttempt < MAX_ALT_CREATE_RETRIES; altAttempt++) {
|
|
6099
4327
|
try {
|
|
6100
4328
|
const slot = await connection.getSlot("finalized");
|
|
6101
|
-
const [createIx, altAddress] =
|
|
4329
|
+
const [createIx, altAddress] = import_web36.AddressLookupTableProgram.createLookupTable({
|
|
6102
4330
|
authority: depositor.publicKey,
|
|
6103
4331
|
payer: depositor.publicKey,
|
|
6104
4332
|
recentSlot: slot
|
|
6105
4333
|
});
|
|
6106
|
-
const extendIx =
|
|
4334
|
+
const extendIx = import_web36.AddressLookupTableProgram.extendLookupTable({
|
|
6107
4335
|
payer: depositor.publicKey,
|
|
6108
4336
|
authority: depositor.publicKey,
|
|
6109
4337
|
lookupTable: altAddress,
|
|
6110
4338
|
addresses: addressesForCreate
|
|
6111
4339
|
});
|
|
6112
|
-
const tx = new
|
|
4340
|
+
const tx = new import_web36.Transaction().add(createIx).add(extendIx);
|
|
6113
4341
|
const { blockhash } = await connection.getLatestBlockhash();
|
|
6114
4342
|
tx.recentBlockhash = blockhash;
|
|
6115
4343
|
tx.feePayer = depositor.publicKey;
|
|
6116
4344
|
if (depositor.keypair) {
|
|
6117
|
-
await (0,
|
|
4345
|
+
await (0, import_web36.sendAndConfirmTransaction)(connection, tx, [depositor.keypair], { commitment: "confirmed" });
|
|
6118
4346
|
} else if (depositor.signTransaction) {
|
|
6119
4347
|
const signedTx = await depositor.signTransaction(tx);
|
|
6120
4348
|
const sig = await connection.sendRawTransaction(signedTx.serialize());
|
|
@@ -6275,8 +4503,8 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
|
|
|
6275
4503
|
if (riskQuoteIx) {
|
|
6276
4504
|
baseInstructions.unshift(riskQuoteIx);
|
|
6277
4505
|
}
|
|
6278
|
-
const cuLimitIx =
|
|
6279
|
-
const cuPriceIx =
|
|
4506
|
+
const cuLimitIx = import_web36.ComputeBudgetProgram.setComputeUnitLimit({ units: 4e5 });
|
|
4507
|
+
const cuPriceIx = import_web36.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1e5 });
|
|
6280
4508
|
const fullInstructions = [...baseInstructions, cuLimitIx, cuPriceIx, transactIx];
|
|
6281
4509
|
const compactInstructions = [...baseInstructions, cuLimitIx, transactIx];
|
|
6282
4510
|
const minimalInstructions = [...baseInstructions, transactIx];
|
|
@@ -6287,7 +4515,7 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
|
|
|
6287
4515
|
if (useV0) {
|
|
6288
4516
|
const estimateV0Size = (ixs) => {
|
|
6289
4517
|
try {
|
|
6290
|
-
const messageV0 = new
|
|
4518
|
+
const messageV0 = new import_web36.TransactionMessage({
|
|
6291
4519
|
payerKey: depositor.publicKey,
|
|
6292
4520
|
recentBlockhash: blockhash,
|
|
6293
4521
|
instructions: ixs
|
|
@@ -6347,12 +4575,12 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
|
|
|
6347
4575
|
}
|
|
6348
4576
|
let transportAttempt = 0;
|
|
6349
4577
|
while (true) {
|
|
6350
|
-
const messageV0 = new
|
|
4578
|
+
const messageV0 = new import_web36.TransactionMessage({
|
|
6351
4579
|
payerKey: depositor.publicKey,
|
|
6352
4580
|
recentBlockhash: blockhash,
|
|
6353
4581
|
instructions: ixs
|
|
6354
4582
|
}).compileToV0Message(addressLookupTableAccounts);
|
|
6355
|
-
const versionedTx = new
|
|
4583
|
+
const versionedTx = new import_web36.VersionedTransaction(messageV0);
|
|
6356
4584
|
try {
|
|
6357
4585
|
if (depositor.keypair) {
|
|
6358
4586
|
onProgress?.("Signing and sending transaction...");
|
|
@@ -6485,7 +4713,7 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
|
|
|
6485
4713
|
let legacyTransportAttempt = 0;
|
|
6486
4714
|
while (true) {
|
|
6487
4715
|
try {
|
|
6488
|
-
const tx = new
|
|
4716
|
+
const tx = new import_web36.Transaction();
|
|
6489
4717
|
for (const ix of instructions) {
|
|
6490
4718
|
tx.add(ix);
|
|
6491
4719
|
}
|
|
@@ -6493,7 +4721,7 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
|
|
|
6493
4721
|
tx.feePayer = depositor.publicKey;
|
|
6494
4722
|
if (depositor.keypair) {
|
|
6495
4723
|
onProgress?.("Signing and sending transaction...");
|
|
6496
|
-
signature = await (0,
|
|
4724
|
+
signature = await (0, import_web36.sendAndConfirmTransaction)(
|
|
6497
4725
|
connection,
|
|
6498
4726
|
tx,
|
|
6499
4727
|
[depositor.keypair],
|
|
@@ -6667,7 +4895,7 @@ async function fetchRiskQuoteInstruction(riskQuoteUrl, wallet, options) {
|
|
|
6667
4895
|
const signerB58 = signerKey;
|
|
6668
4896
|
const signatureBuf = Buffer.from(signatureB64, "base64");
|
|
6669
4897
|
const messageArr = hexToBytes2(messageHex);
|
|
6670
|
-
const publicKeyBytes = new
|
|
4898
|
+
const publicKeyBytes = new import_web36.PublicKey(signerB58).toBytes();
|
|
6671
4899
|
if (signatureBuf.length !== 64 || messageArr.length !== 33 && messageArr.length !== 41 && messageArr.length !== 50 || publicKeyBytes.length !== 32) {
|
|
6672
4900
|
throw new Error(
|
|
6673
4901
|
`Invalid risk quote response: invalid lengths (sig=${signatureBuf.length}, msg=${messageArr.length}, pk=${publicKeyBytes.length})`
|
|
@@ -6676,7 +4904,7 @@ async function fetchRiskQuoteInstruction(riskQuoteUrl, wallet, options) {
|
|
|
6676
4904
|
const signature = new Uint8Array(signatureBuf);
|
|
6677
4905
|
const message = new Uint8Array(messageArr);
|
|
6678
4906
|
const publicKey = new Uint8Array(publicKeyBytes);
|
|
6679
|
-
return
|
|
4907
|
+
return import_web36.Ed25519Program.createInstructionWithPublicKey({
|
|
6680
4908
|
publicKey,
|
|
6681
4909
|
message,
|
|
6682
4910
|
signature
|
|
@@ -6689,10 +4917,10 @@ async function fetchRiskQuoteInstruction(riskQuoteUrl, wallet, options) {
|
|
|
6689
4917
|
`Invalid risk quote response: expected relay format { signature, message, signer_pubkey } or { instruction }. Received keys: ${keys}`
|
|
6690
4918
|
);
|
|
6691
4919
|
}
|
|
6692
|
-
return new
|
|
6693
|
-
programId: new
|
|
4920
|
+
return new import_web36.TransactionInstruction({
|
|
4921
|
+
programId: new import_web36.PublicKey(raw.programId),
|
|
6694
4922
|
keys: raw.keys.map((k) => ({
|
|
6695
|
-
pubkey: new
|
|
4923
|
+
pubkey: new import_web36.PublicKey(k.pubkey),
|
|
6696
4924
|
isSigner: k.isSigner,
|
|
6697
4925
|
isWritable: k.isWritable
|
|
6698
4926
|
})),
|
|
@@ -6746,6 +4974,7 @@ async function transact(params, options) {
|
|
|
6746
4974
|
if (outputUtxos.length > 2) {
|
|
6747
4975
|
throw new Error("Maximum 2 output UTXOs allowed");
|
|
6748
4976
|
}
|
|
4977
|
+
await preflightNullifiers(inputUtxos, connection, programId);
|
|
6749
4978
|
const mint = inputUtxos.find((u) => u.amount > 0)?.mintAddress ?? outputUtxos.find((u) => u.amount > 0)?.mintAddress ?? NATIVE_SOL_MINT;
|
|
6750
4979
|
const paddedInputs = [...inputUtxos];
|
|
6751
4980
|
while (paddedInputs.length < 2) {
|
|
@@ -7061,7 +5290,7 @@ async function transact(params, options) {
|
|
|
7061
5290
|
const outputCommitments = [];
|
|
7062
5291
|
for (let i = 0; i < paddedOutputs.length; i++) {
|
|
7063
5292
|
const utxo = paddedOutputs[i];
|
|
7064
|
-
const commitment = await
|
|
5293
|
+
const commitment = await computeCommitment(utxo);
|
|
7065
5294
|
outputCommitments.push(commitment);
|
|
7066
5295
|
}
|
|
7067
5296
|
onProgress?.("Computing external data hash...");
|
|
@@ -7142,7 +5371,7 @@ async function transact(params, options) {
|
|
|
7142
5371
|
}
|
|
7143
5372
|
inputNullifiers = [];
|
|
7144
5373
|
for (const utxo of paddedInputs) {
|
|
7145
|
-
inputNullifiers.push(await
|
|
5374
|
+
inputNullifiers.push(await computeNullifier(utxo));
|
|
7146
5375
|
}
|
|
7147
5376
|
if (nextIndex !== null) {
|
|
7148
5377
|
onProgress?.(`[Submit ${submissionAttempts + 1}/${maxRootRetries + 1}] Root: ${rootHex.substring(0, 16)}..., nextIndex: ${nextIndex}`);
|
|
@@ -7573,10 +5802,46 @@ async function transact(params, options) {
|
|
|
7573
5802
|
}
|
|
7574
5803
|
}
|
|
7575
5804
|
if (lastError) {
|
|
5805
|
+
const classified = classifyRelayError(lastError.message);
|
|
5806
|
+
const isMerkleClass = !(classified instanceof UtxoAlreadySpentError) && !(classified instanceof SanctionsQuoteError) && !isRootNotFoundError2(lastError);
|
|
5807
|
+
if (!IS_BROWSER && isMerkleClass && relayUrl) {
|
|
5808
|
+
onProgress?.(
|
|
5809
|
+
"All relay-tree attempts failed. Rebuilding merkle tree from chain as last resort..."
|
|
5810
|
+
);
|
|
5811
|
+
try {
|
|
5812
|
+
const [merkleTreePda] = import_web36.PublicKey.findProgramAddressSync(
|
|
5813
|
+
[Buffer.from("merkle_tree"), mint.toBuffer()],
|
|
5814
|
+
programId
|
|
5815
|
+
);
|
|
5816
|
+
const chainTree = await buildMerkleTreeFromChain(
|
|
5817
|
+
connection,
|
|
5818
|
+
programId,
|
|
5819
|
+
merkleTreePda,
|
|
5820
|
+
onProgress
|
|
5821
|
+
);
|
|
5822
|
+
const chainRootHex = chainTree.root().toString(16).padStart(64, "0");
|
|
5823
|
+
onProgress?.(
|
|
5824
|
+
`Chain-rebuilt tree root: ${chainRootHex.substring(0, 16)}... \u2014 attached to error for caller retry`
|
|
5825
|
+
);
|
|
5826
|
+
throw new RelayInternalError(
|
|
5827
|
+
`All relay-tree attempts failed. Chain-rebuilt tree available (root ${chainRootHex.substring(0, 16)}...). Retry this call with options.cachedMerkleTree = err.cachedTreeFromChain. Original error: ${lastError.message}`,
|
|
5828
|
+
lastError.message,
|
|
5829
|
+
void 0,
|
|
5830
|
+
chainTree
|
|
5831
|
+
);
|
|
5832
|
+
} catch (e) {
|
|
5833
|
+
if (e instanceof RelayInternalError) throw e;
|
|
5834
|
+
onProgress?.(
|
|
5835
|
+
`Chain-replay fallback failed: ${e instanceof Error ? e.message : String(e)}`
|
|
5836
|
+
);
|
|
5837
|
+
}
|
|
5838
|
+
}
|
|
7576
5839
|
if (isRootNotFoundError2(lastError)) {
|
|
7577
|
-
throw new Error(
|
|
5840
|
+
throw new Error(
|
|
5841
|
+
`Transaction failed after ${maxRootRetries + 1} retries: ${lastError.message}`
|
|
5842
|
+
);
|
|
7578
5843
|
}
|
|
7579
|
-
throw
|
|
5844
|
+
throw classified;
|
|
7580
5845
|
}
|
|
7581
5846
|
if (relayUrl && outputCommitments.length >= 2) {
|
|
7582
5847
|
const hasOnChainIndices = commitmentIndices[0] >= 0 && commitmentIndices[1] >= 0;
|
|
@@ -7880,6 +6145,7 @@ async function swapUtxo(params, options) {
|
|
|
7880
6145
|
if (inputUtxos.length > 2) {
|
|
7881
6146
|
throw new Error("Maximum 2 input UTXOs allowed");
|
|
7882
6147
|
}
|
|
6148
|
+
await preflightNullifiers(inputUtxos, connection, programId);
|
|
7883
6149
|
const outputMintAccount = await connection.getAccountInfo(outputMint, {
|
|
7884
6150
|
commitment: "confirmed"
|
|
7885
6151
|
});
|
|
@@ -8187,7 +6453,7 @@ async function swapUtxo(params, options) {
|
|
|
8187
6453
|
let inputNullifiers = [];
|
|
8188
6454
|
const outputCommitments = [];
|
|
8189
6455
|
for (const utxo of paddedOutputs) {
|
|
8190
|
-
outputCommitments.push(await
|
|
6456
|
+
outputCommitments.push(await computeCommitment(utxo));
|
|
8191
6457
|
}
|
|
8192
6458
|
const chainNoteTimestamp = BigInt(Date.now());
|
|
8193
6459
|
onProgress?.("Computing external data hash...");
|
|
@@ -8267,7 +6533,7 @@ async function swapUtxo(params, options) {
|
|
|
8267
6533
|
}
|
|
8268
6534
|
inputNullifiers = [];
|
|
8269
6535
|
for (const utxo of paddedInputs) {
|
|
8270
|
-
inputNullifiers.push(await
|
|
6536
|
+
inputNullifiers.push(await computeNullifier(utxo));
|
|
8271
6537
|
}
|
|
8272
6538
|
if (merkleState) {
|
|
8273
6539
|
onProgress?.(`[Attempt ${attempt + 1}/${maxRootRetries + 1}] Root: ${rootHex.substring(0, 16)}..., nextIndex: ${merkleState.nextIndex}`);
|
|
@@ -8611,7 +6877,7 @@ async function swapWithChange(inputUtxos, swapAmount, outputMint, recipientAta,
|
|
|
8611
6877
|
|
|
8612
6878
|
// src/core/scanner.ts
|
|
8613
6879
|
var import_bs582 = __toESM(require("bs58"), 1);
|
|
8614
|
-
var
|
|
6880
|
+
var import_web37 = require("@solana/web3.js");
|
|
8615
6881
|
var import_spl_token2 = require("@solana/spl-token");
|
|
8616
6882
|
var bs582 = import_bs582.default.default || import_bs582.default;
|
|
8617
6883
|
var TRANSACT_TAG2 = 0;
|
|
@@ -8698,7 +6964,7 @@ function parseSwapOutputMint(data) {
|
|
|
8698
6964
|
const mintBytes = data.slice(SWAP_OUTPUT_MINT_OFFSET, end);
|
|
8699
6965
|
if (mintBytes.every((byte) => byte === 0)) return void 0;
|
|
8700
6966
|
try {
|
|
8701
|
-
return new
|
|
6967
|
+
return new import_web37.PublicKey(mintBytes).toBase58();
|
|
8702
6968
|
} catch {
|
|
8703
6969
|
return void 0;
|
|
8704
6970
|
}
|
|
@@ -8788,7 +7054,7 @@ function parseSwapRecipientAta(data) {
|
|
|
8788
7054
|
const recipientAtaBytes = data.slice(recipientAtaStart, recipientAtaEnd);
|
|
8789
7055
|
if (recipientAtaBytes.every((byte) => byte === 0)) return void 0;
|
|
8790
7056
|
try {
|
|
8791
|
-
return new
|
|
7057
|
+
return new import_web37.PublicKey(recipientAtaBytes).toBase58();
|
|
8792
7058
|
} catch {
|
|
8793
7059
|
return void 0;
|
|
8794
7060
|
}
|
|
@@ -9103,8 +7369,8 @@ async function scanTransactions(opts) {
|
|
|
9103
7369
|
if (onChainAta) {
|
|
9104
7370
|
try {
|
|
9105
7371
|
const expectedAta = (0, import_spl_token2.getAssociatedTokenAddressSync)(
|
|
9106
|
-
new
|
|
9107
|
-
new
|
|
7372
|
+
new import_web37.PublicKey(asset.mint),
|
|
7373
|
+
new import_web37.PublicKey(walletPublicKey)
|
|
9108
7374
|
).toBase58();
|
|
9109
7375
|
if (onChainAta === expectedAta) {
|
|
9110
7376
|
isOurs = true;
|
|
@@ -9407,7 +7673,7 @@ function formatComplianceCsv(report) {
|
|
|
9407
7673
|
}
|
|
9408
7674
|
|
|
9409
7675
|
// src/core/wallet.ts
|
|
9410
|
-
var
|
|
7676
|
+
var import_web38 = require("@solana/web3.js");
|
|
9411
7677
|
init_utxo();
|
|
9412
7678
|
var UtxoWallet = class _UtxoWallet {
|
|
9413
7679
|
constructor(viewingKey) {
|
|
@@ -9620,7 +7886,7 @@ var UtxoWallet = class _UtxoWallet {
|
|
|
9620
7886
|
data.viewingKey ? new Uint8Array(data.viewingKey) : void 0
|
|
9621
7887
|
);
|
|
9622
7888
|
for (const w of data.wallets) {
|
|
9623
|
-
const mint = new
|
|
7889
|
+
const mint = new import_web38.PublicKey(w.mint);
|
|
9624
7890
|
for (const u of w.utxos) {
|
|
9625
7891
|
wallet.addUtxo({
|
|
9626
7892
|
amount: BigInt(u.amount),
|
|
@@ -9703,16 +7969,14 @@ var SimpleWallet = class {
|
|
|
9703
7969
|
};
|
|
9704
7970
|
|
|
9705
7971
|
// src/index.ts
|
|
9706
|
-
var VERSION = "1.
|
|
7972
|
+
var VERSION = "0.1.6";
|
|
9707
7973
|
var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
9708
7974
|
// Annotate the CommonJS export names for ESM import in node:
|
|
9709
7975
|
0 && (module.exports = {
|
|
9710
7976
|
CLOAK_PROGRAM_ID,
|
|
9711
7977
|
CloakError,
|
|
9712
7978
|
CloakSDK,
|
|
9713
|
-
DEFAULT_CIRCUITS_URL,
|
|
9714
7979
|
DEFAULT_TRANSACTION_CIRCUITS_URL,
|
|
9715
|
-
EXPECTED_CIRCUIT_HASHES,
|
|
9716
7980
|
FIXED_FEE_LAMPORTS,
|
|
9717
7981
|
LAMPORTS_PER_SOL,
|
|
9718
7982
|
LocalStorageAdapter,
|
|
@@ -9721,18 +7985,20 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
|
9721
7985
|
MemoryStorageAdapter,
|
|
9722
7986
|
MerkleTree,
|
|
9723
7987
|
NATIVE_SOL_MINT,
|
|
7988
|
+
RelayInternalError,
|
|
9724
7989
|
RelayService,
|
|
9725
7990
|
RootNotFoundError,
|
|
9726
7991
|
SCANNER_SUPPORTS_TRANSACT_SWAP,
|
|
9727
7992
|
SIGN_IN_MESSAGE,
|
|
7993
|
+
SanctionsQuoteError,
|
|
9728
7994
|
ShieldPoolErrors,
|
|
9729
7995
|
SimpleWallet,
|
|
7996
|
+
UtxoAlreadySpentError,
|
|
9730
7997
|
UtxoWallet,
|
|
9731
7998
|
VARIABLE_FEE_DENOMINATOR,
|
|
9732
7999
|
VARIABLE_FEE_NUMERATOR,
|
|
9733
8000
|
VARIABLE_FEE_RATE,
|
|
9734
8001
|
VERSION,
|
|
9735
|
-
areCircuitsAvailable,
|
|
9736
8002
|
bigintToBytes32,
|
|
9737
8003
|
bigintToHex,
|
|
9738
8004
|
buildMerkleTree,
|
|
@@ -9745,30 +8011,16 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
|
9745
8011
|
calculateRelayFee,
|
|
9746
8012
|
chainNoteFromBase64,
|
|
9747
8013
|
chainNoteToBase64,
|
|
9748
|
-
|
|
9749
|
-
clearPendingDeposits,
|
|
9750
|
-
clearPendingWithdrawals,
|
|
8014
|
+
classifyRelayError,
|
|
9751
8015
|
computeChainNoteHash,
|
|
9752
|
-
computeCommitment,
|
|
9753
8016
|
computeExtDataHash,
|
|
9754
8017
|
computeMerkleRoot,
|
|
9755
|
-
computeNullifier,
|
|
9756
|
-
computeNullifierAsync,
|
|
9757
|
-
computeNullifierSync,
|
|
9758
|
-
computeOutputsHash,
|
|
9759
|
-
computeOutputsHashAsync,
|
|
9760
|
-
computeOutputsHashSync,
|
|
9761
8018
|
computeProofForLatestDeposit,
|
|
9762
8019
|
computeProofFromChain,
|
|
9763
8020
|
computeSignature,
|
|
9764
|
-
computeSwapOutputsHash,
|
|
9765
|
-
computeSwapOutputsHashAsync,
|
|
9766
|
-
computeSwapOutputsHashSync,
|
|
9767
8021
|
computeUtxoCommitment,
|
|
9768
8022
|
computeUtxoNullifier,
|
|
9769
|
-
copyNoteToClipboard,
|
|
9770
8023
|
createCloakError,
|
|
9771
|
-
createDepositInstruction,
|
|
9772
8024
|
createLogger,
|
|
9773
8025
|
createUtxo,
|
|
9774
8026
|
createZeroUtxo,
|
|
@@ -9788,77 +8040,52 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
|
9788
8040
|
deriveViewingKeyFromUtxoPrivateKey,
|
|
9789
8041
|
deserializeUtxo,
|
|
9790
8042
|
detectNetworkFromRpcUrl,
|
|
9791
|
-
downloadNote,
|
|
9792
|
-
encodeNoteSimple,
|
|
9793
8043
|
encryptCompactChainNote,
|
|
9794
8044
|
encryptNoteForRecipient,
|
|
9795
8045
|
encryptTransactionMetadata,
|
|
9796
8046
|
encryptTransactionMetadataBundle,
|
|
9797
8047
|
expandSpendKey,
|
|
9798
8048
|
exportKeys,
|
|
9799
|
-
exportNote,
|
|
9800
|
-
exportWalletKeys,
|
|
9801
8049
|
fetchCommitments,
|
|
9802
8050
|
fetchRiskQuoteInstruction,
|
|
9803
8051
|
fetchRiskQuoteIx,
|
|
9804
|
-
filterNotesByNetwork,
|
|
9805
|
-
filterWithdrawableNotes,
|
|
9806
|
-
findNoteByCommitment,
|
|
9807
8052
|
formatAmount,
|
|
9808
8053
|
formatComplianceCsv,
|
|
9809
8054
|
formatErrorForLogging,
|
|
9810
8055
|
formatSol,
|
|
9811
8056
|
fullWithdraw,
|
|
9812
8057
|
generateCloakKeys,
|
|
9813
|
-
generateCommitment,
|
|
9814
|
-
generateCommitmentAsync,
|
|
9815
8058
|
generateMasterSeed,
|
|
9816
|
-
generateNote,
|
|
9817
|
-
generateNoteFromWallet,
|
|
9818
8059
|
generateUtxoKeypair,
|
|
9819
8060
|
generateViewingKeyPair,
|
|
9820
|
-
generateWithdrawRegularProof,
|
|
9821
|
-
generateWithdrawSwapProof,
|
|
9822
8061
|
getAddressExplorerUrl,
|
|
9823
8062
|
getCircuitsPath,
|
|
9824
|
-
getDefaultCircuitsPath,
|
|
9825
8063
|
getDistributableAmount,
|
|
9826
8064
|
getExplorerUrl,
|
|
9827
8065
|
getNkFromUtxoPrivateKey,
|
|
9828
8066
|
getNullifierPDA,
|
|
9829
|
-
getPendingOperationsSummary,
|
|
9830
8067
|
getPublicKey,
|
|
9831
|
-
getPublicViewKey,
|
|
9832
|
-
getRecipientAmount,
|
|
9833
8068
|
getRpcUrlForNetwork,
|
|
9834
8069
|
getShieldPoolPDAs,
|
|
9835
8070
|
getSwapStatePDA,
|
|
9836
|
-
getViewKey,
|
|
9837
|
-
hasPendingOperations,
|
|
9838
8071
|
hexToBigint,
|
|
9839
8072
|
hexToBytes,
|
|
9840
8073
|
importKeys,
|
|
9841
|
-
importWalletKeys,
|
|
9842
8074
|
isDebugEnabled,
|
|
9843
8075
|
isRootNotFoundError,
|
|
9844
8076
|
isValidHex,
|
|
9845
8077
|
isValidRpcUrl,
|
|
9846
8078
|
isValidSolanaAddress,
|
|
9847
8079
|
isWithdrawAmountSufficient,
|
|
9848
|
-
isWithdrawable,
|
|
9849
8080
|
keypairToAdapter,
|
|
9850
|
-
loadPendingDeposits,
|
|
9851
|
-
loadPendingWithdrawals,
|
|
9852
8081
|
parseAmount,
|
|
9853
8082
|
parseError,
|
|
9854
|
-
parseNote,
|
|
9855
8083
|
parseRelayErrorResponse,
|
|
9856
8084
|
parseTransactionError,
|
|
9857
8085
|
partialWithdraw,
|
|
9858
8086
|
poseidonHash,
|
|
9859
8087
|
preflightCheck,
|
|
9860
|
-
|
|
9861
|
-
prepareEncryptedOutputForRecipient,
|
|
8088
|
+
preflightNullifiers,
|
|
9862
8089
|
proofToBytes,
|
|
9863
8090
|
pubkeyToFieldElement,
|
|
9864
8091
|
pubkeyToLimbs,
|
|
@@ -9866,16 +8093,11 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
|
9866
8093
|
randomFieldElement,
|
|
9867
8094
|
readMerkleTreeState,
|
|
9868
8095
|
registerViewingKey,
|
|
9869
|
-
removePendingDeposit,
|
|
9870
|
-
removePendingWithdrawal,
|
|
9871
|
-
savePendingDeposit,
|
|
9872
|
-
savePendingWithdrawal,
|
|
9873
8096
|
scanNotesForWallet,
|
|
9874
8097
|
scanTransactions,
|
|
9875
8098
|
sdkLogger,
|
|
9876
8099
|
selectUtxos,
|
|
9877
8100
|
sendTransaction,
|
|
9878
|
-
serializeNote,
|
|
9879
8101
|
serializeUtxo,
|
|
9880
8102
|
setCircuitsPath,
|
|
9881
8103
|
setDebugMode,
|
|
@@ -9889,21 +8111,13 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
|
9889
8111
|
transfer,
|
|
9890
8112
|
truncate,
|
|
9891
8113
|
tryDecryptNote,
|
|
9892
|
-
updateNoteWithDeposit,
|
|
9893
|
-
updatePendingDeposit,
|
|
9894
|
-
updatePendingWithdrawal,
|
|
9895
8114
|
utxoBigintToBytes32,
|
|
9896
8115
|
utxoEquals,
|
|
9897
8116
|
utxoHexToBigint,
|
|
9898
|
-
validateDepositParams,
|
|
9899
|
-
validateNote,
|
|
9900
8117
|
validateOutputsSum,
|
|
9901
8118
|
validateRoot,
|
|
9902
|
-
validateTransfers,
|
|
9903
8119
|
validateWalletConnected,
|
|
9904
|
-
|
|
9905
|
-
verifyAllCircuits,
|
|
9906
|
-
verifyCircuitIntegrity,
|
|
8120
|
+
verifyUtxos,
|
|
9907
8121
|
waitForRoot,
|
|
9908
8122
|
withTiming
|
|
9909
8123
|
});
|