@cloak.dev/sdk 0.2.2 → 0.2.3-staging.005e3de
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -11
- package/dist/index.cjs +1620 -378
- package/dist/index.d.cts +1077 -109
- package/dist/index.d.ts +1077 -109
- package/dist/index.js +1545 -327
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -455,7 +455,7 @@ async function deserializeUtxo(bytes) {
|
|
|
455
455
|
const blindingBytes = bytes.slice(40, 72);
|
|
456
456
|
const blinding = bytesToBigint2(blindingBytes);
|
|
457
457
|
const mintBytes = bytes.slice(72, 104);
|
|
458
|
-
const mintAddress = new
|
|
458
|
+
const mintAddress = new import_web34.PublicKey(mintBytes);
|
|
459
459
|
const index = view.getUint32(104, true);
|
|
460
460
|
const utxo = {
|
|
461
461
|
amount,
|
|
@@ -523,18 +523,18 @@ function selectUtxos(available, targetAmount) {
|
|
|
523
523
|
}
|
|
524
524
|
return selected;
|
|
525
525
|
}
|
|
526
|
-
var
|
|
526
|
+
var import_web34, import_circomlibjs2, import_blake33, FIELD_MODULUS, KEYPAIR_DOMAIN_TAG, UTXO_KEY_DOMAIN, NATIVE_SOL_MINT, poseidonInstance;
|
|
527
527
|
var init_utxo = __esm({
|
|
528
528
|
"src/notes/utxo.ts"() {
|
|
529
529
|
"use strict";
|
|
530
|
-
|
|
530
|
+
import_web34 = require("@solana/web3.js");
|
|
531
531
|
import_circomlibjs2 = require("circomlibjs");
|
|
532
532
|
import_blake33 = require("@noble/hashes/blake3");
|
|
533
533
|
init_inputs();
|
|
534
534
|
FIELD_MODULUS = BigInt("21888242871839275222246405745257275088548364400416034343698204186575808495617");
|
|
535
535
|
KEYPAIR_DOMAIN_TAG = BigInt("5423839527465210463");
|
|
536
536
|
UTXO_KEY_DOMAIN = new TextEncoder().encode("cloak_utxo_priv_v1");
|
|
537
|
-
NATIVE_SOL_MINT = new
|
|
537
|
+
NATIVE_SOL_MINT = new import_web34.PublicKey("So11111111111111111111111111111111111111112");
|
|
538
538
|
poseidonInstance = null;
|
|
539
539
|
}
|
|
540
540
|
});
|
|
@@ -542,8 +542,10 @@ var init_utxo = __esm({
|
|
|
542
542
|
// src/index.ts
|
|
543
543
|
var index_exports = {};
|
|
544
544
|
__export(index_exports, {
|
|
545
|
+
BRIDGE_ESCROW_LABEL: () => BRIDGE_ESCROW_LABEL,
|
|
545
546
|
BUILD_ALLOWS_LOCAL_ENDPOINTS: () => BUILD_ALLOWS_LOCAL_ENDPOINTS,
|
|
546
547
|
CHAIN_NOTE_SALT_BITS: () => CHAIN_NOTE_SALT_BITS,
|
|
548
|
+
CLEANUP_FEE_BUDGET: () => CLEANUP_FEE_BUDGET,
|
|
547
549
|
CLOAK_PRODUCTION_RELAY_URL: () => CLOAK_PRODUCTION_RELAY_URL,
|
|
548
550
|
CLOAK_PROGRAM_ID: () => CLOAK_PROGRAM_ID,
|
|
549
551
|
CloakError: () => CloakError,
|
|
@@ -551,25 +553,38 @@ __export(index_exports, {
|
|
|
551
553
|
DEFAULT_TRANSACTION_CIRCUITS_URL: () => DEFAULT_TRANSACTION_CIRCUITS_URL,
|
|
552
554
|
DELIVERY_MEMO_TAG: () => DELIVERY_MEMO_TAG,
|
|
553
555
|
DELIVERY_REGISTRY_SEED: () => DELIVERY_REGISTRY_SEED,
|
|
556
|
+
DepositError: () => DepositError,
|
|
557
|
+
DoNotRetry: () => DoNotRetry,
|
|
558
|
+
ECONOMIC_MINIMUM: () => ECONOMIC_MINIMUM,
|
|
554
559
|
EXPECTED_CIRCUIT_HASHES: () => EXPECTED_CIRCUIT_HASHES,
|
|
560
|
+
FEE_BUDGET: () => FEE_BUDGET,
|
|
555
561
|
FIXED_FEE_LAMPORTS: () => FIXED_FEE_LAMPORTS,
|
|
556
562
|
InsecureRandomnessError: () => InsecureRandomnessError,
|
|
557
563
|
LAMPORTS_PER_SOL: () => LAMPORTS_PER_SOL,
|
|
558
564
|
LocalStorageAdapter: () => LocalStorageAdapter,
|
|
565
|
+
MAINNET_RENT_0: () => MAINNET_RENT_0,
|
|
566
|
+
MAINNET_RENT_1: () => MAINNET_RENT_1,
|
|
567
|
+
MAX_RECEIVER_INDEX: () => MAX_RECEIVER_INDEX,
|
|
559
568
|
MERKLE_TREE_HEIGHT: () => MERKLE_TREE_HEIGHT2,
|
|
560
569
|
MIN_DEPOSIT_LAMPORTS: () => MIN_DEPOSIT_LAMPORTS,
|
|
570
|
+
MIN_DEPOSIT_SPL_BASE_UNITS: () => MIN_DEPOSIT_SPL_BASE_UNITS,
|
|
561
571
|
MemoryStorageAdapter: () => MemoryStorageAdapter,
|
|
562
572
|
MerkleTree: () => MerkleTree,
|
|
563
573
|
NATIVE_SOL_MINT: () => NATIVE_SOL_MINT,
|
|
574
|
+
ONECLICK_PUBKEY_B58: () => ONECLICK_PUBKEY_B58,
|
|
564
575
|
RECIPIENT_DELIVERY_CIPHERTEXT_LEN: () => RECIPIENT_DELIVERY_CIPHERTEXT_LEN,
|
|
565
576
|
RECIPIENT_DELIVERY_EPHEMERAL_PK_LEN: () => RECIPIENT_DELIVERY_EPHEMERAL_PK_LEN,
|
|
566
577
|
RECIPIENT_DELIVERY_NONCE_LEN: () => RECIPIENT_DELIVERY_NONCE_LEN,
|
|
567
578
|
RECIPIENT_DELIVERY_NOTE_BYTES: () => RECIPIENT_DELIVERY_NOTE_BYTES,
|
|
568
579
|
RECIPIENT_DELIVERY_PLAINTEXT_LEN: () => RECIPIENT_DELIVERY_PLAINTEXT_LEN,
|
|
569
580
|
RECIPIENT_DELIVERY_TAG_LEN: () => RECIPIENT_DELIVERY_TAG_LEN,
|
|
581
|
+
RELAY_BATCH_AUTH_MAX_ITEMS: () => RELAY_BATCH_AUTH_MAX_ITEMS,
|
|
570
582
|
RELAY_ORIGIN_ALLOWLIST: () => RELAY_ORIGIN_ALLOWLIST,
|
|
583
|
+
REQUEST_AUTH_BATCH_DOMAIN: () => REQUEST_AUTH_BATCH_DOMAIN,
|
|
584
|
+
REQUEST_AUTH_BATCH_MAX_AGE_SECONDS: () => REQUEST_AUTH_BATCH_MAX_AGE_SECONDS,
|
|
571
585
|
REQUEST_AUTH_MAX_AGE_SECONDS: () => REQUEST_AUTH_MAX_AGE_SECONDS,
|
|
572
586
|
REQUEST_AUTH_MAX_FUTURE_SKEW_SECONDS: () => REQUEST_AUTH_MAX_FUTURE_SKEW_SECONDS,
|
|
587
|
+
RelayBatchAuthCoordinator: () => RelayBatchAuthCoordinator,
|
|
573
588
|
RelayInternalError: () => RelayInternalError,
|
|
574
589
|
RelayService: () => RelayService,
|
|
575
590
|
RootNotFoundError: () => RootNotFoundError,
|
|
@@ -579,6 +594,7 @@ __export(index_exports, {
|
|
|
579
594
|
SettlementVerificationError: () => SettlementVerificationError,
|
|
580
595
|
ShieldPoolErrors: () => ShieldPoolErrors,
|
|
581
596
|
SimpleWallet: () => SimpleWallet,
|
|
597
|
+
TERMINAL_FAILURE: () => TERMINAL_FAILURE,
|
|
582
598
|
TRANSACTION_CIRCUITS_VERSION: () => TRANSACTION_CIRCUITS_VERSION,
|
|
583
599
|
TRANSACT_AUTH_FIELDS: () => TRANSACT_AUTH_FIELDS,
|
|
584
600
|
TRANSACT_SWAP_AUTH_FIELDS: () => TRANSACT_SWAP_AUTH_FIELDS,
|
|
@@ -588,15 +604,21 @@ __export(index_exports, {
|
|
|
588
604
|
VARIABLE_FEE_NUMERATOR: () => VARIABLE_FEE_NUMERATOR,
|
|
589
605
|
VARIABLE_FEE_RATE: () => VARIABLE_FEE_RATE,
|
|
590
606
|
VERSION: () => VERSION,
|
|
607
|
+
WITHDRAW_FEE_BPS: () => WITHDRAW_FEE_BPS,
|
|
608
|
+
WITHDRAW_FIXED_FEE: () => WITHDRAW_FIXED_FEE,
|
|
591
609
|
assertDirectSubmissionLanded: () => assertDirectSubmissionLanded,
|
|
610
|
+
assertInputMints: () => assertInputMints,
|
|
592
611
|
assertTransactionCircuitIntegrity: () => assertTransactionCircuitIntegrity,
|
|
612
|
+
assessBridgeQuote: () => assessBridgeQuote,
|
|
593
613
|
bigintToBytes32: () => bigintToBytes32,
|
|
594
614
|
bigintToHex: () => bigintToHex,
|
|
615
|
+
buildAuthTransactionMessage: () => buildAuthTransactionMessage,
|
|
595
616
|
buildMerkleTree: () => buildMerkleTree,
|
|
596
617
|
buildMerkleTreeFromChain: () => buildMerkleTreeFromChain,
|
|
597
618
|
buildMerkleTreeFromRelay: () => buildMerkleTreeFromRelay,
|
|
598
619
|
buildRecipientDeliveryNotes: () => buildRecipientDeliveryNotes,
|
|
599
620
|
buildRelayAuthPreimage: () => buildRelayAuthPreimage,
|
|
621
|
+
buildRelayBatchAuthMessage: () => buildRelayBatchAuthMessage,
|
|
600
622
|
buildTransactRequestBody: () => buildTransactRequestBody,
|
|
601
623
|
bytesToHex: () => bytesToHex,
|
|
602
624
|
calculateFee: () => calculateFee,
|
|
@@ -604,12 +626,16 @@ __export(index_exports, {
|
|
|
604
626
|
calculateRelayFee: () => calculateRelayFee,
|
|
605
627
|
canRebuildMerkleTreeFromChain: () => canRebuildMerkleTreeFromChain,
|
|
606
628
|
canonicalJson: () => canonicalJson,
|
|
629
|
+
canonicalPayloadString: () => canonicalPayloadString,
|
|
607
630
|
chainNoteFromBase64: () => chainNoteFromBase64,
|
|
608
631
|
chainNoteToBase64: () => chainNoteToBase64,
|
|
632
|
+
classifyPostFailure: () => classifyPostFailure,
|
|
609
633
|
classifyRelayError: () => classifyRelayError,
|
|
634
|
+
cleanupReceivingAddress: () => cleanupReceivingAddress,
|
|
610
635
|
cleanupStalePendingOperations: () => cleanupStalePendingOperations,
|
|
611
636
|
clearPendingDeposits: () => clearPendingDeposits,
|
|
612
637
|
clearPendingWithdrawals: () => clearPendingWithdrawals,
|
|
638
|
+
cloakBridgeRail: () => cloakBridgeRail,
|
|
613
639
|
computeChainNoteHash: () => computeChainNoteHash,
|
|
614
640
|
computeExtDataHash: () => computeExtDataHash,
|
|
615
641
|
computeMerkleRoot: () => computeMerkleRoot,
|
|
@@ -626,15 +652,19 @@ __export(index_exports, {
|
|
|
626
652
|
createLogger: () => createLogger,
|
|
627
653
|
createRecoverableChangeUtxo: () => createRecoverableChangeUtxo,
|
|
628
654
|
createRecoverableDepositUtxo: () => createRecoverableDepositUtxo,
|
|
655
|
+
createRelayBatchAuthCoordinator: () => createRelayBatchAuthCoordinator,
|
|
629
656
|
createUtxo: () => createUtxo,
|
|
630
657
|
createZeroUtxo: () => createZeroUtxo,
|
|
631
658
|
decryptCompactChainNote: () => decryptCompactChainNote,
|
|
632
659
|
decryptComplianceMetadataWithMasterKey: () => decryptComplianceMetadataWithMasterKey,
|
|
633
660
|
decryptTransactionMetadata: () => decryptTransactionMetadata,
|
|
661
|
+
depositFromDerivedKey: () => depositFromDerivedKey,
|
|
662
|
+
deriveBridgeReceiver: () => deriveBridgeReceiver,
|
|
634
663
|
deriveChangeNoteBlinding: () => deriveChangeNoteBlinding,
|
|
635
664
|
deriveDepositNoteSecrets: () => deriveDepositNoteSecrets,
|
|
636
665
|
deriveDiversifiedViewingKey: () => deriveDiversifiedViewingKey,
|
|
637
666
|
deriveDiversifier: () => deriveDiversifier,
|
|
667
|
+
deriveFundingTargets: () => deriveFundingTargets,
|
|
638
668
|
deriveInputNullifierPdas: () => deriveInputNullifierPdas,
|
|
639
669
|
derivePublicKey: () => derivePublicKey,
|
|
640
670
|
deriveSpendKey: () => deriveSpendKey,
|
|
@@ -673,6 +703,7 @@ __export(index_exports, {
|
|
|
673
703
|
formatErrorForLogging: () => formatErrorForLogging,
|
|
674
704
|
formatSol: () => formatSol,
|
|
675
705
|
fullWithdraw: () => fullWithdraw,
|
|
706
|
+
fundReceiverViaPaymaster: () => fundReceiverViaPaymaster,
|
|
676
707
|
generateCloakKeys: () => generateCloakKeys,
|
|
677
708
|
generateCommitmentAsync: () => generateCommitmentAsync,
|
|
678
709
|
generateMasterSeed: () => generateMasterSeed,
|
|
@@ -708,18 +739,21 @@ __export(index_exports, {
|
|
|
708
739
|
isReactNative: () => isReactNative,
|
|
709
740
|
isRootNotFoundError: () => isRootNotFoundError,
|
|
710
741
|
isSubmissionOutcomeUnknownResponse: () => isSubmissionOutcomeUnknownResponse,
|
|
742
|
+
isTerminalFailure: () => isTerminalFailure,
|
|
711
743
|
isValidHex: () => isValidHex,
|
|
712
744
|
isValidRpcUrl: () => isValidRpcUrl,
|
|
713
745
|
isValidSolanaAddress: () => isValidSolanaAddress,
|
|
714
746
|
isWithdrawAmountSufficient: () => isWithdrawAmountSufficient,
|
|
715
747
|
isWithdrawable: () => isWithdrawable,
|
|
716
748
|
keypairToAdapter: () => keypairToAdapter,
|
|
749
|
+
listBridgeDeposits: () => listBridgeDeposits,
|
|
717
750
|
loadPendingDeposits: () => loadPendingDeposits,
|
|
718
751
|
loadPendingWithdrawals: () => loadPendingWithdrawals,
|
|
719
752
|
loadVerifiedCircuitArtifacts: () => loadVerifiedCircuitArtifacts,
|
|
720
753
|
matchChangeNote: () => matchChangeNote,
|
|
721
754
|
matchDepositNote: () => matchDepositNote,
|
|
722
755
|
matchSwapRefundLeaf: () => matchSwapRefundLeaf,
|
|
756
|
+
mayRetry: () => mayRetry,
|
|
723
757
|
openRecipientDeliveryNote: () => openRecipientDeliveryNote,
|
|
724
758
|
parseAmount: () => parseAmount,
|
|
725
759
|
parseDeliveryCarrierMemo: () => parseDeliveryCarrierMemo,
|
|
@@ -742,11 +776,15 @@ __export(index_exports, {
|
|
|
742
776
|
randomDepositNoteSalt: () => randomDepositNoteSalt,
|
|
743
777
|
randomFieldElement: () => randomFieldElement,
|
|
744
778
|
readMerkleTreeState: () => readMerkleTreeState,
|
|
779
|
+
readRent: () => readRent,
|
|
745
780
|
recipientDeliveryNoteToBase64: () => recipientDeliveryNoteToBase64,
|
|
746
781
|
registerViewingKey: () => registerViewingKey,
|
|
782
|
+
relayRequestDigestHex: () => relayRequestDigestHex,
|
|
747
783
|
removePendingDeposit: () => removePendingDeposit,
|
|
748
784
|
removePendingWithdrawal: () => removePendingWithdrawal,
|
|
785
|
+
renderAssessment: () => renderAssessment,
|
|
749
786
|
resolveCircuitsBase: () => resolveCircuitsBase,
|
|
787
|
+
retryDelayMs: () => retryDelayMs,
|
|
750
788
|
savePendingDeposit: () => savePendingDeposit,
|
|
751
789
|
savePendingWithdrawal: () => savePendingWithdrawal,
|
|
752
790
|
scanNotesForWallet: () => scanNotesForWallet,
|
|
@@ -755,10 +793,12 @@ __export(index_exports, {
|
|
|
755
793
|
sdkLogger: () => sdkLogger,
|
|
756
794
|
selectUtxos: () => selectUtxos,
|
|
757
795
|
sendTransaction: () => sendTransaction,
|
|
796
|
+
serializeAuthTransactionMessage: () => serializeAuthTransactionMessage,
|
|
758
797
|
serializeNote: () => serializeNote,
|
|
759
798
|
serializeUtxo: () => serializeUtxo,
|
|
760
799
|
setCircuitsPath: () => setCircuitsPath,
|
|
761
800
|
setDebugMode: () => setDebugMode,
|
|
801
|
+
signRelayAuthPayload: () => signRelayAuthPayload,
|
|
762
802
|
signTransaction: () => signTransaction,
|
|
763
803
|
splitTo2Limbs: () => splitTo2Limbs,
|
|
764
804
|
submitTransactToRelay: () => submitTransactToRelay,
|
|
@@ -767,6 +807,7 @@ __export(index_exports, {
|
|
|
767
807
|
swapWithChange: () => swapWithChange,
|
|
768
808
|
toComplianceReport: () => toComplianceReport,
|
|
769
809
|
transact: () => transact,
|
|
810
|
+
transactBatch: () => transactBatch,
|
|
770
811
|
transfer: () => transfer,
|
|
771
812
|
truncate: () => truncate,
|
|
772
813
|
tryDecryptNote: () => tryDecryptNote,
|
|
@@ -779,15 +820,19 @@ __export(index_exports, {
|
|
|
779
820
|
validateDepositParams: () => validateDepositParams,
|
|
780
821
|
validateNote: () => validateNote,
|
|
781
822
|
validateOutputsSum: () => validateOutputsSum,
|
|
823
|
+
validatePaymasterTopUpTransaction: () => validatePaymasterTopUpTransaction,
|
|
782
824
|
validateRoot: () => validateRoot,
|
|
783
825
|
validateTransfers: () => validateTransfers,
|
|
784
826
|
validateWalletConnected: () => validateWalletConnected,
|
|
785
827
|
validateWithdrawableNote: () => validateWithdrawableNote,
|
|
786
828
|
verifyAllCircuits: () => verifyAllCircuits,
|
|
787
829
|
verifyCircuitIntegrity: () => verifyCircuitIntegrity,
|
|
830
|
+
verifyQuoteSignature: () => verifyQuoteSignature,
|
|
788
831
|
verifyUtxos: () => verifyUtxos,
|
|
789
832
|
waitForRoot: () => waitForRoot,
|
|
790
|
-
|
|
833
|
+
withShieldRetries: () => withShieldRetries,
|
|
834
|
+
withTiming: () => withTiming,
|
|
835
|
+
withdrawFeeFor: () => withdrawFeeFor
|
|
791
836
|
});
|
|
792
837
|
module.exports = __toCommonJS(index_exports);
|
|
793
838
|
|
|
@@ -1346,9 +1391,9 @@ var LocalStorageAdapter = class {
|
|
|
1346
1391
|
};
|
|
1347
1392
|
|
|
1348
1393
|
// src/scanning/compliance-keys.ts
|
|
1349
|
-
var
|
|
1394
|
+
var import_tweetnacl3 = __toESM(require("tweetnacl"), 1);
|
|
1350
1395
|
var import_blake32 = require("@noble/hashes/blake3");
|
|
1351
|
-
var
|
|
1396
|
+
var import_sha22 = require("@noble/hashes/sha2");
|
|
1352
1397
|
init_inputs();
|
|
1353
1398
|
|
|
1354
1399
|
// src/relay/viewing-key.ts
|
|
@@ -1391,7 +1436,7 @@ function parseStrictViewingKeyChallenge(response, userPubkey, viewingKeyHex) {
|
|
|
1391
1436
|
|
|
1392
1437
|
// src/config/relay.ts
|
|
1393
1438
|
var CLOAK_PRODUCTION_RELAY_URL = "https://api.cloak.ag";
|
|
1394
|
-
var RELAY_ORIGIN_ALLOWLIST = [
|
|
1439
|
+
var RELAY_ORIGIN_ALLOWLIST = ["https://staging-api.cloak.ag"];
|
|
1395
1440
|
var ABSOLUTE_URL = /^[a-z][a-z0-9+.-]*:\/\//i;
|
|
1396
1441
|
var LOOPBACK_IPV4 = /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
|
|
1397
1442
|
var THIS_NETWORK_IPV4 = /^0\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
|
|
@@ -1536,6 +1581,276 @@ function connectionRpcEndpoint(connection) {
|
|
|
1536
1581
|
return connection?._rpcEndpoint ?? connection?.rpcEndpoint;
|
|
1537
1582
|
}
|
|
1538
1583
|
|
|
1584
|
+
// src/relay/payload.ts
|
|
1585
|
+
var import_sha2 = require("@noble/hashes/sha2");
|
|
1586
|
+
var import_utils = require("@noble/hashes/utils");
|
|
1587
|
+
var import_tweetnacl2 = __toESM(require("tweetnacl"), 1);
|
|
1588
|
+
var import_web33 = require("@solana/web3.js");
|
|
1589
|
+
init_inputs();
|
|
1590
|
+
var REQUEST_AUTH_DOMAIN = "CLOAK_RELAY_REQUEST_AUTH_V1";
|
|
1591
|
+
var REQUEST_AUTH_MAX_AGE_SECONDS = 300;
|
|
1592
|
+
var REQUEST_AUTH_MAX_FUTURE_SKEW_SECONDS = 30;
|
|
1593
|
+
var RELAY_AUTH_APPROVAL_MARGIN_SECONDS = 15;
|
|
1594
|
+
function sha256Hex(text) {
|
|
1595
|
+
return (0, import_utils.bytesToHex)((0, import_sha2.sha256)(new TextEncoder().encode(text)));
|
|
1596
|
+
}
|
|
1597
|
+
function randomNonceUuid() {
|
|
1598
|
+
const webCrypto = globalThis?.crypto;
|
|
1599
|
+
if (typeof webCrypto?.randomUUID === "function") return webCrypto.randomUUID();
|
|
1600
|
+
const proc = globalThis?.process;
|
|
1601
|
+
const nodeCrypto = typeof proc?.getBuiltinModule === "function" ? proc.getBuiltinModule("node:crypto") : void 0;
|
|
1602
|
+
if (typeof nodeCrypto?.randomUUID === "function") return nodeCrypto.randomUUID();
|
|
1603
|
+
const bytes = randomBytes(16);
|
|
1604
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
1605
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
1606
|
+
const hex = (0, import_utils.bytesToHex)(bytes);
|
|
1607
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
|
1608
|
+
}
|
|
1609
|
+
var TRANSACT_AUTH_FIELDS = [
|
|
1610
|
+
"encrypted_notes",
|
|
1611
|
+
"max_fee",
|
|
1612
|
+
"mint",
|
|
1613
|
+
"proof_bytes",
|
|
1614
|
+
"public_inputs",
|
|
1615
|
+
"recipient",
|
|
1616
|
+
"recipient_delivery_notes",
|
|
1617
|
+
"risk_quote",
|
|
1618
|
+
"sender"
|
|
1619
|
+
];
|
|
1620
|
+
var TRANSACT_SWAP_AUTH_FIELDS = [
|
|
1621
|
+
"close_timed_out",
|
|
1622
|
+
"dexes",
|
|
1623
|
+
"encrypted_notes",
|
|
1624
|
+
"exclude_dexes",
|
|
1625
|
+
"max_fee",
|
|
1626
|
+
"min_output_amount",
|
|
1627
|
+
"output_mint",
|
|
1628
|
+
"proof_bytes",
|
|
1629
|
+
"public_inputs",
|
|
1630
|
+
"recipient",
|
|
1631
|
+
"recipient_ata",
|
|
1632
|
+
"refund_blinding",
|
|
1633
|
+
"refund_pubkey",
|
|
1634
|
+
"retry_request_id",
|
|
1635
|
+
"risk_quote",
|
|
1636
|
+
"route_retry_attempts",
|
|
1637
|
+
"sender",
|
|
1638
|
+
"slippage_bps",
|
|
1639
|
+
"swap_max_retries"
|
|
1640
|
+
];
|
|
1641
|
+
var UTF8 = new TextEncoder();
|
|
1642
|
+
function compareKeysBytewise(a, b) {
|
|
1643
|
+
if (a === b) return 0;
|
|
1644
|
+
const ab = UTF8.encode(a);
|
|
1645
|
+
const bb = UTF8.encode(b);
|
|
1646
|
+
const shared = Math.min(ab.length, bb.length);
|
|
1647
|
+
for (let i = 0; i < shared; i++) {
|
|
1648
|
+
if (ab[i] !== bb[i]) return ab[i] - bb[i];
|
|
1649
|
+
}
|
|
1650
|
+
return ab.length - bb.length;
|
|
1651
|
+
}
|
|
1652
|
+
function canonicalJson(value) {
|
|
1653
|
+
if (value === null || value === void 0) return "null";
|
|
1654
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
1655
|
+
if (typeof value === "number") {
|
|
1656
|
+
if (!Number.isFinite(value) || !Number.isInteger(value) || !Number.isSafeInteger(value)) {
|
|
1657
|
+
throw new Error(
|
|
1658
|
+
`canonicalJson: refusing to sign the number ${String(value)}. The relay renders numbers with serde_json, which spells non-integer, non-finite and very large values differently from JavaScript, so the two digests would differ and the request would be rejected with an unexplained 401. Send it as a decimal string instead, which is how every amount in this schema travels.`
|
|
1659
|
+
);
|
|
1660
|
+
}
|
|
1661
|
+
return String(value);
|
|
1662
|
+
}
|
|
1663
|
+
if (typeof value === "bigint") return value.toString();
|
|
1664
|
+
if (typeof value === "string") return JSON.stringify(value);
|
|
1665
|
+
if (Array.isArray(value)) return "[" + value.map(canonicalJson).join(",") + "]";
|
|
1666
|
+
if (typeof value === "object") {
|
|
1667
|
+
const obj = value;
|
|
1668
|
+
const keys = Object.keys(obj).sort(compareKeysBytewise);
|
|
1669
|
+
return "{" + keys.map((k) => {
|
|
1670
|
+
const v = obj[k];
|
|
1671
|
+
if (typeof v === "function" || typeof v === "symbol") {
|
|
1672
|
+
throw new Error(
|
|
1673
|
+
`canonicalJson: the key "${k}" holds a ${typeof v}, which has no JSON representation. JSON.stringify would drop it from the wire body while it stayed in the signed view, so the relay's digest could never match this one.`
|
|
1674
|
+
);
|
|
1675
|
+
}
|
|
1676
|
+
return `${JSON.stringify(k)}:${canonicalJson(v)}`;
|
|
1677
|
+
}).join(",") + "}";
|
|
1678
|
+
}
|
|
1679
|
+
throw new Error(`canonicalJson: unsupported value of type ${typeof value}`);
|
|
1680
|
+
}
|
|
1681
|
+
var FIELDS_WITHOUT_NULL_ENCODING = {
|
|
1682
|
+
slippage_bps: "500 (`default_slippage_bps` in api/transact_swap.rs)"
|
|
1683
|
+
};
|
|
1684
|
+
function buildAuthRequest(body, sender, fields) {
|
|
1685
|
+
const out = {};
|
|
1686
|
+
for (const k of fields) {
|
|
1687
|
+
if (k === "sender") {
|
|
1688
|
+
out[k] = sender;
|
|
1689
|
+
continue;
|
|
1690
|
+
}
|
|
1691
|
+
const value = body[k];
|
|
1692
|
+
if (value === void 0 || value === null) {
|
|
1693
|
+
const relayDefault = FIELDS_WITHOUT_NULL_ENCODING[k];
|
|
1694
|
+
if (relayDefault) {
|
|
1695
|
+
throw new Error(
|
|
1696
|
+
`Relay request auth: \`${k}\` must be present in the body. It is the one field in this schema that is not optional on the relay side: when it is missing the relay signs its default, ${relayDefault}, while this request would sign null. The two digests differ and the relay answers 401 "Relay request signature does not match the exact request", which points at nothing. Set \`${k}\` explicitly, to the same value the body will carry.`
|
|
1697
|
+
);
|
|
1698
|
+
}
|
|
1699
|
+
out[k] = null;
|
|
1700
|
+
continue;
|
|
1701
|
+
}
|
|
1702
|
+
out[k] = value;
|
|
1703
|
+
}
|
|
1704
|
+
return out;
|
|
1705
|
+
}
|
|
1706
|
+
function buildRequestAuthMessage(endpoint, programId, issuedAt, nonce, request) {
|
|
1707
|
+
const digest = sha256Hex(canonicalJson(request));
|
|
1708
|
+
return new TextEncoder().encode(
|
|
1709
|
+
`${REQUEST_AUTH_DOMAIN}
|
|
1710
|
+
${endpoint}
|
|
1711
|
+
${programId.toBase58()}
|
|
1712
|
+
${nonce}
|
|
1713
|
+
${issuedAt}
|
|
1714
|
+
${digest}`
|
|
1715
|
+
);
|
|
1716
|
+
}
|
|
1717
|
+
function buildRelayAuthPreimage(endpoint, programId, body, sender, nowSeconds, fields = TRANSACT_AUTH_FIELDS) {
|
|
1718
|
+
const senderB58 = sender.toBase58();
|
|
1719
|
+
const issuedAt = String(nowSeconds ?? Math.floor(Date.now() / 1e3));
|
|
1720
|
+
const nonce = randomNonceUuid();
|
|
1721
|
+
const request = buildAuthRequest({ ...body, sender: senderB58 }, senderB58, fields);
|
|
1722
|
+
const message = buildRequestAuthMessage(endpoint, programId, issuedAt, nonce, request);
|
|
1723
|
+
return { sender: senderB58, auth_issued_at: issuedAt, auth_nonce: nonce, message };
|
|
1724
|
+
}
|
|
1725
|
+
function signRelayRequest(endpoint, programId, body, signer, nowSeconds, fields = TRANSACT_AUTH_FIELDS) {
|
|
1726
|
+
const preimage = buildRelayAuthPreimage(
|
|
1727
|
+
endpoint,
|
|
1728
|
+
programId,
|
|
1729
|
+
body,
|
|
1730
|
+
signer.publicKey,
|
|
1731
|
+
nowSeconds,
|
|
1732
|
+
fields
|
|
1733
|
+
);
|
|
1734
|
+
const signature = import_tweetnacl2.default.sign.detached(preimage.message, signer.secretKey);
|
|
1735
|
+
return {
|
|
1736
|
+
sender: preimage.sender,
|
|
1737
|
+
auth_issued_at: preimage.auth_issued_at,
|
|
1738
|
+
auth_nonce: preimage.auth_nonce,
|
|
1739
|
+
auth_signature: Buffer.from(signature).toString("base64")
|
|
1740
|
+
};
|
|
1741
|
+
}
|
|
1742
|
+
function buildAuthTransactionMessage(sender, digest) {
|
|
1743
|
+
if (digest.length !== 32) throw new Error(`auth transaction digest must be 32 bytes (got ${digest.length})`);
|
|
1744
|
+
const tx = new import_web33.Transaction();
|
|
1745
|
+
tx.add(import_web33.SystemProgram.transfer({ fromPubkey: sender, toPubkey: sender, lamports: 0 }));
|
|
1746
|
+
tx.feePayer = sender;
|
|
1747
|
+
tx.recentBlockhash = new import_web33.PublicKey(digest).toBase58();
|
|
1748
|
+
return tx;
|
|
1749
|
+
}
|
|
1750
|
+
function serializeAuthTransactionMessage(sender, digest) {
|
|
1751
|
+
return Uint8Array.from(buildAuthTransactionMessage(sender, digest).compileMessage().serialize());
|
|
1752
|
+
}
|
|
1753
|
+
async function signRelayAuthPayload(signer, payload) {
|
|
1754
|
+
if (signer instanceof import_web33.Keypair) {
|
|
1755
|
+
return { signature: import_tweetnacl2.default.sign.detached(payload, signer.secretKey), mode: "message" };
|
|
1756
|
+
}
|
|
1757
|
+
if (signer.signMessage) {
|
|
1758
|
+
const signature = await signer.signMessage(payload);
|
|
1759
|
+
assertDetachedSignature(signature, "signMessage");
|
|
1760
|
+
return { signature, mode: "message" };
|
|
1761
|
+
}
|
|
1762
|
+
if (signer.signAuthTransaction) {
|
|
1763
|
+
const digest = (0, import_sha2.sha256)(payload);
|
|
1764
|
+
const signed = await signer.signAuthTransaction(buildAuthTransactionMessage(signer.walletPublicKey, digest));
|
|
1765
|
+
const own = signed.signatures.find((s) => s.publicKey.equals(signer.walletPublicKey))?.signature;
|
|
1766
|
+
if (!own) {
|
|
1767
|
+
throw new Error(
|
|
1768
|
+
"The wallet returned no signature for the Cloak auth transaction. Nothing was submitted."
|
|
1769
|
+
);
|
|
1770
|
+
}
|
|
1771
|
+
const signature = Uint8Array.from(own);
|
|
1772
|
+
assertDetachedSignature(signature, "signTransaction");
|
|
1773
|
+
return { signature, mode: "transaction" };
|
|
1774
|
+
}
|
|
1775
|
+
throw new Error("Relay auth signer has neither signMessage nor signAuthTransaction.");
|
|
1776
|
+
}
|
|
1777
|
+
function assertDetachedSignature(signature, method) {
|
|
1778
|
+
if (!(signature instanceof Uint8Array) || signature.length !== 64) {
|
|
1779
|
+
throw new Error(
|
|
1780
|
+
`The wallet's ${method} did not return a 64-byte ed25519 detached signature (got ${signature instanceof Uint8Array ? `${signature.length} bytes` : typeof signature}). Cloak signs with a raw detached signature over the bytes it hands the wallet; adapters that wrap or re-encode the result cannot be used here.`
|
|
1781
|
+
);
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
function assertApprovalWithinFreshnessWindow(elapsedMs, maxAgeSeconds = REQUEST_AUTH_MAX_AGE_SECONDS) {
|
|
1785
|
+
const elapsedSeconds = elapsedMs / 1e3;
|
|
1786
|
+
const budget = maxAgeSeconds - RELAY_AUTH_APPROVAL_MARGIN_SECONDS;
|
|
1787
|
+
if (elapsedSeconds <= budget) return;
|
|
1788
|
+
throw new Error(
|
|
1789
|
+
`The wallet approval took ${Math.round(elapsedSeconds)} seconds, and Cloak requests stay valid for ${maxAgeSeconds} seconds from the moment they are signed (${RELAY_AUTH_APPROVAL_MARGIN_SECONDS} of those are held back for the request to reach the network). The timestamp is inside the signature, so it cannot be refreshed without your approval again. NOTHING WAS SUBMITTED and no funds moved. Start the same operation again and approve the prompt when it appears.`
|
|
1790
|
+
);
|
|
1791
|
+
}
|
|
1792
|
+
function explainRelayAuthRejection(responseText) {
|
|
1793
|
+
const text = String(responseText);
|
|
1794
|
+
const has = (needle) => text.includes(needle);
|
|
1795
|
+
if (has("issued too far in the future")) {
|
|
1796
|
+
return `This computer's clock is more than ${REQUEST_AUTH_MAX_FUTURE_SKEW_SECONDS} seconds ahead of the network's, so the request looks like it was signed in the future and is refused before anything else happens. Turn on automatic date and time (or resynchronise the clock) and try again. Nothing was submitted.`;
|
|
1797
|
+
}
|
|
1798
|
+
if (has("signature expired")) {
|
|
1799
|
+
return `The request was signed more than ${REQUEST_AUTH_MAX_AGE_SECONDS} seconds before it arrived, usually because a wallet approval was left waiting, or because this computer's clock is running behind. The signed timestamp cannot be refreshed on its own. Start the operation again. Nothing was submitted.`;
|
|
1800
|
+
}
|
|
1801
|
+
if (has("does not match the exact request")) {
|
|
1802
|
+
return `The request body changed after it was signed, so the relay's digest and this one disagree. Sign the exact body that is POSTed, and re-POST a retry byte for byte rather than rebuilding it. If this is a hand-built swap call, check that every field in TRANSACT_SWAP_AUTH_FIELDS is present, including \`slippage_bps\`.`;
|
|
1803
|
+
}
|
|
1804
|
+
if (has("has no registered viewing key")) {
|
|
1805
|
+
return `The signature was accepted, so this is not a signing problem: the wallet that signed has no viewing key registered with Cloak yet. Register one first, or let the SDK do it by leaving \`enforceViewingKeyRegistration\` on and supplying the viewing key.`;
|
|
1806
|
+
}
|
|
1807
|
+
if (has("Authenticated sender is required") || has("auth_issued_at is required") || has("auth_nonce is required") || has("auth_signature is required")) {
|
|
1808
|
+
return `The request reached the relay without its authentication fields. Pass \`depositorKeypair\`, or \`signMessage\` together with \`walletPublicKey\`, so the request can be signed.`;
|
|
1809
|
+
}
|
|
1810
|
+
if (has("auth_nonce must be a canonical UUID")) {
|
|
1811
|
+
return `\`auth_nonce\` must be a canonical lowercase UUID. Reuse the one from the preimage.`;
|
|
1812
|
+
}
|
|
1813
|
+
if (has("Relay request signature must be exactly 64 bytes") || has("Invalid relay request signature encoding")) {
|
|
1814
|
+
return `\`auth_signature\` must be the raw 64-byte ed25519 detached signature, base64 encoded. Wallet adapters that wrap or re-encode what \`signMessage\` returns cannot be used here.`;
|
|
1815
|
+
}
|
|
1816
|
+
return null;
|
|
1817
|
+
}
|
|
1818
|
+
async function buildRelayAuthFields(endpoint, programId, body, signers, fields = TRANSACT_AUTH_FIELDS) {
|
|
1819
|
+
if (signers.depositorKeypair) {
|
|
1820
|
+
return signRelayRequest(endpoint, programId, body, signers.depositorKeypair, void 0, fields);
|
|
1821
|
+
}
|
|
1822
|
+
const wallet = signers.relayAuthSigner;
|
|
1823
|
+
if (!wallet) return null;
|
|
1824
|
+
const preimage = buildRelayAuthPreimage(
|
|
1825
|
+
endpoint,
|
|
1826
|
+
programId,
|
|
1827
|
+
body,
|
|
1828
|
+
wallet.walletPublicKey,
|
|
1829
|
+
void 0,
|
|
1830
|
+
fields
|
|
1831
|
+
);
|
|
1832
|
+
const approvalStartedMs = Date.now();
|
|
1833
|
+
const { signature, mode } = await signRelayAuthPayload(wallet, preimage.message);
|
|
1834
|
+
assertApprovalWithinFreshnessWindow(Date.now() - approvalStartedMs);
|
|
1835
|
+
return {
|
|
1836
|
+
sender: preimage.sender,
|
|
1837
|
+
auth_issued_at: preimage.auth_issued_at,
|
|
1838
|
+
auth_nonce: preimage.auth_nonce,
|
|
1839
|
+
auth_signature: Buffer.from(signature).toString("base64"),
|
|
1840
|
+
...mode === "transaction" ? { auth_mode: mode } : {}
|
|
1841
|
+
};
|
|
1842
|
+
}
|
|
1843
|
+
var REQUEST_AUTH_BATCH_DOMAIN = "CLOAK_RELAY_BATCH_AUTH_V1";
|
|
1844
|
+
var REQUEST_AUTH_BATCH_MAX_AGE_SECONDS = 600;
|
|
1845
|
+
var RELAY_BATCH_AUTH_MAX_ITEMS = 64;
|
|
1846
|
+
function relayRequestDigestHex(preimage) {
|
|
1847
|
+
return (0, import_utils.bytesToHex)((0, import_sha2.sha256)(preimage.message));
|
|
1848
|
+
}
|
|
1849
|
+
function buildRelayBatchAuthMessage(programId, issuedAt, digests) {
|
|
1850
|
+
const lines = [REQUEST_AUTH_BATCH_DOMAIN, programId.toBase58(), issuedAt, String(digests.length)];
|
|
1851
|
+
return new TextEncoder().encode([...lines, ...digests].join("\n"));
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1539
1854
|
// src/scanning/compliance-keys.ts
|
|
1540
1855
|
var X25519_KEY_LENGTH = 32;
|
|
1541
1856
|
var DIVERSIFIER_LENGTH = 11;
|
|
@@ -1573,7 +1888,7 @@ function deriveViewingKeyFromNk(nk) {
|
|
|
1573
1888
|
preimage.set(nk, CHAIN_NOTE_VK_DOMAIN.length);
|
|
1574
1889
|
const derived = (0, import_blake32.blake3)(preimage);
|
|
1575
1890
|
const privateKey = clampX25519Secret(derived);
|
|
1576
|
-
const publicKey =
|
|
1891
|
+
const publicKey = import_tweetnacl3.default.scalarMult.base(privateKey);
|
|
1577
1892
|
return { privateKey, publicKey };
|
|
1578
1893
|
}
|
|
1579
1894
|
function deriveDiversifier(nk, commitmentHex, outputIndex) {
|
|
@@ -1608,7 +1923,7 @@ function deriveDiversifiedViewingKey(nk, diversifier) {
|
|
|
1608
1923
|
preimage.set(diversifier, SK_D_DOMAIN.length + nk.length);
|
|
1609
1924
|
const derived = (0, import_blake32.blake3)(preimage);
|
|
1610
1925
|
const privateKey = clampX25519Secret(derived);
|
|
1611
|
-
const publicKey =
|
|
1926
|
+
const publicKey = import_tweetnacl3.default.scalarMult.base(privateKey);
|
|
1612
1927
|
return { privateKey, publicKey };
|
|
1613
1928
|
}
|
|
1614
1929
|
function deriveViewingKeyFromSpendKey(skSpend) {
|
|
@@ -1671,19 +1986,19 @@ async function deriveUserCompliancePublicKey(masterCompliancePublicKey, userPubl
|
|
|
1671
1986
|
masterCompliancePublicKey,
|
|
1672
1987
|
userPublicKey
|
|
1673
1988
|
);
|
|
1674
|
-
return
|
|
1989
|
+
return import_tweetnacl3.default.scalarMult(factor, masterCompliancePublicKey);
|
|
1675
1990
|
}
|
|
1676
|
-
function
|
|
1991
|
+
function bytesToHex3(bytes) {
|
|
1677
1992
|
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
1678
1993
|
}
|
|
1679
1994
|
function computeViewingKeyIdentifier(userPubkey, nkHex) {
|
|
1680
1995
|
const preimage = new TextEncoder().encode(`${userPubkey.toBase58()}${nkHex}`);
|
|
1681
|
-
return
|
|
1996
|
+
return bytesToHex3((0, import_sha22.sha256)(preimage));
|
|
1682
1997
|
}
|
|
1683
1998
|
var SIGN_IN_MESSAGE = "Cloak: Sign in\n\nSign this message to securely access your Cloak account.\nThis does NOT authorize any transaction or spend any funds.";
|
|
1684
1999
|
async function registerViewingKey(relayUrl, userPubkey, nk, signMessage) {
|
|
1685
2000
|
assert32Bytes(nk, "nk");
|
|
1686
|
-
const nkHex =
|
|
2001
|
+
const nkHex = bytesToHex3(nk);
|
|
1687
2002
|
const keyId = computeViewingKeyIdentifier(userPubkey, nkHex);
|
|
1688
2003
|
const challengeResponse = await relayFetch(`${relayUrl}/viewing-key/challenge`, {
|
|
1689
2004
|
method: "POST",
|
|
@@ -1704,8 +2019,11 @@ async function registerViewingKey(relayUrl, userPubkey, nk, signMessage) {
|
|
|
1704
2019
|
nkHex
|
|
1705
2020
|
);
|
|
1706
2021
|
const messageBytes = new TextEncoder().encode(challenge.message);
|
|
1707
|
-
const
|
|
1708
|
-
|
|
2022
|
+
const signed = await signRelayAuthPayload(
|
|
2023
|
+
typeof signMessage === "function" ? { walletPublicKey: userPubkey, signMessage } : signMessage,
|
|
2024
|
+
messageBytes
|
|
2025
|
+
);
|
|
2026
|
+
const signature = Buffer.from(signed.signature).toString("base64");
|
|
1709
2027
|
const response = await relayFetch(`${relayUrl}/viewing-key/register`, {
|
|
1710
2028
|
method: "POST",
|
|
1711
2029
|
headers: { "Content-Type": "application/json" },
|
|
@@ -1714,7 +2032,8 @@ async function registerViewingKey(relayUrl, userPubkey, nk, signMessage) {
|
|
|
1714
2032
|
viewing_key: nkHex,
|
|
1715
2033
|
nonce: challenge.nonce,
|
|
1716
2034
|
identifier: keyId,
|
|
1717
|
-
signature
|
|
2035
|
+
signature,
|
|
2036
|
+
...signed.mode === "transaction" ? { auth_mode: signed.mode } : {}
|
|
1718
2037
|
})
|
|
1719
2038
|
});
|
|
1720
2039
|
if (!response.ok) {
|
|
@@ -1723,7 +2042,7 @@ async function registerViewingKey(relayUrl, userPubkey, nk, signMessage) {
|
|
|
1723
2042
|
}
|
|
1724
2043
|
|
|
1725
2044
|
// src/scanning/metadata-encryption.ts
|
|
1726
|
-
var
|
|
2045
|
+
var import_tweetnacl4 = __toESM(require("tweetnacl"), 1);
|
|
1727
2046
|
init_inputs();
|
|
1728
2047
|
var AES_GCM_NONCE_LENGTH = 12;
|
|
1729
2048
|
var AES_GCM_TAG_LENGTH = 16;
|
|
@@ -1821,8 +2140,8 @@ async function aesGcmDecrypt(ciphertext, keyBytes, nonce) {
|
|
|
1821
2140
|
}
|
|
1822
2141
|
async function encryptForRecipient(plaintext, recipientPublicKey) {
|
|
1823
2142
|
assert32Bytes2(recipientPublicKey, "recipientPublicKey");
|
|
1824
|
-
const ephemeral =
|
|
1825
|
-
const sharedSecret =
|
|
2143
|
+
const ephemeral = import_tweetnacl4.default.box.keyPair();
|
|
2144
|
+
const sharedSecret = import_tweetnacl4.default.scalarMult(ephemeral.secretKey, recipientPublicKey);
|
|
1826
2145
|
const nonce = randomBytes(AES_GCM_NONCE_LENGTH);
|
|
1827
2146
|
const ciphertext = await aesGcmEncrypt(plaintext, sharedSecret, nonce);
|
|
1828
2147
|
return {
|
|
@@ -1881,7 +2200,7 @@ async function encryptTransactionMetadataBundle(metadata, viewingKeyPrivate, use
|
|
|
1881
2200
|
const plaintext = new TextEncoder().encode(JSON.stringify(metadata));
|
|
1882
2201
|
const basePoint = new Uint8Array(32);
|
|
1883
2202
|
basePoint[0] = 9;
|
|
1884
|
-
const viewingKeyPublic =
|
|
2203
|
+
const viewingKeyPublic = import_tweetnacl4.default.scalarMult(viewingKeyPrivate, basePoint);
|
|
1885
2204
|
const [userPayload, compliancePayload] = await Promise.all([
|
|
1886
2205
|
encryptForRecipient(plaintext, viewingKeyPublic),
|
|
1887
2206
|
encryptForRecipient(plaintext, viewingKeyPublic)
|
|
@@ -1900,7 +2219,7 @@ async function decryptTransactionMetadata(encrypted, viewKeySecret) {
|
|
|
1900
2219
|
const payload = decodePayload(encrypted);
|
|
1901
2220
|
const ephemeralPk = hexToBytes(payload.ephemeral_pk);
|
|
1902
2221
|
assert32Bytes2(ephemeralPk, "ephemeral public key");
|
|
1903
|
-
const sharedSecret =
|
|
2222
|
+
const sharedSecret = import_tweetnacl4.default.scalarMult(viewKeySecret, ephemeralPk);
|
|
1904
2223
|
return decryptWithSharedSecret(payload, sharedSecret);
|
|
1905
2224
|
}
|
|
1906
2225
|
async function decryptComplianceMetadataWithMasterKey(encrypted, masterCompliancePrivateKey, masterCompliancePublicKey, userPublicKey) {
|
|
@@ -1913,8 +2232,8 @@ async function decryptComplianceMetadataWithMasterKey(encrypted, masterComplianc
|
|
|
1913
2232
|
masterCompliancePublicKey,
|
|
1914
2233
|
userPublicKey
|
|
1915
2234
|
);
|
|
1916
|
-
const scaledEphemeral =
|
|
1917
|
-
const sharedSecret =
|
|
2235
|
+
const scaledEphemeral = import_tweetnacl4.default.scalarMult(userFactor, ephemeralPk);
|
|
2236
|
+
const sharedSecret = import_tweetnacl4.default.scalarMult(masterCompliancePrivateKey, scaledEphemeral);
|
|
1918
2237
|
return decryptWithSharedSecret(payload, sharedSecret);
|
|
1919
2238
|
}
|
|
1920
2239
|
|
|
@@ -2851,23 +3170,23 @@ function formatErrorForLogging(error) {
|
|
|
2851
3170
|
init_utxo();
|
|
2852
3171
|
|
|
2853
3172
|
// src/program/pda.ts
|
|
2854
|
-
var
|
|
3173
|
+
var import_web35 = require("@solana/web3.js");
|
|
2855
3174
|
init_utxo();
|
|
2856
3175
|
function getShieldPoolPDAs(programId, mint = NATIVE_SOL_MINT) {
|
|
2857
3176
|
const pid = programId || CLOAK_PROGRAM_ID;
|
|
2858
|
-
const [pool] =
|
|
3177
|
+
const [pool] = import_web35.PublicKey.findProgramAddressSync(
|
|
2859
3178
|
[Buffer.from("pool"), mint.toBuffer()],
|
|
2860
3179
|
pid
|
|
2861
3180
|
);
|
|
2862
|
-
const [merkleTree] =
|
|
3181
|
+
const [merkleTree] = import_web35.PublicKey.findProgramAddressSync(
|
|
2863
3182
|
[Buffer.from("merkle_tree"), mint.toBuffer()],
|
|
2864
3183
|
pid
|
|
2865
3184
|
);
|
|
2866
|
-
const [treasury] =
|
|
3185
|
+
const [treasury] = import_web35.PublicKey.findProgramAddressSync(
|
|
2867
3186
|
[Buffer.from("treasury"), mint.toBuffer()],
|
|
2868
3187
|
pid
|
|
2869
3188
|
);
|
|
2870
|
-
const [vaultAuthority] =
|
|
3189
|
+
const [vaultAuthority] = import_web35.PublicKey.findProgramAddressSync(
|
|
2871
3190
|
[Buffer.from("vault_authority"), mint.toBuffer()],
|
|
2872
3191
|
pid
|
|
2873
3192
|
);
|
|
@@ -2883,7 +3202,7 @@ function getNullifierPDA(poolPubkey, nullifier, programId) {
|
|
|
2883
3202
|
if (nullifier.length !== 32) {
|
|
2884
3203
|
throw new Error(`Nullifier must be 32 bytes, got ${nullifier.length}`);
|
|
2885
3204
|
}
|
|
2886
|
-
return
|
|
3205
|
+
return import_web35.PublicKey.findProgramAddressSync(
|
|
2887
3206
|
[Buffer.from("nullifier"), poolPubkey.toBuffer(), Buffer.from(nullifier)],
|
|
2888
3207
|
pid
|
|
2889
3208
|
);
|
|
@@ -2893,25 +3212,25 @@ function getSwapStatePDA(poolPubkey, nullifier, programId) {
|
|
|
2893
3212
|
if (nullifier.length !== 32) {
|
|
2894
3213
|
throw new Error(`Nullifier must be 32 bytes, got ${nullifier.length}`);
|
|
2895
3214
|
}
|
|
2896
|
-
return
|
|
3215
|
+
return import_web35.PublicKey.findProgramAddressSync(
|
|
2897
3216
|
[Buffer.from("swap_state"), poolPubkey.toBuffer(), Buffer.from(nullifier)],
|
|
2898
3217
|
pid
|
|
2899
3218
|
);
|
|
2900
3219
|
}
|
|
2901
3220
|
function getPoolAuthorityConfigPDA(mint = NATIVE_SOL_MINT, programId) {
|
|
2902
3221
|
const pid = programId || CLOAK_PROGRAM_ID;
|
|
2903
|
-
return
|
|
3222
|
+
return import_web35.PublicKey.findProgramAddressSync(
|
|
2904
3223
|
[Buffer.from("pool_authority"), mint.toBuffer()],
|
|
2905
3224
|
pid
|
|
2906
3225
|
);
|
|
2907
3226
|
}
|
|
2908
3227
|
function getDeliveryRegistryPDA(programId) {
|
|
2909
3228
|
const pid = programId || CLOAK_PROGRAM_ID;
|
|
2910
|
-
return
|
|
3229
|
+
return import_web35.PublicKey.findProgramAddressSync([Buffer.from("cloak_delivery_registry")], pid)[0];
|
|
2911
3230
|
}
|
|
2912
3231
|
function getChainNoteRegistryPDA(programId) {
|
|
2913
3232
|
const pid = programId || CLOAK_PROGRAM_ID;
|
|
2914
|
-
return
|
|
3233
|
+
return import_web35.PublicKey.findProgramAddressSync([Buffer.from("cloak_chain_note_registry")], pid)[0];
|
|
2915
3234
|
}
|
|
2916
3235
|
|
|
2917
3236
|
// src/flows/verify-utxos.ts
|
|
@@ -2929,8 +3248,8 @@ async function verifyUtxos(utxos, connection, programId, commitment = "confirmed
|
|
|
2929
3248
|
const nullifierHex = nullifierBig.toString(16).padStart(64, "0");
|
|
2930
3249
|
const nullifierBytes = Buffer.from(nullifierHex, "hex");
|
|
2931
3250
|
const { pool } = getShieldPoolPDAs(programId, utxo.mintAddress);
|
|
2932
|
-
const [
|
|
2933
|
-
checkable.push({ utxo, pda });
|
|
3251
|
+
const [pda2] = getNullifierPDA(pool, nullifierBytes, programId);
|
|
3252
|
+
checkable.push({ utxo, pda: pda2 });
|
|
2934
3253
|
} catch {
|
|
2935
3254
|
skipped.push(utxo);
|
|
2936
3255
|
}
|
|
@@ -3000,8 +3319,8 @@ function deriveInputNullifierPdas(programId, mint, inputNullifiers) {
|
|
|
3000
3319
|
const pdas = [];
|
|
3001
3320
|
for (const nullifier of inputNullifiers) {
|
|
3002
3321
|
if (nullifier === BigInt(0)) continue;
|
|
3003
|
-
const [
|
|
3004
|
-
pdas.push(
|
|
3322
|
+
const [pda2] = getNullifierPDA(pool, bigintToBytes324(nullifier), programId);
|
|
3323
|
+
pdas.push(pda2);
|
|
3005
3324
|
}
|
|
3006
3325
|
return pdas;
|
|
3007
3326
|
}
|
|
@@ -3242,8 +3561,9 @@ function truncate(str, len = 20) {
|
|
|
3242
3561
|
var sdkLogger = createLogger("cloak::sdk");
|
|
3243
3562
|
|
|
3244
3563
|
// src/relay/relay-service.ts
|
|
3245
|
-
var
|
|
3564
|
+
var import_sha23 = require("@noble/hashes/sha2");
|
|
3246
3565
|
init_inputs();
|
|
3566
|
+
var import_web36 = require("@solana/web3.js");
|
|
3247
3567
|
var RelayService = class {
|
|
3248
3568
|
/**
|
|
3249
3569
|
* Create a new Relay Service client
|
|
@@ -3559,8 +3879,11 @@ var RelayService = class {
|
|
|
3559
3879
|
userPubkey,
|
|
3560
3880
|
viewingKey
|
|
3561
3881
|
);
|
|
3562
|
-
const
|
|
3563
|
-
|
|
3882
|
+
const signed = await signRelayAuthPayload(
|
|
3883
|
+
typeof signMessage === "function" ? { walletPublicKey: new import_web36.PublicKey(userPubkey), signMessage } : signMessage,
|
|
3884
|
+
new TextEncoder().encode(challenge.message)
|
|
3885
|
+
);
|
|
3886
|
+
const signature = this.bytesToBase64(signed.signature);
|
|
3564
3887
|
const response = await relayFetch(`${this.baseUrl}/viewing-key/register`, {
|
|
3565
3888
|
method: "POST",
|
|
3566
3889
|
headers: { "Content-Type": "application/json" },
|
|
@@ -3569,7 +3892,8 @@ var RelayService = class {
|
|
|
3569
3892
|
viewing_key: viewingKey,
|
|
3570
3893
|
nonce: challenge.nonce,
|
|
3571
3894
|
identifier,
|
|
3572
|
-
signature
|
|
3895
|
+
signature,
|
|
3896
|
+
...signed.mode === "transaction" ? { auth_mode: signed.mode } : {}
|
|
3573
3897
|
})
|
|
3574
3898
|
});
|
|
3575
3899
|
if (!response.ok) {
|
|
@@ -3607,7 +3931,7 @@ var RelayService = class {
|
|
|
3607
3931
|
}
|
|
3608
3932
|
computeViewingKeyIdentifier(userPubkey, viewingKeyHex) {
|
|
3609
3933
|
const preimage = new TextEncoder().encode(`${userPubkey}${viewingKeyHex}`);
|
|
3610
|
-
const hash = (0,
|
|
3934
|
+
const hash = (0, import_sha23.sha256)(preimage);
|
|
3611
3935
|
return Array.from(hash).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
3612
3936
|
}
|
|
3613
3937
|
};
|
|
@@ -3725,10 +4049,10 @@ function encodeNoteSimple(note) {
|
|
|
3725
4049
|
}
|
|
3726
4050
|
|
|
3727
4051
|
// src/wallet/adapter.ts
|
|
3728
|
-
var
|
|
4052
|
+
var import_web37 = require("@solana/web3.js");
|
|
3729
4053
|
init_types();
|
|
3730
4054
|
function validateWalletConnected(wallet) {
|
|
3731
|
-
if (wallet instanceof
|
|
4055
|
+
if (wallet instanceof import_web37.Keypair) {
|
|
3732
4056
|
return;
|
|
3733
4057
|
}
|
|
3734
4058
|
if (!wallet.publicKey) {
|
|
@@ -3740,7 +4064,7 @@ function validateWalletConnected(wallet) {
|
|
|
3740
4064
|
}
|
|
3741
4065
|
}
|
|
3742
4066
|
function getPublicKey(wallet) {
|
|
3743
|
-
if (wallet instanceof
|
|
4067
|
+
if (wallet instanceof import_web37.Keypair) {
|
|
3744
4068
|
return wallet.publicKey;
|
|
3745
4069
|
}
|
|
3746
4070
|
if (!wallet.publicKey) {
|
|
@@ -3754,7 +4078,7 @@ function getPublicKey(wallet) {
|
|
|
3754
4078
|
}
|
|
3755
4079
|
async function sendTransaction(transaction, wallet, connection, options) {
|
|
3756
4080
|
assertAllowedRpcConnection(connection);
|
|
3757
|
-
if (wallet instanceof
|
|
4081
|
+
if (wallet instanceof import_web37.Keypair) {
|
|
3758
4082
|
return await connection.sendTransaction(transaction, [wallet], options);
|
|
3759
4083
|
}
|
|
3760
4084
|
if (wallet.sendTransaction) {
|
|
@@ -3771,7 +4095,7 @@ async function sendTransaction(transaction, wallet, connection, options) {
|
|
|
3771
4095
|
}
|
|
3772
4096
|
}
|
|
3773
4097
|
async function signTransaction(transaction, wallet) {
|
|
3774
|
-
if (wallet instanceof
|
|
4098
|
+
if (wallet instanceof import_web37.Keypair) {
|
|
3775
4099
|
transaction.sign(wallet);
|
|
3776
4100
|
return transaction;
|
|
3777
4101
|
}
|
|
@@ -3799,7 +4123,7 @@ function keypairToAdapter(keypair) {
|
|
|
3799
4123
|
}
|
|
3800
4124
|
|
|
3801
4125
|
// src/program/instructions.ts
|
|
3802
|
-
var
|
|
4126
|
+
var import_web38 = require("@solana/web3.js");
|
|
3803
4127
|
function createDepositInstruction(params) {
|
|
3804
4128
|
if (params.commitment.length !== 32) {
|
|
3805
4129
|
throw new Error(
|
|
@@ -3821,7 +4145,7 @@ function createDepositInstruction(params) {
|
|
|
3821
4145
|
data.set(discriminant, 0);
|
|
3822
4146
|
data.set(amountBytes, 1);
|
|
3823
4147
|
data.set(params.commitment, 9);
|
|
3824
|
-
return new
|
|
4148
|
+
return new import_web38.TransactionInstruction({
|
|
3825
4149
|
programId: params.programId,
|
|
3826
4150
|
keys: [
|
|
3827
4151
|
// Account 0: Payer (signer, writable) - pays for transaction
|
|
@@ -3829,7 +4153,7 @@ function createDepositInstruction(params) {
|
|
|
3829
4153
|
// Account 1: Pool (writable) - receives SOL
|
|
3830
4154
|
{ pubkey: params.pool, isSigner: false, isWritable: true },
|
|
3831
4155
|
// Account 2: System Program (readonly) - for transfers
|
|
3832
|
-
{ pubkey:
|
|
4156
|
+
{ pubkey: import_web38.SystemProgram.programId, isSigner: false, isWritable: false },
|
|
3833
4157
|
// Account 3: Merkle Tree (writable) - stores on-chain Merkle tree
|
|
3834
4158
|
{ pubkey: params.merkleTree, isSigner: false, isWritable: true }
|
|
3835
4159
|
],
|
|
@@ -3837,16 +4161,16 @@ function createDepositInstruction(params) {
|
|
|
3837
4161
|
});
|
|
3838
4162
|
}
|
|
3839
4163
|
function validateDepositParams(params) {
|
|
3840
|
-
if (!(params.programId instanceof
|
|
4164
|
+
if (!(params.programId instanceof import_web38.PublicKey)) {
|
|
3841
4165
|
throw new Error("programId must be a PublicKey");
|
|
3842
4166
|
}
|
|
3843
|
-
if (!(params.payer instanceof
|
|
4167
|
+
if (!(params.payer instanceof import_web38.PublicKey)) {
|
|
3844
4168
|
throw new Error("payer must be a PublicKey");
|
|
3845
4169
|
}
|
|
3846
|
-
if (!(params.pool instanceof
|
|
4170
|
+
if (!(params.pool instanceof import_web38.PublicKey)) {
|
|
3847
4171
|
throw new Error("pool must be a PublicKey");
|
|
3848
4172
|
}
|
|
3849
|
-
if (!(params.merkleTree instanceof
|
|
4173
|
+
if (!(params.merkleTree instanceof import_web38.PublicKey)) {
|
|
3850
4174
|
throw new Error("merkleTree must be a PublicKey");
|
|
3851
4175
|
}
|
|
3852
4176
|
if (typeof params.amount !== "number" || params.amount <= 0) {
|
|
@@ -4187,7 +4511,7 @@ async function buildMerkleTree(commitments, height = MERKLE_TREE_HEIGHT2) {
|
|
|
4187
4511
|
}
|
|
4188
4512
|
|
|
4189
4513
|
// src/relay/client.ts
|
|
4190
|
-
var
|
|
4514
|
+
var import_web39 = require("@solana/web3.js");
|
|
4191
4515
|
var import_bs58 = __toESM(require("bs58"), 1);
|
|
4192
4516
|
|
|
4193
4517
|
// src/notes/refund-leaf.ts
|
|
@@ -4304,7 +4628,7 @@ var CLOSE_SWAP_STATE_TAG = 13;
|
|
|
4304
4628
|
var CLOSE_SWAP_STATE_PHASE1_PAYLOAD_LEN = 40;
|
|
4305
4629
|
var CLOSE_SWAP_STATE_PHASE1_WIRE_LEN = 1 + CLOSE_SWAP_STATE_PHASE1_PAYLOAD_LEN;
|
|
4306
4630
|
var CLOSE_SWAP_STATE_COMMITMENT_END = 1 + 32;
|
|
4307
|
-
var NATIVE_SOL_MINT2 = new
|
|
4631
|
+
var NATIVE_SOL_MINT2 = new import_web39.PublicKey("So11111111111111111111111111111111111111112");
|
|
4308
4632
|
var PROOF_LEN = 256;
|
|
4309
4633
|
var PUBLIC_INPUTS_LEN = 264;
|
|
4310
4634
|
var COMMITMENTS_OFFSET = 168;
|
|
@@ -4320,7 +4644,7 @@ async function fetchWithRetry(url, options = {}) {
|
|
|
4320
4644
|
const {
|
|
4321
4645
|
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
4322
4646
|
maxRetries = DEFAULT_MAX_RETRIES,
|
|
4323
|
-
retryDelayMs = 1e3
|
|
4647
|
+
retryDelayMs: retryDelayMs2 = 1e3
|
|
4324
4648
|
} = options;
|
|
4325
4649
|
assertAllowedRelayOrigin(url);
|
|
4326
4650
|
let lastError = null;
|
|
@@ -4344,7 +4668,7 @@ async function fetchWithRetry(url, options = {}) {
|
|
|
4344
4668
|
`Relay request failed after ${attempt + 1} attempts: ${lastError.message}`
|
|
4345
4669
|
);
|
|
4346
4670
|
}
|
|
4347
|
-
const delay =
|
|
4671
|
+
const delay = retryDelayMs2 * Math.pow(2, attempt) + Math.random() * 500;
|
|
4348
4672
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
4349
4673
|
}
|
|
4350
4674
|
}
|
|
@@ -4593,7 +4917,7 @@ async function preflightCheck(relayUrl, rootHex) {
|
|
|
4593
4917
|
}
|
|
4594
4918
|
|
|
4595
4919
|
// src/flows/transact.ts
|
|
4596
|
-
var
|
|
4920
|
+
var import_web311 = require("@solana/web3.js");
|
|
4597
4921
|
var import_spl_token = require("@solana/spl-token");
|
|
4598
4922
|
var snarkjs = __toESM(require("snarkjs"), 1);
|
|
4599
4923
|
init_utxo();
|
|
@@ -4821,7 +5145,7 @@ function chainNoteFromBase64(base64) {
|
|
|
4821
5145
|
}
|
|
4822
5146
|
|
|
4823
5147
|
// src/notes/delivery-note.ts
|
|
4824
|
-
var
|
|
5148
|
+
var import_tweetnacl5 = __toESM(require("tweetnacl"), 1);
|
|
4825
5149
|
var RECIPIENT_DELIVERY_EPHEMERAL_PK_LEN = 32;
|
|
4826
5150
|
var RECIPIENT_DELIVERY_NONCE_LEN = 24;
|
|
4827
5151
|
var RECIPIENT_DELIVERY_PLAINTEXT_LEN = 40;
|
|
@@ -4872,10 +5196,10 @@ function encodeRecipientDeliveryNote(note, recipientViewingPublicKey) {
|
|
|
4872
5196
|
const plaintext = new Uint8Array(RECIPIENT_DELIVERY_PLAINTEXT_LEN);
|
|
4873
5197
|
writeU64LE2(plaintext, 0, note.amount);
|
|
4874
5198
|
writeU256BE2(plaintext, 8, note.blinding);
|
|
4875
|
-
const ephemeral =
|
|
4876
|
-
const shared =
|
|
4877
|
-
const nonce =
|
|
4878
|
-
const ciphertext =
|
|
5199
|
+
const ephemeral = import_tweetnacl5.default.box.keyPair();
|
|
5200
|
+
const shared = import_tweetnacl5.default.box.before(recipientViewingPublicKey, ephemeral.secretKey);
|
|
5201
|
+
const nonce = import_tweetnacl5.default.randomBytes(import_tweetnacl5.default.secretbox.nonceLength);
|
|
5202
|
+
const ciphertext = import_tweetnacl5.default.secretbox(plaintext, nonce, shared);
|
|
4879
5203
|
if (ciphertext.length !== RECIPIENT_DELIVERY_CIPHERTEXT_LEN) {
|
|
4880
5204
|
throw new Error(
|
|
4881
5205
|
`delivery ciphertext must be ${RECIPIENT_DELIVERY_CIPHERTEXT_LEN} bytes, got ${ciphertext.length}`
|
|
@@ -4901,8 +5225,8 @@ function openRecipientDeliveryNote(envelope, viewingSecretKey) {
|
|
|
4901
5225
|
const ciphertext = envelope.slice(
|
|
4902
5226
|
RECIPIENT_DELIVERY_EPHEMERAL_PK_LEN + RECIPIENT_DELIVERY_NONCE_LEN
|
|
4903
5227
|
);
|
|
4904
|
-
const shared =
|
|
4905
|
-
const plaintext =
|
|
5228
|
+
const shared = import_tweetnacl5.default.box.before(ephemeralPk, viewingSecretKey);
|
|
5229
|
+
const plaintext = import_tweetnacl5.default.secretbox.open(ciphertext, nonce, shared);
|
|
4906
5230
|
if (!plaintext || plaintext.length !== RECIPIENT_DELIVERY_PLAINTEXT_LEN) return null;
|
|
4907
5231
|
return { amount: readU64LE2(plaintext, 0), blinding: readU256BE2(plaintext, 8) };
|
|
4908
5232
|
} catch {
|
|
@@ -4960,7 +5284,7 @@ function parseDeliveryCarrierMemo(data) {
|
|
|
4960
5284
|
|
|
4961
5285
|
// src/notes/swap-refund.ts
|
|
4962
5286
|
var import_blake34 = require("@noble/hashes/blake3");
|
|
4963
|
-
var
|
|
5287
|
+
var import_web310 = require("@solana/web3.js");
|
|
4964
5288
|
var import_bs582 = __toESM(require("bs58"), 1);
|
|
4965
5289
|
init_utxo();
|
|
4966
5290
|
init_inputs();
|
|
@@ -5107,7 +5431,7 @@ async function discoverSwapRefunds(connection, programId, viewingKeyNk, options
|
|
|
5107
5431
|
const { limit = 0, untilSignature, batchSize = 50, onStatus } = options;
|
|
5108
5432
|
const poolMint = options.poolMint ?? NATIVE_SOL_MINT;
|
|
5109
5433
|
const programIdBase58 = programId.toBase58();
|
|
5110
|
-
const [pool] =
|
|
5434
|
+
const [pool] = import_web310.PublicKey.findProgramAddressSync(
|
|
5111
5435
|
[Buffer.from("pool"), poolMint.toBuffer()],
|
|
5112
5436
|
programId
|
|
5113
5437
|
);
|
|
@@ -5158,7 +5482,7 @@ async function discoverSwapRefunds(connection, programId, viewingKeyNk, options
|
|
|
5158
5482
|
if (data[0] !== TRANSACT_SWAP_TAG2 || data.length < MIN_TRANSACT_SWAP_LEN) continue;
|
|
5159
5483
|
const nullifier = data.slice(NULLIFIER_0_OFFSET, NULLIFIER_0_OFFSET + 32);
|
|
5160
5484
|
if (nullifier.length !== 32) continue;
|
|
5161
|
-
const [swapState] =
|
|
5485
|
+
const [swapState] = import_web310.PublicKey.findProgramAddressSync(
|
|
5162
5486
|
[Buffer.from("swap_state"), pool.toBuffer(), Buffer.from(nullifier)],
|
|
5163
5487
|
programId
|
|
5164
5488
|
);
|
|
@@ -5388,229 +5712,6 @@ async function matchChangeNote(params) {
|
|
|
5388
5712
|
// src/flows/transact.ts
|
|
5389
5713
|
var import_circomlibjs4 = require("circomlibjs");
|
|
5390
5714
|
var import_tweetnacl6 = __toESM(require("tweetnacl"), 1);
|
|
5391
|
-
|
|
5392
|
-
// src/relay/payload.ts
|
|
5393
|
-
var import_sha23 = require("@noble/hashes/sha2");
|
|
5394
|
-
var import_utils = require("@noble/hashes/utils");
|
|
5395
|
-
var import_tweetnacl5 = __toESM(require("tweetnacl"), 1);
|
|
5396
|
-
init_inputs();
|
|
5397
|
-
var REQUEST_AUTH_DOMAIN = "CLOAK_RELAY_REQUEST_AUTH_V1";
|
|
5398
|
-
var REQUEST_AUTH_MAX_AGE_SECONDS = 300;
|
|
5399
|
-
var REQUEST_AUTH_MAX_FUTURE_SKEW_SECONDS = 30;
|
|
5400
|
-
var RELAY_AUTH_APPROVAL_MARGIN_SECONDS = 15;
|
|
5401
|
-
function sha256Hex(text) {
|
|
5402
|
-
return (0, import_utils.bytesToHex)((0, import_sha23.sha256)(new TextEncoder().encode(text)));
|
|
5403
|
-
}
|
|
5404
|
-
function randomNonceUuid() {
|
|
5405
|
-
const webCrypto = globalThis?.crypto;
|
|
5406
|
-
if (typeof webCrypto?.randomUUID === "function") return webCrypto.randomUUID();
|
|
5407
|
-
const proc = globalThis?.process;
|
|
5408
|
-
const nodeCrypto = typeof proc?.getBuiltinModule === "function" ? proc.getBuiltinModule("node:crypto") : void 0;
|
|
5409
|
-
if (typeof nodeCrypto?.randomUUID === "function") return nodeCrypto.randomUUID();
|
|
5410
|
-
const bytes = randomBytes(16);
|
|
5411
|
-
bytes[6] = bytes[6] & 15 | 64;
|
|
5412
|
-
bytes[8] = bytes[8] & 63 | 128;
|
|
5413
|
-
const hex = (0, import_utils.bytesToHex)(bytes);
|
|
5414
|
-
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
|
5415
|
-
}
|
|
5416
|
-
var TRANSACT_AUTH_FIELDS = [
|
|
5417
|
-
"encrypted_notes",
|
|
5418
|
-
"max_fee",
|
|
5419
|
-
"mint",
|
|
5420
|
-
"proof_bytes",
|
|
5421
|
-
"public_inputs",
|
|
5422
|
-
"recipient",
|
|
5423
|
-
"recipient_delivery_notes",
|
|
5424
|
-
"risk_quote",
|
|
5425
|
-
"sender"
|
|
5426
|
-
];
|
|
5427
|
-
var TRANSACT_SWAP_AUTH_FIELDS = [
|
|
5428
|
-
"close_timed_out",
|
|
5429
|
-
"dexes",
|
|
5430
|
-
"encrypted_notes",
|
|
5431
|
-
"exclude_dexes",
|
|
5432
|
-
"max_fee",
|
|
5433
|
-
"min_output_amount",
|
|
5434
|
-
"output_mint",
|
|
5435
|
-
"proof_bytes",
|
|
5436
|
-
"public_inputs",
|
|
5437
|
-
"recipient",
|
|
5438
|
-
"recipient_ata",
|
|
5439
|
-
"refund_blinding",
|
|
5440
|
-
"refund_pubkey",
|
|
5441
|
-
"retry_request_id",
|
|
5442
|
-
"risk_quote",
|
|
5443
|
-
"route_retry_attempts",
|
|
5444
|
-
"sender",
|
|
5445
|
-
"slippage_bps",
|
|
5446
|
-
"swap_max_retries"
|
|
5447
|
-
];
|
|
5448
|
-
var UTF8 = new TextEncoder();
|
|
5449
|
-
function compareKeysBytewise(a, b) {
|
|
5450
|
-
if (a === b) return 0;
|
|
5451
|
-
const ab = UTF8.encode(a);
|
|
5452
|
-
const bb = UTF8.encode(b);
|
|
5453
|
-
const shared = Math.min(ab.length, bb.length);
|
|
5454
|
-
for (let i = 0; i < shared; i++) {
|
|
5455
|
-
if (ab[i] !== bb[i]) return ab[i] - bb[i];
|
|
5456
|
-
}
|
|
5457
|
-
return ab.length - bb.length;
|
|
5458
|
-
}
|
|
5459
|
-
function canonicalJson(value) {
|
|
5460
|
-
if (value === null || value === void 0) return "null";
|
|
5461
|
-
if (typeof value === "boolean") return value ? "true" : "false";
|
|
5462
|
-
if (typeof value === "number") {
|
|
5463
|
-
if (!Number.isFinite(value) || !Number.isInteger(value) || !Number.isSafeInteger(value)) {
|
|
5464
|
-
throw new Error(
|
|
5465
|
-
`canonicalJson: refusing to sign the number ${String(value)}. The relay renders numbers with serde_json, which spells non-integer, non-finite and very large values differently from JavaScript, so the two digests would differ and the request would be rejected with an unexplained 401. Send it as a decimal string instead, which is how every amount in this schema travels.`
|
|
5466
|
-
);
|
|
5467
|
-
}
|
|
5468
|
-
return String(value);
|
|
5469
|
-
}
|
|
5470
|
-
if (typeof value === "bigint") return value.toString();
|
|
5471
|
-
if (typeof value === "string") return JSON.stringify(value);
|
|
5472
|
-
if (Array.isArray(value)) return "[" + value.map(canonicalJson).join(",") + "]";
|
|
5473
|
-
if (typeof value === "object") {
|
|
5474
|
-
const obj = value;
|
|
5475
|
-
const keys = Object.keys(obj).sort(compareKeysBytewise);
|
|
5476
|
-
return "{" + keys.map((k) => {
|
|
5477
|
-
const v = obj[k];
|
|
5478
|
-
if (typeof v === "function" || typeof v === "symbol") {
|
|
5479
|
-
throw new Error(
|
|
5480
|
-
`canonicalJson: the key "${k}" holds a ${typeof v}, which has no JSON representation. JSON.stringify would drop it from the wire body while it stayed in the signed view, so the relay's digest could never match this one.`
|
|
5481
|
-
);
|
|
5482
|
-
}
|
|
5483
|
-
return `${JSON.stringify(k)}:${canonicalJson(v)}`;
|
|
5484
|
-
}).join(",") + "}";
|
|
5485
|
-
}
|
|
5486
|
-
throw new Error(`canonicalJson: unsupported value of type ${typeof value}`);
|
|
5487
|
-
}
|
|
5488
|
-
var FIELDS_WITHOUT_NULL_ENCODING = {
|
|
5489
|
-
slippage_bps: "500 (`default_slippage_bps` in api/transact_swap.rs)"
|
|
5490
|
-
};
|
|
5491
|
-
function buildAuthRequest(body, sender, fields) {
|
|
5492
|
-
const out = {};
|
|
5493
|
-
for (const k of fields) {
|
|
5494
|
-
if (k === "sender") {
|
|
5495
|
-
out[k] = sender;
|
|
5496
|
-
continue;
|
|
5497
|
-
}
|
|
5498
|
-
const value = body[k];
|
|
5499
|
-
if (value === void 0 || value === null) {
|
|
5500
|
-
const relayDefault = FIELDS_WITHOUT_NULL_ENCODING[k];
|
|
5501
|
-
if (relayDefault) {
|
|
5502
|
-
throw new Error(
|
|
5503
|
-
`Relay request auth: \`${k}\` must be present in the body. It is the one field in this schema that is not optional on the relay side: when it is missing the relay signs its default, ${relayDefault}, while this request would sign null. The two digests differ and the relay answers 401 "Relay request signature does not match the exact request", which points at nothing. Set \`${k}\` explicitly, to the same value the body will carry.`
|
|
5504
|
-
);
|
|
5505
|
-
}
|
|
5506
|
-
out[k] = null;
|
|
5507
|
-
continue;
|
|
5508
|
-
}
|
|
5509
|
-
out[k] = value;
|
|
5510
|
-
}
|
|
5511
|
-
return out;
|
|
5512
|
-
}
|
|
5513
|
-
function buildRequestAuthMessage(endpoint, programId, issuedAt, nonce, request) {
|
|
5514
|
-
const digest = sha256Hex(canonicalJson(request));
|
|
5515
|
-
return new TextEncoder().encode(
|
|
5516
|
-
`${REQUEST_AUTH_DOMAIN}
|
|
5517
|
-
${endpoint}
|
|
5518
|
-
${programId.toBase58()}
|
|
5519
|
-
${nonce}
|
|
5520
|
-
${issuedAt}
|
|
5521
|
-
${digest}`
|
|
5522
|
-
);
|
|
5523
|
-
}
|
|
5524
|
-
function buildRelayAuthPreimage(endpoint, programId, body, sender, nowSeconds, fields = TRANSACT_AUTH_FIELDS) {
|
|
5525
|
-
const senderB58 = sender.toBase58();
|
|
5526
|
-
const issuedAt = String(nowSeconds ?? Math.floor(Date.now() / 1e3));
|
|
5527
|
-
const nonce = randomNonceUuid();
|
|
5528
|
-
const request = buildAuthRequest({ ...body, sender: senderB58 }, senderB58, fields);
|
|
5529
|
-
const message = buildRequestAuthMessage(endpoint, programId, issuedAt, nonce, request);
|
|
5530
|
-
return { sender: senderB58, auth_issued_at: issuedAt, auth_nonce: nonce, message };
|
|
5531
|
-
}
|
|
5532
|
-
function signRelayRequest(endpoint, programId, body, signer, nowSeconds, fields = TRANSACT_AUTH_FIELDS) {
|
|
5533
|
-
const preimage = buildRelayAuthPreimage(
|
|
5534
|
-
endpoint,
|
|
5535
|
-
programId,
|
|
5536
|
-
body,
|
|
5537
|
-
signer.publicKey,
|
|
5538
|
-
nowSeconds,
|
|
5539
|
-
fields
|
|
5540
|
-
);
|
|
5541
|
-
const signature = import_tweetnacl5.default.sign.detached(preimage.message, signer.secretKey);
|
|
5542
|
-
return {
|
|
5543
|
-
sender: preimage.sender,
|
|
5544
|
-
auth_issued_at: preimage.auth_issued_at,
|
|
5545
|
-
auth_nonce: preimage.auth_nonce,
|
|
5546
|
-
auth_signature: Buffer.from(signature).toString("base64")
|
|
5547
|
-
};
|
|
5548
|
-
}
|
|
5549
|
-
function assertApprovalWithinFreshnessWindow(elapsedMs) {
|
|
5550
|
-
const elapsedSeconds = elapsedMs / 1e3;
|
|
5551
|
-
const budget = REQUEST_AUTH_MAX_AGE_SECONDS - RELAY_AUTH_APPROVAL_MARGIN_SECONDS;
|
|
5552
|
-
if (elapsedSeconds <= budget) return;
|
|
5553
|
-
throw new Error(
|
|
5554
|
-
`The wallet approval took ${Math.round(elapsedSeconds)} seconds, and Cloak requests stay valid for ${REQUEST_AUTH_MAX_AGE_SECONDS} seconds from the moment they are signed (${RELAY_AUTH_APPROVAL_MARGIN_SECONDS} of those are held back for the request to reach the network). The timestamp is inside the signature, so it cannot be refreshed without your approval again. NOTHING WAS SUBMITTED and no funds moved. Start the same operation again and approve the prompt when it appears.`
|
|
5555
|
-
);
|
|
5556
|
-
}
|
|
5557
|
-
function explainRelayAuthRejection(responseText) {
|
|
5558
|
-
const text = String(responseText);
|
|
5559
|
-
const has = (needle) => text.includes(needle);
|
|
5560
|
-
if (has("issued too far in the future")) {
|
|
5561
|
-
return `This computer's clock is more than ${REQUEST_AUTH_MAX_FUTURE_SKEW_SECONDS} seconds ahead of the network's, so the request looks like it was signed in the future and is refused before anything else happens. Turn on automatic date and time (or resynchronise the clock) and try again. Nothing was submitted.`;
|
|
5562
|
-
}
|
|
5563
|
-
if (has("signature expired")) {
|
|
5564
|
-
return `The request was signed more than ${REQUEST_AUTH_MAX_AGE_SECONDS} seconds before it arrived, usually because a wallet approval was left waiting, or because this computer's clock is running behind. The signed timestamp cannot be refreshed on its own. Start the operation again. Nothing was submitted.`;
|
|
5565
|
-
}
|
|
5566
|
-
if (has("does not match the exact request")) {
|
|
5567
|
-
return `The request body changed after it was signed, so the relay's digest and this one disagree. Sign the exact body that is POSTed, and re-POST a retry byte for byte rather than rebuilding it. If this is a hand-built swap call, check that every field in TRANSACT_SWAP_AUTH_FIELDS is present, including \`slippage_bps\`.`;
|
|
5568
|
-
}
|
|
5569
|
-
if (has("has no registered viewing key")) {
|
|
5570
|
-
return `The signature was accepted, so this is not a signing problem: the wallet that signed has no viewing key registered with Cloak yet. Register one first, or let the SDK do it by leaving \`enforceViewingKeyRegistration\` on and supplying the viewing key.`;
|
|
5571
|
-
}
|
|
5572
|
-
if (has("Authenticated sender is required") || has("auth_issued_at is required") || has("auth_nonce is required") || has("auth_signature is required")) {
|
|
5573
|
-
return `The request reached the relay without its authentication fields. Pass \`depositorKeypair\`, or \`signMessage\` together with \`walletPublicKey\`, so the request can be signed.`;
|
|
5574
|
-
}
|
|
5575
|
-
if (has("auth_nonce must be a canonical UUID")) {
|
|
5576
|
-
return `\`auth_nonce\` must be a canonical lowercase UUID. Reuse the one from the preimage.`;
|
|
5577
|
-
}
|
|
5578
|
-
if (has("Relay request signature must be exactly 64 bytes") || has("Invalid relay request signature encoding")) {
|
|
5579
|
-
return `\`auth_signature\` must be the raw 64-byte ed25519 detached signature, base64 encoded. Wallet adapters that wrap or re-encode what \`signMessage\` returns cannot be used here.`;
|
|
5580
|
-
}
|
|
5581
|
-
return null;
|
|
5582
|
-
}
|
|
5583
|
-
async function buildRelayAuthFields(endpoint, programId, body, signers, fields = TRANSACT_AUTH_FIELDS) {
|
|
5584
|
-
if (signers.depositorKeypair) {
|
|
5585
|
-
return signRelayRequest(endpoint, programId, body, signers.depositorKeypair, void 0, fields);
|
|
5586
|
-
}
|
|
5587
|
-
const wallet = signers.relayAuthSigner;
|
|
5588
|
-
if (!wallet) return null;
|
|
5589
|
-
const preimage = buildRelayAuthPreimage(
|
|
5590
|
-
endpoint,
|
|
5591
|
-
programId,
|
|
5592
|
-
body,
|
|
5593
|
-
wallet.walletPublicKey,
|
|
5594
|
-
void 0,
|
|
5595
|
-
fields
|
|
5596
|
-
);
|
|
5597
|
-
const approvalStartedMs = Date.now();
|
|
5598
|
-
const signature = await wallet.signMessage(preimage.message);
|
|
5599
|
-
assertApprovalWithinFreshnessWindow(Date.now() - approvalStartedMs);
|
|
5600
|
-
if (!(signature instanceof Uint8Array) || signature.length !== 64) {
|
|
5601
|
-
throw new Error(
|
|
5602
|
-
`The wallet's signMessage did not return a 64-byte ed25519 detached signature (got ${signature instanceof Uint8Array ? `${signature.length} bytes` : typeof signature}). Cloak signs with a raw detached signature over the bytes it hands the wallet; adapters that wrap or re-encode the result cannot be used here. Pass depositorKeypair instead, or use an adapter whose signMessage returns the raw signature.`
|
|
5603
|
-
);
|
|
5604
|
-
}
|
|
5605
|
-
return {
|
|
5606
|
-
sender: preimage.sender,
|
|
5607
|
-
auth_issued_at: preimage.auth_issued_at,
|
|
5608
|
-
auth_nonce: preimage.auth_nonce,
|
|
5609
|
-
auth_signature: Buffer.from(signature).toString("base64")
|
|
5610
|
-
};
|
|
5611
|
-
}
|
|
5612
|
-
|
|
5613
|
-
// src/flows/transact.ts
|
|
5614
5715
|
init_inputs();
|
|
5615
5716
|
|
|
5616
5717
|
// src/proving/artifacts.ts
|
|
@@ -5888,6 +5989,7 @@ async function resolveTransactionCircuitFiles() {
|
|
|
5888
5989
|
}
|
|
5889
5990
|
var _transactPaddingSaltCounter = 0;
|
|
5890
5991
|
var _registeredViewingKeys = /* @__PURE__ */ new Set();
|
|
5992
|
+
var _viewingKeyRegistrationsInFlight = /* @__PURE__ */ new Map();
|
|
5891
5993
|
var _resolvedAltAccountsCache = /* @__PURE__ */ new Map();
|
|
5892
5994
|
var _relayMerkleDisabledMints = /* @__PURE__ */ new Set();
|
|
5893
5995
|
var _relayMerkleFallbackNotifiedMints = /* @__PURE__ */ new Set();
|
|
@@ -6012,7 +6114,7 @@ async function fetchSupplementalAltFromRelay(relayUrl, params) {
|
|
|
6012
6114
|
throw new Error("Supplemental ALT response is missing a 'table' address");
|
|
6013
6115
|
}
|
|
6014
6116
|
try {
|
|
6015
|
-
return new
|
|
6117
|
+
return new import_web311.PublicKey(table);
|
|
6016
6118
|
} catch {
|
|
6017
6119
|
throw new Error(`Supplemental ALT response 'table' is not a valid public key: ${table}`);
|
|
6018
6120
|
}
|
|
@@ -6080,7 +6182,7 @@ async function resolveAddressLookupTableAccounts(connection, relayUrl, altAddres
|
|
|
6080
6182
|
const fetched = await Promise.all(
|
|
6081
6183
|
altAddresses.map(async (addr) => {
|
|
6082
6184
|
try {
|
|
6083
|
-
const pubkey = new
|
|
6185
|
+
const pubkey = new import_web311.PublicKey(addr);
|
|
6084
6186
|
const result = await connection.getAddressLookupTable(pubkey);
|
|
6085
6187
|
return result.value ?? null;
|
|
6086
6188
|
} catch {
|
|
@@ -6170,7 +6272,7 @@ function calculateConfiguredProtocolFee(amount, config) {
|
|
|
6170
6272
|
return total;
|
|
6171
6273
|
}
|
|
6172
6274
|
async function readLiveProtocolFee(connection, programId, mint, amount, requireSwapOpen = false) {
|
|
6173
|
-
const [poolConfigPda] =
|
|
6275
|
+
const [poolConfigPda] = import_web311.PublicKey.findProgramAddressSync(
|
|
6174
6276
|
[Buffer.from("pool_config"), mint.toBuffer()],
|
|
6175
6277
|
programId
|
|
6176
6278
|
);
|
|
@@ -6220,6 +6322,23 @@ async function computeSwapExtDataHash(outputMint, recipientAta, minOutputAmount,
|
|
|
6220
6322
|
]);
|
|
6221
6323
|
return poseidonHasher.F.toObject(hash);
|
|
6222
6324
|
}
|
|
6325
|
+
function assertInputMints(inputUtxos, expectedMint) {
|
|
6326
|
+
const funded = inputUtxos.filter((u) => u.amount > BigInt(0));
|
|
6327
|
+
if (funded.length === 0) return;
|
|
6328
|
+
const first = funded[0].mintAddress;
|
|
6329
|
+
for (const utxo of funded) {
|
|
6330
|
+
if (!utxo.mintAddress.equals(first)) {
|
|
6331
|
+
throw new Error(
|
|
6332
|
+
`Input notes span more than one mint (${first.toBase58()} and ${utxo.mintAddress.toBase58()}). Every pool holds a single mint, so this cannot be a valid spend of either. Select notes of one mint.`
|
|
6333
|
+
);
|
|
6334
|
+
}
|
|
6335
|
+
}
|
|
6336
|
+
if (expectedMint && !first.equals(expectedMint)) {
|
|
6337
|
+
throw new Error(
|
|
6338
|
+
`Input notes are ${first.toBase58()} but this transaction expects ${expectedMint.toBase58()}. The notes decide which pool is spent, so proceeding would move the wrong asset \u2014 refusing. This usually means note selection ignored the mint.`
|
|
6339
|
+
);
|
|
6340
|
+
}
|
|
6341
|
+
}
|
|
6223
6342
|
function parseNkInput(input) {
|
|
6224
6343
|
if (typeof input !== "string") {
|
|
6225
6344
|
if (input.length !== 32) {
|
|
@@ -6275,6 +6394,26 @@ async function ensureViewingKeyRegistered(options, onProgress, fallbackNk) {
|
|
|
6275
6394
|
if (_registeredViewingKeys.has(cacheKey)) {
|
|
6276
6395
|
return;
|
|
6277
6396
|
}
|
|
6397
|
+
const inFlight = _viewingKeyRegistrationsInFlight.get(cacheKey);
|
|
6398
|
+
if (inFlight) {
|
|
6399
|
+
return inFlight;
|
|
6400
|
+
}
|
|
6401
|
+
const registration = registerViewingKeyOnce(
|
|
6402
|
+
relayUrl,
|
|
6403
|
+
userPubkey,
|
|
6404
|
+
viewingKeyHex,
|
|
6405
|
+
cacheKey,
|
|
6406
|
+
options,
|
|
6407
|
+
onProgress
|
|
6408
|
+
);
|
|
6409
|
+
_viewingKeyRegistrationsInFlight.set(cacheKey, registration);
|
|
6410
|
+
try {
|
|
6411
|
+
await registration;
|
|
6412
|
+
} finally {
|
|
6413
|
+
_viewingKeyRegistrationsInFlight.delete(cacheKey);
|
|
6414
|
+
}
|
|
6415
|
+
}
|
|
6416
|
+
async function registerViewingKeyOnce(relayUrl, userPubkey, viewingKeyHex, cacheKey, options, onProgress) {
|
|
6278
6417
|
const challengeResponse = await relayFetch(`${relayUrl}/viewing-key/challenge`, {
|
|
6279
6418
|
method: "POST",
|
|
6280
6419
|
headers: { "Content-Type": "application/json" },
|
|
@@ -6295,17 +6434,27 @@ async function ensureViewingKeyRegistered(options, onProgress, fallbackNk) {
|
|
|
6295
6434
|
viewingKeyHex
|
|
6296
6435
|
);
|
|
6297
6436
|
let signatureBase64;
|
|
6437
|
+
let authMode = "message";
|
|
6438
|
+
const messageBytes = new TextEncoder().encode(challenge.message);
|
|
6298
6439
|
if (options.signMessage) {
|
|
6299
|
-
const messageBytes = new TextEncoder().encode(challenge.message);
|
|
6300
6440
|
const sig = await options.signMessage(messageBytes);
|
|
6301
6441
|
signatureBase64 = Buffer.from(sig).toString("base64");
|
|
6302
6442
|
} else if (options.depositorKeypair) {
|
|
6303
|
-
const messageBytes = new TextEncoder().encode(challenge.message);
|
|
6304
6443
|
const sig = import_tweetnacl6.default.sign.detached(messageBytes, options.depositorKeypair.secretKey);
|
|
6305
6444
|
signatureBase64 = Buffer.from(sig).toString("base64");
|
|
6445
|
+
} else if (options.signAuthTransaction) {
|
|
6446
|
+
onProgress?.(
|
|
6447
|
+
"Approve the viewing-key registration in your wallet. It shows as a 0 SOL transfer to yourself and is never sent to the network."
|
|
6448
|
+
);
|
|
6449
|
+
const signed = await signRelayAuthPayload(
|
|
6450
|
+
{ walletPublicKey: userPubkey, signAuthTransaction: options.signAuthTransaction },
|
|
6451
|
+
messageBytes
|
|
6452
|
+
);
|
|
6453
|
+
signatureBase64 = Buffer.from(signed.signature).toString("base64");
|
|
6454
|
+
authMode = signed.mode;
|
|
6306
6455
|
} else {
|
|
6307
6456
|
throw new Error(
|
|
6308
|
-
"Viewing key registration is mandatory: signMessage (wallet) or depositorKeypair is required."
|
|
6457
|
+
"Viewing key registration is mandatory: signMessage or signAuthTransaction (wallet) or depositorKeypair is required."
|
|
6309
6458
|
);
|
|
6310
6459
|
}
|
|
6311
6460
|
onProgress?.("Registering viewing key...");
|
|
@@ -6316,7 +6465,8 @@ async function ensureViewingKeyRegistered(options, onProgress, fallbackNk) {
|
|
|
6316
6465
|
user_pubkey: userPubkey.toBase58(),
|
|
6317
6466
|
viewing_key: viewingKeyHex,
|
|
6318
6467
|
nonce: challenge.nonce,
|
|
6319
|
-
signature: signatureBase64
|
|
6468
|
+
signature: signatureBase64,
|
|
6469
|
+
...authMode === "transaction" ? { auth_mode: authMode } : {}
|
|
6320
6470
|
})
|
|
6321
6471
|
});
|
|
6322
6472
|
if (!response.ok) {
|
|
@@ -6410,7 +6560,7 @@ function buildPublicInputsBytesFromSignals(publicSignals) {
|
|
|
6410
6560
|
var TRANSACT_DISCRIMINATOR = 0;
|
|
6411
6561
|
var RANGE_QUOTE_TAG_DEPOSIT = 1;
|
|
6412
6562
|
function deriveRiskNoncePDA(programId, nonce) {
|
|
6413
|
-
return
|
|
6563
|
+
return import_web311.PublicKey.findProgramAddressSync(
|
|
6414
6564
|
[Buffer.from("risk_nonce"), Buffer.from(nonce)],
|
|
6415
6565
|
programId
|
|
6416
6566
|
);
|
|
@@ -6429,7 +6579,7 @@ function extractDepositNonceFromEd25519Ix(ix) {
|
|
|
6429
6579
|
}
|
|
6430
6580
|
}
|
|
6431
6581
|
function deriveNullifierPDA(programId, poolPDA, nullifier) {
|
|
6432
|
-
return
|
|
6582
|
+
return import_web311.PublicKey.findProgramAddressSync(
|
|
6433
6583
|
[Buffer.from("nullifier"), poolPDA.toBuffer(), Buffer.from(nullifier)],
|
|
6434
6584
|
programId
|
|
6435
6585
|
);
|
|
@@ -6462,7 +6612,7 @@ function buildTransactInstruction(programId, payer, poolPDA, treasuryPDA, merkle
|
|
|
6462
6612
|
}
|
|
6463
6613
|
if (!isDepositData) {
|
|
6464
6614
|
data[offset++] = 3;
|
|
6465
|
-
data.set((relayer ??
|
|
6615
|
+
data.set((relayer ?? import_web311.PublicKey.default).toBytes(), offset);
|
|
6466
6616
|
offset += 32;
|
|
6467
6617
|
writeBigUInt64LE(data, relayerFee ?? BigInt(0), offset);
|
|
6468
6618
|
offset += 8;
|
|
@@ -6492,7 +6642,7 @@ function buildTransactInstruction(programId, payer, poolPDA, treasuryPDA, merkle
|
|
|
6492
6642
|
// 4. nullifier PDA 0 (writable)
|
|
6493
6643
|
{ pubkey: nullifierPDA1, isSigner: false, isWritable: true },
|
|
6494
6644
|
// 5. nullifier PDA 1 (writable)
|
|
6495
|
-
{ pubkey:
|
|
6645
|
+
{ pubkey: import_web311.SystemProgram.programId, isSigner: false, isWritable: false }
|
|
6496
6646
|
// 6. system_program
|
|
6497
6647
|
];
|
|
6498
6648
|
if (splAccounts) {
|
|
@@ -6501,34 +6651,34 @@ function buildTransactInstruction(programId, payer, poolPDA, treasuryPDA, merkle
|
|
|
6501
6651
|
accounts.push({ pubkey: splAccounts.tokenProgram, isSigner: false, isWritable: false });
|
|
6502
6652
|
if (splAccounts.payerAta) {
|
|
6503
6653
|
if (enableRiskCheck) {
|
|
6504
|
-
accounts.push({ pubkey:
|
|
6654
|
+
accounts.push({ pubkey: import_web311.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
|
|
6505
6655
|
}
|
|
6506
6656
|
accounts.push({ pubkey: splAccounts.payerAta, isSigner: false, isWritable: true });
|
|
6507
6657
|
if (riskNonce) {
|
|
6508
6658
|
accounts.push({ pubkey: riskNonce, isSigner: false, isWritable: true });
|
|
6509
6659
|
}
|
|
6510
6660
|
} else if (enableRiskCheck && !recipient) {
|
|
6511
|
-
accounts.push({ pubkey:
|
|
6661
|
+
accounts.push({ pubkey: import_web311.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
|
|
6512
6662
|
}
|
|
6513
6663
|
} else {
|
|
6514
6664
|
if (recipient) {
|
|
6515
6665
|
accounts.push({ pubkey: recipient, isSigner: false, isWritable: true });
|
|
6516
6666
|
if (enableRiskCheck) {
|
|
6517
|
-
accounts.push({ pubkey:
|
|
6667
|
+
accounts.push({ pubkey: import_web311.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
|
|
6518
6668
|
}
|
|
6519
6669
|
} else if (enableRiskCheck) {
|
|
6520
6670
|
if (publicAmountForAccounts === BigInt(0)) {
|
|
6521
6671
|
accounts.push({ pubkey: payer, isSigner: true, isWritable: false });
|
|
6522
|
-
accounts.push({ pubkey:
|
|
6672
|
+
accounts.push({ pubkey: import_web311.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
|
|
6523
6673
|
} else {
|
|
6524
|
-
accounts.push({ pubkey:
|
|
6674
|
+
accounts.push({ pubkey: import_web311.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
|
|
6525
6675
|
if (riskNonce) {
|
|
6526
6676
|
accounts.push({ pubkey: riskNonce, isSigner: false, isWritable: true });
|
|
6527
6677
|
}
|
|
6528
6678
|
}
|
|
6529
6679
|
}
|
|
6530
6680
|
}
|
|
6531
|
-
return new
|
|
6681
|
+
return new import_web311.TransactionInstruction({
|
|
6532
6682
|
programId,
|
|
6533
6683
|
keys: accounts,
|
|
6534
6684
|
data
|
|
@@ -6537,10 +6687,10 @@ function buildTransactInstruction(programId, payer, poolPDA, treasuryPDA, merkle
|
|
|
6537
6687
|
var PACKET_LIMIT_BYTES = 1232;
|
|
6538
6688
|
function getCommonALTAddresses() {
|
|
6539
6689
|
return [
|
|
6540
|
-
|
|
6541
|
-
|
|
6542
|
-
|
|
6543
|
-
|
|
6690
|
+
import_web311.SystemProgram.programId,
|
|
6691
|
+
import_web311.SYSVAR_SLOT_HASHES_PUBKEY,
|
|
6692
|
+
import_web311.SYSVAR_INSTRUCTIONS_PUBKEY,
|
|
6693
|
+
import_web311.ComputeBudgetProgram.programId
|
|
6544
6694
|
];
|
|
6545
6695
|
}
|
|
6546
6696
|
function dedupePubkeys(addresses) {
|
|
@@ -6591,12 +6741,12 @@ async function createEphemeralALT(connection, depositor, onProgress, additionalA
|
|
|
6591
6741
|
try {
|
|
6592
6742
|
const slot = await connection.getSlot("finalized");
|
|
6593
6743
|
const altPayerPubkey = externalFeePayer ? await externalFeePayer.getPayerPubkey() : depositor.publicKey;
|
|
6594
|
-
const [createIx, altAddress] =
|
|
6744
|
+
const [createIx, altAddress] = import_web311.AddressLookupTableProgram.createLookupTable({
|
|
6595
6745
|
authority: depositor.publicKey,
|
|
6596
6746
|
payer: altPayerPubkey,
|
|
6597
6747
|
recentSlot: slot
|
|
6598
6748
|
});
|
|
6599
|
-
const extendIx =
|
|
6749
|
+
const extendIx = import_web311.AddressLookupTableProgram.extendLookupTable({
|
|
6600
6750
|
payer: altPayerPubkey,
|
|
6601
6751
|
authority: depositor.publicKey,
|
|
6602
6752
|
lookupTable: altAddress,
|
|
@@ -6615,12 +6765,12 @@ async function createEphemeralALT(connection, depositor, onProgress, additionalA
|
|
|
6615
6765
|
allowSupplementalAlt
|
|
6616
6766
|
);
|
|
6617
6767
|
} else {
|
|
6618
|
-
const tx = new
|
|
6768
|
+
const tx = new import_web311.Transaction().add(createIx).add(extendIx);
|
|
6619
6769
|
const { blockhash } = await connection.getLatestBlockhash();
|
|
6620
6770
|
tx.recentBlockhash = blockhash;
|
|
6621
6771
|
tx.feePayer = depositor.publicKey;
|
|
6622
6772
|
if (depositor.keypair) {
|
|
6623
|
-
await (0,
|
|
6773
|
+
await (0, import_web311.sendAndConfirmTransaction)(connection, tx, [depositor.keypair], { commitment: "confirmed" });
|
|
6624
6774
|
} else if (depositor.signTransaction) {
|
|
6625
6775
|
const signedTx = await depositor.signTransaction(tx);
|
|
6626
6776
|
const sig = await connection.sendRawTransaction(signedTx.serialize());
|
|
@@ -6659,7 +6809,7 @@ async function createEphemeralALT(connection, depositor, onProgress, additionalA
|
|
|
6659
6809
|
var SOLANA_PACKET_DATA_SIZE = 1232;
|
|
6660
6810
|
function estimateV0TransactionSize(payerKey, recentBlockhash, instructions, addressLookupTableAccounts) {
|
|
6661
6811
|
try {
|
|
6662
|
-
const messageV0 = new
|
|
6812
|
+
const messageV0 = new import_web311.TransactionMessage({
|
|
6663
6813
|
payerKey,
|
|
6664
6814
|
recentBlockhash,
|
|
6665
6815
|
instructions
|
|
@@ -6728,7 +6878,7 @@ async function submitViaExternalFeePayer(connection, depositor, externalFeePayer
|
|
|
6728
6878
|
const { blockhash: quoteBlockhash } = await connection.getLatestBlockhash();
|
|
6729
6879
|
const measure = (instrs, alts) => {
|
|
6730
6880
|
try {
|
|
6731
|
-
const message = new
|
|
6881
|
+
const message = new import_web311.TransactionMessage({
|
|
6732
6882
|
payerKey: externalFeePayerPubkey,
|
|
6733
6883
|
recentBlockhash: quoteBlockhash,
|
|
6734
6884
|
instructions: instrs
|
|
@@ -6786,12 +6936,12 @@ async function submitViaExternalFeePayer(connection, depositor, externalFeePayer
|
|
|
6786
6936
|
pre = measure(instructions, addressLookupTableAccounts);
|
|
6787
6937
|
}
|
|
6788
6938
|
failIfOverLimit(pre.size, pre.error);
|
|
6789
|
-
const quoteMessage = new
|
|
6939
|
+
const quoteMessage = new import_web311.TransactionMessage({
|
|
6790
6940
|
payerKey: externalFeePayerPubkey,
|
|
6791
6941
|
recentBlockhash: quoteBlockhash,
|
|
6792
6942
|
instructions
|
|
6793
6943
|
}).compileToV0Message(addressLookupTableAccounts ?? []);
|
|
6794
|
-
const quoteTxBase64 = Buffer.from(new
|
|
6944
|
+
const quoteTxBase64 = Buffer.from(new import_web311.VersionedTransaction(quoteMessage).serialize()).toString("base64");
|
|
6795
6945
|
const paymentInstruction = await externalFeePayer.getPaymentInstruction(quoteTxBase64);
|
|
6796
6946
|
const finalInstructions = [...instructions, paymentInstruction];
|
|
6797
6947
|
let post = measure(finalInstructions, addressLookupTableAccounts);
|
|
@@ -6802,12 +6952,12 @@ async function submitViaExternalFeePayer(connection, depositor, externalFeePayer
|
|
|
6802
6952
|
failIfOverLimit(post.size, post.error);
|
|
6803
6953
|
onProgress?.("Building final transaction...");
|
|
6804
6954
|
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
|
|
6805
|
-
const finalMessage = new
|
|
6955
|
+
const finalMessage = new import_web311.TransactionMessage({
|
|
6806
6956
|
payerKey: externalFeePayerPubkey,
|
|
6807
6957
|
recentBlockhash: blockhash,
|
|
6808
6958
|
instructions: finalInstructions
|
|
6809
6959
|
}).compileToV0Message(addressLookupTableAccounts ?? []);
|
|
6810
|
-
const finalTx = new
|
|
6960
|
+
const finalTx = new import_web311.VersionedTransaction(finalMessage);
|
|
6811
6961
|
onProgress?.("Waiting for wallet signature...");
|
|
6812
6962
|
const partiallySignedTx = await depositor.signTransaction(finalTx);
|
|
6813
6963
|
const partiallySignedBase64 = Buffer.from(partiallySignedTx.serialize()).toString("base64");
|
|
@@ -7028,8 +7178,8 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
|
|
|
7028
7178
|
baseInstructions.splice(riskQuoteIx ? 1 : 0, 0, ...topUpInstructions);
|
|
7029
7179
|
}
|
|
7030
7180
|
}
|
|
7031
|
-
const cuLimitIx =
|
|
7032
|
-
const cuPriceIx =
|
|
7181
|
+
const cuLimitIx = import_web311.ComputeBudgetProgram.setComputeUnitLimit({ units: 12e5 });
|
|
7182
|
+
const cuPriceIx = import_web311.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1e5 });
|
|
7033
7183
|
const fullInstructions = [...baseInstructions, cuLimitIx, cuPriceIx, transactIx];
|
|
7034
7184
|
const compactInstructions = [...baseInstructions, cuLimitIx, transactIx];
|
|
7035
7185
|
const minimalInstructions = [...baseInstructions, cuLimitIx, transactIx];
|
|
@@ -7141,12 +7291,12 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
|
|
|
7141
7291
|
}
|
|
7142
7292
|
let transportAttempt = 0;
|
|
7143
7293
|
while (true) {
|
|
7144
|
-
const messageV0 = new
|
|
7294
|
+
const messageV0 = new import_web311.TransactionMessage({
|
|
7145
7295
|
payerKey: depositor.publicKey,
|
|
7146
7296
|
recentBlockhash: blockhash,
|
|
7147
7297
|
instructions: ixs
|
|
7148
7298
|
}).compileToV0Message(addressLookupTableAccounts);
|
|
7149
|
-
const versionedTx = new
|
|
7299
|
+
const versionedTx = new import_web311.VersionedTransaction(messageV0);
|
|
7150
7300
|
if (onTransactProofBuilt) {
|
|
7151
7301
|
await onTransactProofBuilt();
|
|
7152
7302
|
}
|
|
@@ -7276,7 +7426,7 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
|
|
|
7276
7426
|
let legacyTransportAttempt = 0;
|
|
7277
7427
|
while (true) {
|
|
7278
7428
|
try {
|
|
7279
|
-
const tx = new
|
|
7429
|
+
const tx = new import_web311.Transaction();
|
|
7280
7430
|
for (const ix of instructions) {
|
|
7281
7431
|
tx.add(ix);
|
|
7282
7432
|
}
|
|
@@ -7284,7 +7434,7 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
|
|
|
7284
7434
|
tx.feePayer = depositor.publicKey;
|
|
7285
7435
|
if (depositor.keypair) {
|
|
7286
7436
|
onProgress?.("Signing and sending transaction...");
|
|
7287
|
-
signature = await (0,
|
|
7437
|
+
signature = await (0, import_web311.sendAndConfirmTransaction)(
|
|
7288
7438
|
connection,
|
|
7289
7439
|
tx,
|
|
7290
7440
|
[depositor.keypair],
|
|
@@ -7500,7 +7650,7 @@ async function fetchRiskQuote(riskQuoteUrl, wallet, options) {
|
|
|
7500
7650
|
const messageArr = hexToBytes2(messageHex);
|
|
7501
7651
|
let signerPubkey;
|
|
7502
7652
|
try {
|
|
7503
|
-
signerPubkey = new
|
|
7653
|
+
signerPubkey = new import_web311.PublicKey(signerB58);
|
|
7504
7654
|
} catch {
|
|
7505
7655
|
throw new Error("Invalid risk quote response: signer_pubkey is not a public key");
|
|
7506
7656
|
}
|
|
@@ -7524,7 +7674,7 @@ async function fetchRiskQuote(riskQuoteUrl, wallet, options) {
|
|
|
7524
7674
|
const signature = new Uint8Array(signatureBuf);
|
|
7525
7675
|
const message = new Uint8Array(messageArr);
|
|
7526
7676
|
const publicKey = new Uint8Array(publicKeyBytes);
|
|
7527
|
-
const instruction =
|
|
7677
|
+
const instruction = import_web311.Ed25519Program.createInstructionWithPublicKey({
|
|
7528
7678
|
publicKey,
|
|
7529
7679
|
message,
|
|
7530
7680
|
signature
|
|
@@ -7601,18 +7751,22 @@ function planRelayAuth(options) {
|
|
|
7601
7751
|
const walletPublicKey = resolveUserWallet(options);
|
|
7602
7752
|
if (options.depositorKeypair) return { kind: "keypair" };
|
|
7603
7753
|
const signMessage = options.signMessage;
|
|
7754
|
+
const signAuthTransaction = options.signAuthTransaction;
|
|
7604
7755
|
if (walletPublicKey && signMessage) return { kind: "wallet", signer: { walletPublicKey, signMessage } };
|
|
7605
|
-
if (
|
|
7756
|
+
if (walletPublicKey && signAuthTransaction) {
|
|
7757
|
+
return { kind: "wallet", signer: { walletPublicKey, signAuthTransaction } };
|
|
7758
|
+
}
|
|
7759
|
+
if (!walletPublicKey && !signMessage && !signAuthTransaction) return { kind: "none" };
|
|
7606
7760
|
return {
|
|
7607
7761
|
kind: "incomplete",
|
|
7608
|
-
detail: signMessage ? "
|
|
7762
|
+
detail: signMessage || signAuthTransaction ? "a signer was provided but no wallet public key, so the request has no sender to authenticate as. Add `walletPublicKey` (or `depositorPublicKey`)." : "a wallet public key was provided but no `signMessage` and no `signAuthTransaction`, so nothing can sign. Add `signMessage` from the wallet adapter, or `signAuthTransaction` (its `signTransaction`) for a wallet that cannot sign messages."
|
|
7609
7763
|
};
|
|
7610
7764
|
}
|
|
7611
7765
|
function assertRelayAuthAvailable(plan, flow) {
|
|
7612
7766
|
if (plan.kind === "keypair" || plan.kind === "wallet") return;
|
|
7613
7767
|
const cause = plan.kind === "none" ? "no signer was provided." : `the signer provided cannot produce one: ${plan.detail}`;
|
|
7614
7768
|
throw new Error(
|
|
7615
|
-
`Cloak ${flow} requires an authenticated sender, and ${cause} Pass either \`depositorKeypair\` (a local Keypair) or \`signMessage\`
|
|
7769
|
+
`Cloak ${flow} requires an authenticated sender, and ${cause} Pass either \`depositorKeypair\` (a local Keypair), or \`signMessage\` (a browser wallet adapter) or \`signAuthTransaction\` (a hardware wallet that cannot sign messages) together with \`walletPublicKey\`. The sender must be the end user's own wallet. Checked up front, so nothing has been computed or submitted yet.`
|
|
7616
7770
|
);
|
|
7617
7771
|
}
|
|
7618
7772
|
async function submitTransactToRelay(args) {
|
|
@@ -7630,12 +7784,12 @@ async function submitTransactToRelay(args) {
|
|
|
7630
7784
|
const doFetch = args.fetchImpl ?? fetch;
|
|
7631
7785
|
const maxNetworkRetries = args.maxNetworkRetries ?? relayNetworkRetries();
|
|
7632
7786
|
const requestTimeoutMs = args.requestTimeoutMs ?? relayRequestTimeoutMs();
|
|
7633
|
-
if (!depositorKeypair && args.relayAuthSigner) {
|
|
7787
|
+
if (!depositorKeypair && args.relayAuthSigner && !args.relayAuthBatch) {
|
|
7634
7788
|
onProgress?.(
|
|
7635
7789
|
"Approve the request in your wallet. This signs the request for the relay, not a transaction, and it must be approved within a few minutes to stay valid."
|
|
7636
7790
|
);
|
|
7637
7791
|
}
|
|
7638
|
-
const authFields = await buildRelayAuthFields("/transact", programId, requestBody, {
|
|
7792
|
+
const authFields = args.relayAuthBatch ? await args.relayAuthBatch.authorize("/transact", programId, requestBody, TRANSACT_AUTH_FIELDS) : await buildRelayAuthFields("/transact", programId, requestBody, {
|
|
7639
7793
|
depositorKeypair,
|
|
7640
7794
|
relayAuthSigner: args.relayAuthSigner
|
|
7641
7795
|
});
|
|
@@ -7831,7 +7985,7 @@ async function transact(params, options) {
|
|
|
7831
7985
|
// names is refused.
|
|
7832
7986
|
maxRootRetries = 40,
|
|
7833
7987
|
// Increased to handle prolonged relay sync recovery under high concurrency
|
|
7834
|
-
retryDelayMs = 500,
|
|
7988
|
+
retryDelayMs: retryDelayMs2 = 500,
|
|
7835
7989
|
// Start with short delay, will exponentially back off
|
|
7836
7990
|
riskOracleQueue,
|
|
7837
7991
|
riskQuoteUrl: riskQuoteUrlOption,
|
|
@@ -7886,6 +8040,7 @@ async function transact(params, options) {
|
|
|
7886
8040
|
}
|
|
7887
8041
|
}
|
|
7888
8042
|
assertAllowedRpcConnection(connection);
|
|
8043
|
+
assertInputMints(inputUtxos, options.expectedMint);
|
|
7889
8044
|
await ensureViewingKeyRegistered(options, onProgress, fallbackNk);
|
|
7890
8045
|
const resolvedChainNoteNk = explicitChainNoteNk ?? fallbackNk ?? null;
|
|
7891
8046
|
if (inputUtxos.length > 2) {
|
|
@@ -8252,7 +8407,7 @@ async function transact(params, options) {
|
|
|
8252
8407
|
while (submissionAttempts <= maxRootRetries) {
|
|
8253
8408
|
const isRetry = submissionAttempts > 0;
|
|
8254
8409
|
if (isRetry) {
|
|
8255
|
-
const exponentialDelay = Math.min(
|
|
8410
|
+
const exponentialDelay = Math.min(retryDelayMs2 * Math.pow(1.5, submissionAttempts - 1), 5e3);
|
|
8256
8411
|
const jitter = Math.random() * 500;
|
|
8257
8412
|
const totalDelay = Math.floor(exponentialDelay + jitter);
|
|
8258
8413
|
await sleep2(totalDelay);
|
|
@@ -8391,11 +8546,14 @@ async function transact(params, options) {
|
|
|
8391
8546
|
}
|
|
8392
8547
|
let proof;
|
|
8393
8548
|
let publicSignals;
|
|
8549
|
+
const releaseProofSlot = options.relayAuthBatch ? await options.relayAuthBatch.proofSlot() : void 0;
|
|
8394
8550
|
try {
|
|
8395
8551
|
const result = await generateTransactionProof(circuitInputs, onProofProgress);
|
|
8396
8552
|
proof = result.proof;
|
|
8397
8553
|
publicSignals = result.publicSignals;
|
|
8554
|
+
releaseProofSlot?.();
|
|
8398
8555
|
} catch (proofError) {
|
|
8556
|
+
releaseProofSlot?.();
|
|
8399
8557
|
const errorMsg = proofError?.message || String(proofError);
|
|
8400
8558
|
if ((errorMsg.includes("ForceEqual") || errorMsg.includes("Assert Failed")) && submissionAttempts < maxRootRetries) {
|
|
8401
8559
|
onProgress?.(`Merkle proof error (stale sibling data), will regenerate with fresh tree...`);
|
|
@@ -8415,7 +8573,7 @@ async function transact(params, options) {
|
|
|
8415
8573
|
}
|
|
8416
8574
|
treeState = null;
|
|
8417
8575
|
useSiblingInfo = false;
|
|
8418
|
-
const waitMs = Math.min(
|
|
8576
|
+
const waitMs = Math.min(retryDelayMs2 * Math.pow(2, Math.min(submissionAttempts, 4)), 5e3);
|
|
8419
8577
|
if (waitMs > 0) {
|
|
8420
8578
|
onProgress?.(`Waiting ${waitMs}ms for relay sync before retry...`);
|
|
8421
8579
|
await new Promise((r) => setTimeout(r, waitMs));
|
|
@@ -8708,6 +8866,7 @@ async function transact(params, options) {
|
|
|
8708
8866
|
// point where they reach the relay submission instead of stopping at viewing-key
|
|
8709
8867
|
// registration. The keypair still wins when both are present.
|
|
8710
8868
|
relayAuthSigner: relayAuthPlan.kind === "wallet" ? relayAuthPlan.signer : void 0,
|
|
8869
|
+
relayAuthBatch: options.relayAuthBatch,
|
|
8711
8870
|
settlement: {
|
|
8712
8871
|
connection,
|
|
8713
8872
|
programId,
|
|
@@ -8753,7 +8912,7 @@ async function transact(params, options) {
|
|
|
8753
8912
|
"All relay-tree attempts failed. Rebuilding merkle tree from chain as last resort..."
|
|
8754
8913
|
);
|
|
8755
8914
|
try {
|
|
8756
|
-
const [merkleTreePda] =
|
|
8915
|
+
const [merkleTreePda] = import_web311.PublicKey.findProgramAddressSync(
|
|
8757
8916
|
[Buffer.from("merkle_tree"), mint.toBuffer()],
|
|
8758
8917
|
programId
|
|
8759
8918
|
);
|
|
@@ -9109,7 +9268,7 @@ async function swapUtxo(params, options) {
|
|
|
9109
9268
|
riskQuoteUrl: riskQuoteUrlOption,
|
|
9110
9269
|
maxRootRetries = 40,
|
|
9111
9270
|
// Increased to handle prolonged relay sync recovery under high concurrency
|
|
9112
|
-
retryDelayMs = 500,
|
|
9271
|
+
retryDelayMs: retryDelayMs2 = 500,
|
|
9113
9272
|
// Start with short delay, will exponentially back off
|
|
9114
9273
|
useUniqueNullifiers,
|
|
9115
9274
|
useChainRootForProof = true
|
|
@@ -9128,6 +9287,12 @@ async function swapUtxo(params, options) {
|
|
|
9128
9287
|
if (inputUtxos.length > 2) {
|
|
9129
9288
|
throw new Error("Maximum 2 input UTXOs allowed");
|
|
9130
9289
|
}
|
|
9290
|
+
assertInputMints(inputUtxos, NATIVE_SOL_MINT);
|
|
9291
|
+
if (options.expectedMint && !options.expectedMint.equals(NATIVE_SOL_MINT)) {
|
|
9292
|
+
throw new Error(
|
|
9293
|
+
`swapUtxo expects ${NATIVE_SOL_MINT.toBase58()} input notes (a swap always spends from the wSOL pool), but expectedMint was ${options.expectedMint.toBase58()}. Drop expectedMint, or spend the other mint with transact/transfer instead.`
|
|
9294
|
+
);
|
|
9295
|
+
}
|
|
9131
9296
|
await preflightNullifiers(inputUtxos, connection, programId);
|
|
9132
9297
|
const outputMintAccount = await connection.getAccountInfo(outputMint, {
|
|
9133
9298
|
commitment: "confirmed"
|
|
@@ -9514,7 +9679,7 @@ async function swapUtxo(params, options) {
|
|
|
9514
9679
|
let walletApprovals = 0;
|
|
9515
9680
|
for (let attempt = 0; attempt <= maxRootRetries; attempt++) {
|
|
9516
9681
|
if (attempt > 0) {
|
|
9517
|
-
const exponentialDelay = Math.min(
|
|
9682
|
+
const exponentialDelay = Math.min(retryDelayMs2 * Math.pow(1.5, attempt - 1), 5e3);
|
|
9518
9683
|
const jitter = Math.random() * 500;
|
|
9519
9684
|
const totalDelay = Math.floor(exponentialDelay + jitter);
|
|
9520
9685
|
await sleep2(totalDelay);
|
|
@@ -9652,7 +9817,7 @@ async function swapUtxo(params, options) {
|
|
|
9652
9817
|
}
|
|
9653
9818
|
merkleState = null;
|
|
9654
9819
|
useSiblingInfo = false;
|
|
9655
|
-
const waitMs = Math.min(
|
|
9820
|
+
const waitMs = Math.min(retryDelayMs2 * Math.pow(2, Math.min(attempt, 4)), 5e3);
|
|
9656
9821
|
if (waitMs > 0) {
|
|
9657
9822
|
onProgress?.(`Waiting ${waitMs}ms for relay sync before retry...`);
|
|
9658
9823
|
await new Promise((r) => setTimeout(r, waitMs));
|
|
@@ -9735,7 +9900,7 @@ async function swapUtxo(params, options) {
|
|
|
9735
9900
|
}
|
|
9736
9901
|
if (options.onSwapStatePredicted && inputNullifiers.length > 0) {
|
|
9737
9902
|
const nBytes = bigintToBytes323(inputNullifiers[0]);
|
|
9738
|
-
const [ssp] =
|
|
9903
|
+
const [ssp] = import_web311.PublicKey.findProgramAddressSync(
|
|
9739
9904
|
[Buffer.from("swap_state"), pdas.pool.toBuffer(), Buffer.from(nBytes)],
|
|
9740
9905
|
programId
|
|
9741
9906
|
);
|
|
@@ -10157,28 +10322,294 @@ function cleanupStalePendingOperations(maxAgeMs = 24 * 60 * 60 * 1e3) {
|
|
|
10157
10322
|
// src/index.ts
|
|
10158
10323
|
init_utxo();
|
|
10159
10324
|
|
|
10160
|
-
// src/
|
|
10161
|
-
var
|
|
10162
|
-
var
|
|
10163
|
-
|
|
10164
|
-
|
|
10165
|
-
|
|
10166
|
-
|
|
10167
|
-
|
|
10168
|
-
|
|
10169
|
-
|
|
10170
|
-
|
|
10171
|
-
|
|
10172
|
-
|
|
10173
|
-
|
|
10174
|
-
|
|
10175
|
-
|
|
10176
|
-
|
|
10177
|
-
|
|
10178
|
-
|
|
10179
|
-
|
|
10180
|
-
|
|
10181
|
-
|
|
10325
|
+
// src/relay/batch-auth.ts
|
|
10326
|
+
var import_web312 = require("@solana/web3.js");
|
|
10327
|
+
var RelayBatchAuthCoordinator = class {
|
|
10328
|
+
constructor(options) {
|
|
10329
|
+
this.proofSlotsInUse = 0;
|
|
10330
|
+
this.proofQueue = [];
|
|
10331
|
+
this.items = [];
|
|
10332
|
+
this.signing = false;
|
|
10333
|
+
this.slotsInUse = 0;
|
|
10334
|
+
this.ready = [];
|
|
10335
|
+
this.waves = 0;
|
|
10336
|
+
if (!Number.isInteger(options.items) || options.items < 1) {
|
|
10337
|
+
throw new Error(`A batch needs at least one item (got ${options.items}).`);
|
|
10338
|
+
}
|
|
10339
|
+
if (options.items > RELAY_BATCH_AUTH_MAX_ITEMS) {
|
|
10340
|
+
throw new Error(
|
|
10341
|
+
`A batch may cover at most ${RELAY_BATCH_AUTH_MAX_ITEMS} requests (got ${options.items}); the relay refuses longer digest lists. Chunk the payout. The practical bound is lower still: each spend appends two roots to the on-chain ring of 100, so a batch proved against one root evicts it by itself before 50 items.`
|
|
10342
|
+
);
|
|
10343
|
+
}
|
|
10344
|
+
const concurrency = options.submitConcurrency ?? 3;
|
|
10345
|
+
if (!Number.isInteger(concurrency) || concurrency < 1) {
|
|
10346
|
+
throw new Error(`submitConcurrency must be a positive integer (got ${concurrency}).`);
|
|
10347
|
+
}
|
|
10348
|
+
this.signer = options.signer;
|
|
10349
|
+
this.sender = options.signer instanceof import_web312.Keypair ? options.signer.publicKey : options.signer.walletPublicKey;
|
|
10350
|
+
this.expectedItems = options.items;
|
|
10351
|
+
this.submitConcurrency = concurrency;
|
|
10352
|
+
const proofConcurrency = options.proofConcurrency ?? 2;
|
|
10353
|
+
if (!Number.isInteger(proofConcurrency) || proofConcurrency < 1) {
|
|
10354
|
+
throw new Error(`proofConcurrency must be a positive integer (got ${proofConcurrency}).`);
|
|
10355
|
+
}
|
|
10356
|
+
this.proofConcurrency = proofConcurrency;
|
|
10357
|
+
this.onProgress = options.onProgress;
|
|
10358
|
+
}
|
|
10359
|
+
/** The wallet that signs and is every item's authenticated `sender`. */
|
|
10360
|
+
get walletPublicKey() {
|
|
10361
|
+
return this.sender;
|
|
10362
|
+
}
|
|
10363
|
+
/** How many times the signer has been asked so far: one per wave. */
|
|
10364
|
+
get approvals() {
|
|
10365
|
+
return this.waves;
|
|
10366
|
+
}
|
|
10367
|
+
/** Where every item is right now. For progress UIs and for diagnosing a batch that never signs. */
|
|
10368
|
+
snapshot() {
|
|
10369
|
+
const count = (state) => this.items.filter((i) => i.state === state).length;
|
|
10370
|
+
return {
|
|
10371
|
+
building: count("building"),
|
|
10372
|
+
waiting: count("waiting"),
|
|
10373
|
+
submitting: count("submitting"),
|
|
10374
|
+
done: count("done"),
|
|
10375
|
+
proving: this.proofSlotsInUse,
|
|
10376
|
+
proofQueue: this.proofQueue.length,
|
|
10377
|
+
registered: this.items.length,
|
|
10378
|
+
expected: this.expectedItems
|
|
10379
|
+
};
|
|
10380
|
+
}
|
|
10381
|
+
/** Register one item. Throws past `items`: the wave condition would never be reachable. */
|
|
10382
|
+
item() {
|
|
10383
|
+
if (this.items.length >= this.expectedItems) {
|
|
10384
|
+
throw new Error(
|
|
10385
|
+
`This batch was created for ${this.expectedItems} item(s) and all of them are registered.`
|
|
10386
|
+
);
|
|
10387
|
+
}
|
|
10388
|
+
const item = { state: "building", waiting: null, holdsSlot: false };
|
|
10389
|
+
this.items.push(item);
|
|
10390
|
+
return {
|
|
10391
|
+
proofSlot: () => this.acquireProofSlot(),
|
|
10392
|
+
authorize: (endpoint, programId, body, fields) => this.authorize(item, endpoint, programId, body, fields),
|
|
10393
|
+
finish: () => this.finish(item)
|
|
10394
|
+
};
|
|
10395
|
+
}
|
|
10396
|
+
acquireProofSlot() {
|
|
10397
|
+
return new Promise((resolve) => {
|
|
10398
|
+
const grant = () => {
|
|
10399
|
+
this.proofSlotsInUse += 1;
|
|
10400
|
+
let released = false;
|
|
10401
|
+
resolve(() => {
|
|
10402
|
+
if (released) return;
|
|
10403
|
+
released = true;
|
|
10404
|
+
this.proofSlotsInUse -= 1;
|
|
10405
|
+
const next = this.proofQueue.shift();
|
|
10406
|
+
if (next) next();
|
|
10407
|
+
});
|
|
10408
|
+
};
|
|
10409
|
+
if (this.proofSlotsInUse < this.proofConcurrency) grant();
|
|
10410
|
+
else this.proofQueue.push(grant);
|
|
10411
|
+
});
|
|
10412
|
+
}
|
|
10413
|
+
authorize(item, endpoint, programId, body, fields) {
|
|
10414
|
+
if (item.state === "done") {
|
|
10415
|
+
return Promise.reject(new Error("This batch item already finished; it cannot authorize."));
|
|
10416
|
+
}
|
|
10417
|
+
if (item.state === "waiting") {
|
|
10418
|
+
return Promise.reject(new Error("This batch item is already waiting for a signature."));
|
|
10419
|
+
}
|
|
10420
|
+
this.releaseSlot(item);
|
|
10421
|
+
return new Promise((resolve, reject) => {
|
|
10422
|
+
item.state = "waiting";
|
|
10423
|
+
item.waiting = { endpoint, programId, body, fields, resolve, reject };
|
|
10424
|
+
this.maybeSign();
|
|
10425
|
+
});
|
|
10426
|
+
}
|
|
10427
|
+
finish(item) {
|
|
10428
|
+
if (item.state === "done") return;
|
|
10429
|
+
if (item.state === "waiting" && item.waiting) {
|
|
10430
|
+
item.waiting.reject(new Error("Batch item finished before it was signed."));
|
|
10431
|
+
item.waiting = null;
|
|
10432
|
+
}
|
|
10433
|
+
this.releaseSlot(item);
|
|
10434
|
+
item.state = "done";
|
|
10435
|
+
this.maybeSign();
|
|
10436
|
+
this.pump();
|
|
10437
|
+
}
|
|
10438
|
+
releaseSlot(item) {
|
|
10439
|
+
if (!item.holdsSlot) return;
|
|
10440
|
+
item.holdsSlot = false;
|
|
10441
|
+
this.slotsInUse -= 1;
|
|
10442
|
+
}
|
|
10443
|
+
/** Sign when every registered item is either waiting here or finished, and nothing is building. */
|
|
10444
|
+
maybeSign() {
|
|
10445
|
+
if (this.signing) return;
|
|
10446
|
+
if (this.items.length < this.expectedItems) return;
|
|
10447
|
+
if (this.items.some((item) => item.state === "building" || item.state === "submitting")) return;
|
|
10448
|
+
const wave = this.items.filter((item) => item.state === "waiting");
|
|
10449
|
+
if (wave.length === 0) return;
|
|
10450
|
+
this.signing = true;
|
|
10451
|
+
void this.signWave(wave).finally(() => {
|
|
10452
|
+
this.signing = false;
|
|
10453
|
+
this.maybeSign();
|
|
10454
|
+
});
|
|
10455
|
+
}
|
|
10456
|
+
async signWave(wave) {
|
|
10457
|
+
const waiting = wave.map((item) => item.waiting);
|
|
10458
|
+
try {
|
|
10459
|
+
const programId = waiting[0].programId;
|
|
10460
|
+
for (const w of waiting) {
|
|
10461
|
+
if (!w.programId.equals(programId)) {
|
|
10462
|
+
throw new Error("Every item of a batch must target the same program id.");
|
|
10463
|
+
}
|
|
10464
|
+
}
|
|
10465
|
+
const nowSeconds = Math.floor(Date.now() / 1e3);
|
|
10466
|
+
const preimages = waiting.map(
|
|
10467
|
+
(w) => buildRelayAuthPreimage(w.endpoint, programId, w.body, this.sender, nowSeconds, w.fields)
|
|
10468
|
+
);
|
|
10469
|
+
const digests = preimages.map(relayRequestDigestHex);
|
|
10470
|
+
const issuedAt = preimages[0].auth_issued_at;
|
|
10471
|
+
const message = buildRelayBatchAuthMessage(programId, issuedAt, digests);
|
|
10472
|
+
this.waves += 1;
|
|
10473
|
+
if (!(this.signer instanceof import_web312.Keypair)) {
|
|
10474
|
+
const asTransaction = !this.signer.signMessage;
|
|
10475
|
+
this.onProgress?.(
|
|
10476
|
+
`Approve ${waiting.length} request(s) in your wallet` + (this.waves > 1 ? ` (approval ${this.waves}, for the rows that had to re-prove)` : "") + (asTransaction ? `. Your wallet shows this as a 0 SOL transfer to yourself: it is how a wallet that cannot sign messages authorizes the requests for the relay, and it is never sent to the network. Approve it on the device within a few minutes.` : `. This signs the requests for the relay, not a transaction, and must be approved within a few minutes to stay valid.`)
|
|
10477
|
+
);
|
|
10478
|
+
}
|
|
10479
|
+
const approvalStartedMs = Date.now();
|
|
10480
|
+
const { signature, mode } = await signRelayAuthPayload(this.signer, message);
|
|
10481
|
+
if (!(this.signer instanceof import_web312.Keypair)) {
|
|
10482
|
+
assertApprovalWithinFreshnessWindow(
|
|
10483
|
+
Date.now() - approvalStartedMs,
|
|
10484
|
+
REQUEST_AUTH_BATCH_MAX_AGE_SECONDS
|
|
10485
|
+
);
|
|
10486
|
+
}
|
|
10487
|
+
const signatureB64 = Buffer.from(signature).toString("base64");
|
|
10488
|
+
wave.forEach((item, i) => {
|
|
10489
|
+
const preimage = preimages[i];
|
|
10490
|
+
this.ready.push({
|
|
10491
|
+
item,
|
|
10492
|
+
fields: {
|
|
10493
|
+
sender: preimage.sender,
|
|
10494
|
+
auth_issued_at: preimage.auth_issued_at,
|
|
10495
|
+
auth_nonce: preimage.auth_nonce,
|
|
10496
|
+
auth_signature: signatureB64,
|
|
10497
|
+
...mode === "transaction" ? { auth_mode: mode } : {},
|
|
10498
|
+
// A fresh array per item: `Object.assign` puts this on the wire body, and the body is
|
|
10499
|
+
// serialized again on every network retry.
|
|
10500
|
+
auth_batch: { digests: [...digests] }
|
|
10501
|
+
}
|
|
10502
|
+
});
|
|
10503
|
+
});
|
|
10504
|
+
this.pump();
|
|
10505
|
+
} catch (error) {
|
|
10506
|
+
for (const item of wave) {
|
|
10507
|
+
const w = item.waiting;
|
|
10508
|
+
item.waiting = null;
|
|
10509
|
+
item.state = "done";
|
|
10510
|
+
w?.reject(error);
|
|
10511
|
+
}
|
|
10512
|
+
}
|
|
10513
|
+
}
|
|
10514
|
+
/** Release signed items to submit, `submitConcurrency` at a time, in signing order. */
|
|
10515
|
+
pump() {
|
|
10516
|
+
while (this.ready.length > 0 && this.slotsInUse < this.submitConcurrency) {
|
|
10517
|
+
const next = this.ready.shift();
|
|
10518
|
+
const w = next.item.waiting;
|
|
10519
|
+
next.item.waiting = null;
|
|
10520
|
+
if (next.item.state !== "waiting" || !w) continue;
|
|
10521
|
+
next.item.state = "submitting";
|
|
10522
|
+
next.item.holdsSlot = true;
|
|
10523
|
+
this.slotsInUse += 1;
|
|
10524
|
+
w.resolve(next.fields);
|
|
10525
|
+
}
|
|
10526
|
+
}
|
|
10527
|
+
};
|
|
10528
|
+
function createRelayBatchAuthCoordinator(options) {
|
|
10529
|
+
return new RelayBatchAuthCoordinator(options);
|
|
10530
|
+
}
|
|
10531
|
+
|
|
10532
|
+
// src/flows/transact-batch.ts
|
|
10533
|
+
async function transactBatch(items, options) {
|
|
10534
|
+
if (items.length === 0) {
|
|
10535
|
+
return { results: [], approvals: 0 };
|
|
10536
|
+
}
|
|
10537
|
+
if (items.length > RELAY_BATCH_AUTH_MAX_ITEMS) {
|
|
10538
|
+
throw new Error(
|
|
10539
|
+
`transactBatch accepts at most ${RELAY_BATCH_AUTH_MAX_ITEMS} items per call (got ${items.length}). Chunk the payout; see RELAY_BATCH_AUTH_MAX_ITEMS for why the practical bound is lower.`
|
|
10540
|
+
);
|
|
10541
|
+
}
|
|
10542
|
+
if (!options.relayUrl) {
|
|
10543
|
+
throw new Error("transactBatch requires relayUrl: batch approval is a relay authentication scheme.");
|
|
10544
|
+
}
|
|
10545
|
+
const { proofConcurrency, submitConcurrency, ...shared } = options;
|
|
10546
|
+
const signer = shared.depositorKeypair ? shared.depositorKeypair : (() => {
|
|
10547
|
+
const wallet = shared.walletPublicKey ?? shared.depositorPublicKey;
|
|
10548
|
+
if (!wallet) return null;
|
|
10549
|
+
if (shared.signMessage) return { walletPublicKey: wallet, signMessage: shared.signMessage };
|
|
10550
|
+
if (shared.signAuthTransaction) {
|
|
10551
|
+
return { walletPublicKey: wallet, signAuthTransaction: shared.signAuthTransaction };
|
|
10552
|
+
}
|
|
10553
|
+
return null;
|
|
10554
|
+
})();
|
|
10555
|
+
if (!signer) {
|
|
10556
|
+
throw new Error(
|
|
10557
|
+
"transactBatch requires an authenticated sender: pass `depositorKeypair` (a local Keypair), or `walletPublicKey` with `signMessage` (a browser wallet adapter) or `signAuthTransaction` (a hardware wallet). Checked up front, so nothing has been computed or submitted yet."
|
|
10558
|
+
);
|
|
10559
|
+
}
|
|
10560
|
+
const coordinator = createRelayBatchAuthCoordinator({
|
|
10561
|
+
signer,
|
|
10562
|
+
items: items.length,
|
|
10563
|
+
proofConcurrency,
|
|
10564
|
+
submitConcurrency,
|
|
10565
|
+
onProgress: shared.onProgress
|
|
10566
|
+
});
|
|
10567
|
+
const handles = items.map(() => coordinator.item());
|
|
10568
|
+
const results = new Array(items.length);
|
|
10569
|
+
const runOne = async (index) => {
|
|
10570
|
+
const item = items[index];
|
|
10571
|
+
const handle = handles[index];
|
|
10572
|
+
const label = `[batch ${index + 1}/${items.length}]`;
|
|
10573
|
+
try {
|
|
10574
|
+
const value = await transact(item.params, {
|
|
10575
|
+
...shared,
|
|
10576
|
+
...item.options,
|
|
10577
|
+
relayAuthBatch: handle,
|
|
10578
|
+
onProgress: shared.onProgress ? (status) => shared.onProgress?.(`${label} ${status}`) : void 0
|
|
10579
|
+
});
|
|
10580
|
+
results[index] = { status: "fulfilled", value };
|
|
10581
|
+
} catch (reason) {
|
|
10582
|
+
results[index] = { status: "rejected", reason };
|
|
10583
|
+
} finally {
|
|
10584
|
+
handle.finish();
|
|
10585
|
+
}
|
|
10586
|
+
};
|
|
10587
|
+
await Promise.all(items.map((_, index) => runOne(index)));
|
|
10588
|
+
return { results, approvals: coordinator.approvals };
|
|
10589
|
+
}
|
|
10590
|
+
|
|
10591
|
+
// src/scanning/scan.ts
|
|
10592
|
+
var import_bs583 = __toESM(require("bs58"), 1);
|
|
10593
|
+
var import_web313 = require("@solana/web3.js");
|
|
10594
|
+
var import_spl_token2 = require("@solana/spl-token");
|
|
10595
|
+
init_inputs();
|
|
10596
|
+
init_utxo();
|
|
10597
|
+
var bs583 = import_bs583.default.default || import_bs583.default;
|
|
10598
|
+
var TRANSACT_TAG2 = 0;
|
|
10599
|
+
var TRANSACT_SWAP_TAG3 = 1;
|
|
10600
|
+
var PROOF_LEN3 = 256;
|
|
10601
|
+
var PUBLIC_INPUTS_LEN3 = 264;
|
|
10602
|
+
var SWAP_PARAMS_LEN = 72;
|
|
10603
|
+
var CHAIN_NOTES_OFFSET_TRANSACT = 1 + PROOF_LEN3 + PUBLIC_INPUTS_LEN3;
|
|
10604
|
+
var CHAIN_NOTES_OFFSET_TRANSACT_SWAP = 1 + PROOF_LEN3 + PUBLIC_INPUTS_LEN3 + SWAP_PARAMS_LEN;
|
|
10605
|
+
var PUBLIC_INPUTS_OFFSET = 1 + PROOF_LEN3;
|
|
10606
|
+
var SWAP_OUTPUT_MINT_OFFSET = CHAIN_NOTES_OFFSET_TRANSACT;
|
|
10607
|
+
var TOKEN_PROGRAM_ID2 = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
|
|
10608
|
+
var SOL_MINT = "So11111111111111111111111111111111111111112";
|
|
10609
|
+
var USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
10610
|
+
var USDT_MINT = "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB";
|
|
10611
|
+
var PUBLIC_AMOUNT_OFFSET = 32;
|
|
10612
|
+
var EXT_DATA_HASH_OFFSET = 40;
|
|
10182
10613
|
var OUTPUT_COMMITMENT_0_OFFSET = 168;
|
|
10183
10614
|
var OUTPUT_COMMITMENT_1_OFFSET = 200;
|
|
10184
10615
|
var CHAIN_NOTE_HASH_OFFSET = 232;
|
|
@@ -10248,7 +10679,7 @@ function parseSwapOutputMint(data) {
|
|
|
10248
10679
|
const mintBytes = data.slice(SWAP_OUTPUT_MINT_OFFSET, end);
|
|
10249
10680
|
if (mintBytes.every((byte) => byte === 0)) return void 0;
|
|
10250
10681
|
try {
|
|
10251
|
-
return new
|
|
10682
|
+
return new import_web313.PublicKey(mintBytes).toBase58();
|
|
10252
10683
|
} catch {
|
|
10253
10684
|
return void 0;
|
|
10254
10685
|
}
|
|
@@ -10338,7 +10769,7 @@ function parseSwapRecipientAta(data) {
|
|
|
10338
10769
|
const recipientAtaBytes = data.slice(recipientAtaStart, recipientAtaEnd);
|
|
10339
10770
|
if (recipientAtaBytes.every((byte) => byte === 0)) return void 0;
|
|
10340
10771
|
try {
|
|
10341
|
-
return new
|
|
10772
|
+
return new import_web313.PublicKey(recipientAtaBytes).toBase58();
|
|
10342
10773
|
} catch {
|
|
10343
10774
|
return void 0;
|
|
10344
10775
|
}
|
|
@@ -10500,7 +10931,7 @@ async function scanSwapNoteCarriers(connection, programId, viewingKeyNk, swapCtx
|
|
|
10500
10931
|
let rpcCalls = 0;
|
|
10501
10932
|
const candidates = Array.from(swapCtxByCommitment.keys());
|
|
10502
10933
|
if (candidates.length === 0) return { records, rpcCalls };
|
|
10503
|
-
const [registry] =
|
|
10934
|
+
const [registry] = import_web313.PublicKey.findProgramAddressSync(
|
|
10504
10935
|
[Buffer.from("cloak_chain_note_registry")],
|
|
10505
10936
|
programId
|
|
10506
10937
|
);
|
|
@@ -10903,8 +11334,8 @@ async function scanTransactions(opts) {
|
|
|
10903
11334
|
if (onChainAta) {
|
|
10904
11335
|
try {
|
|
10905
11336
|
const expectedAta = (0, import_spl_token2.getAssociatedTokenAddressSync)(
|
|
10906
|
-
new
|
|
10907
|
-
new
|
|
11337
|
+
new import_web313.PublicKey(asset.mint),
|
|
11338
|
+
new import_web313.PublicKey(walletPublicKey)
|
|
10908
11339
|
).toBase58();
|
|
10909
11340
|
if (onChainAta === expectedAta) {
|
|
10910
11341
|
isOurs = true;
|
|
@@ -11118,7 +11549,7 @@ async function scanTransactions(opts) {
|
|
|
11118
11549
|
// deposit — that is the public deposit amount. A deposit that also merged inputs
|
|
11119
11550
|
// carries a larger output 0 and is not recoverable from public data alone.
|
|
11120
11551
|
amount: grossAmount,
|
|
11121
|
-
mintAddress: new
|
|
11552
|
+
mintAddress: new import_web313.PublicKey(asset.mint),
|
|
11122
11553
|
outputCommitments: ixCtx.outputCommitments ?? []
|
|
11123
11554
|
});
|
|
11124
11555
|
if (recoveredDeposit) {
|
|
@@ -11144,7 +11575,7 @@ async function scanTransactions(opts) {
|
|
|
11144
11575
|
noteSalt: compactNote.noteSalt,
|
|
11145
11576
|
amount: compactNote.outAmount0,
|
|
11146
11577
|
keypair: { privateKey: 0n, publicKey: compactNote.outPubkey0 },
|
|
11147
|
-
mintAddress: new
|
|
11578
|
+
mintAddress: new import_web313.PublicKey(asset.mint),
|
|
11148
11579
|
outputIndex: 0,
|
|
11149
11580
|
// v4 describes output 0, which is where change lands
|
|
11150
11581
|
outputCommitments: ixCtx.outputCommitments ?? []
|
|
@@ -11337,7 +11768,7 @@ function formatComplianceCsv(report) {
|
|
|
11337
11768
|
}
|
|
11338
11769
|
|
|
11339
11770
|
// src/wallet/utxo-wallet.ts
|
|
11340
|
-
var
|
|
11771
|
+
var import_web314 = require("@solana/web3.js");
|
|
11341
11772
|
init_utxo();
|
|
11342
11773
|
var UtxoWallet = class _UtxoWallet {
|
|
11343
11774
|
constructor(viewingKey) {
|
|
@@ -11550,7 +11981,7 @@ var UtxoWallet = class _UtxoWallet {
|
|
|
11550
11981
|
data.viewingKey ? new Uint8Array(data.viewingKey) : void 0
|
|
11551
11982
|
);
|
|
11552
11983
|
for (const w of data.wallets) {
|
|
11553
|
-
const mint = new
|
|
11984
|
+
const mint = new import_web314.PublicKey(w.mint);
|
|
11554
11985
|
for (const u of w.utxos) {
|
|
11555
11986
|
wallet.addUtxo({
|
|
11556
11987
|
amount: BigInt(u.amount),
|
|
@@ -11632,13 +12063,781 @@ var SimpleWallet = class {
|
|
|
11632
12063
|
}
|
|
11633
12064
|
};
|
|
11634
12065
|
|
|
12066
|
+
// src/bridge/rail-verify.ts
|
|
12067
|
+
var import_tweetnacl7 = __toESM(require("tweetnacl"), 1);
|
|
12068
|
+
var import_sha256 = require("@noble/hashes/sha256");
|
|
12069
|
+
var ONECLICK_PUBKEY_B58 = "reYaWhvwu8Jzo3WUM3zhn6VrhuMEF4eADL17qtRVifc";
|
|
12070
|
+
var b58 = /* @__PURE__ */ (() => {
|
|
12071
|
+
const A = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
12072
|
+
return {
|
|
12073
|
+
decode(s) {
|
|
12074
|
+
let n = 0n;
|
|
12075
|
+
for (const c of s) {
|
|
12076
|
+
const i = A.indexOf(c);
|
|
12077
|
+
if (i < 0) throw new Error("bad base58");
|
|
12078
|
+
n = n * 58n + BigInt(i);
|
|
12079
|
+
}
|
|
12080
|
+
const bytes = [];
|
|
12081
|
+
while (n > 0n) {
|
|
12082
|
+
bytes.unshift(Number(n & 255n));
|
|
12083
|
+
n >>= 8n;
|
|
12084
|
+
}
|
|
12085
|
+
for (const c of s) {
|
|
12086
|
+
if (c === "1") bytes.unshift(0);
|
|
12087
|
+
else break;
|
|
12088
|
+
}
|
|
12089
|
+
return Uint8Array.from(bytes);
|
|
12090
|
+
},
|
|
12091
|
+
encode(b) {
|
|
12092
|
+
let n = 0n;
|
|
12093
|
+
for (const x of b) n = n * 256n + BigInt(x);
|
|
12094
|
+
let s = "";
|
|
12095
|
+
while (n > 0n) {
|
|
12096
|
+
s = A[Number(n % 58n)] + s;
|
|
12097
|
+
n /= 58n;
|
|
12098
|
+
}
|
|
12099
|
+
for (const x of b) {
|
|
12100
|
+
if (x === 0) s = "1" + s;
|
|
12101
|
+
else break;
|
|
12102
|
+
}
|
|
12103
|
+
return s;
|
|
12104
|
+
}
|
|
12105
|
+
};
|
|
12106
|
+
})();
|
|
12107
|
+
function stable(v) {
|
|
12108
|
+
if (v === null || typeof v !== "object") return JSON.stringify(v) ?? "null";
|
|
12109
|
+
if (Array.isArray(v)) return `[${v.map(stable).join(",")}]`;
|
|
12110
|
+
const o = v;
|
|
12111
|
+
const parts = Object.keys(o).sort().filter((k) => o[k] !== void 0).map((k) => `${JSON.stringify(k)}:${stable(o[k])}`);
|
|
12112
|
+
return `{${parts.join(",")}}`;
|
|
12113
|
+
}
|
|
12114
|
+
function signedRequest(r) {
|
|
12115
|
+
const q = r.quoteRequest ?? {};
|
|
12116
|
+
return {
|
|
12117
|
+
dry: q.dry,
|
|
12118
|
+
swapType: q.swapType,
|
|
12119
|
+
slippageTolerance: q.slippageTolerance,
|
|
12120
|
+
originAsset: q.originAsset,
|
|
12121
|
+
depositType: q.depositType,
|
|
12122
|
+
destinationAsset: q.destinationAsset,
|
|
12123
|
+
amount: q.amount,
|
|
12124
|
+
refundTo: q.refundTo,
|
|
12125
|
+
refundType: q.refundType,
|
|
12126
|
+
recipient: q.recipient,
|
|
12127
|
+
recipientType: q.recipientType,
|
|
12128
|
+
deadline: q.deadline,
|
|
12129
|
+
quoteWaitingTimeMs: q.quoteWaitingTimeMs || void 0,
|
|
12130
|
+
referral: q.referral || void 0,
|
|
12131
|
+
virtualChainRecipient: q.virtualChainRecipient || void 0,
|
|
12132
|
+
virtualChainRefundRecipient: q.virtualChainRefundRecipient || void 0,
|
|
12133
|
+
customRecipientMsg: q.customRecipientMsg || void 0
|
|
12134
|
+
// Deliberately unsigned by the rail: sessionId, connectedWallets, correlationId, appFees,
|
|
12135
|
+
// partnerId, userAccountId, depositMode. APP FEES ARE NOT AUTHENTICATED — never display a fee
|
|
12136
|
+
// caption as if the signature vouched for it.
|
|
12137
|
+
};
|
|
12138
|
+
}
|
|
12139
|
+
function signedQuote(r) {
|
|
12140
|
+
const q = r.quote ?? {};
|
|
12141
|
+
const base = {
|
|
12142
|
+
amountIn: q.amountIn,
|
|
12143
|
+
amountInFormatted: q.amountInFormatted,
|
|
12144
|
+
amountInUsd: q.amountInUsd,
|
|
12145
|
+
minAmountIn: q.minAmountIn,
|
|
12146
|
+
amountOut: q.amountOut,
|
|
12147
|
+
amountOutFormatted: q.amountOutFormatted,
|
|
12148
|
+
amountOutUsd: q.amountOutUsd,
|
|
12149
|
+
minAmountOut: q.minAmountOut
|
|
12150
|
+
};
|
|
12151
|
+
if (r.quoteRequest?.dry) return base;
|
|
12152
|
+
return {
|
|
12153
|
+
...base,
|
|
12154
|
+
depositAddress: q.depositAddress || void 0,
|
|
12155
|
+
// <- the field that makes substitution detectable
|
|
12156
|
+
depositMemo: q.depositMemo || void 0,
|
|
12157
|
+
deadline: q.deadline || void 0,
|
|
12158
|
+
// <- a real, SIGNED expiry
|
|
12159
|
+
timeWhenInactive: q.timeWhenInactive || void 0,
|
|
12160
|
+
timeEstimate: q.timeEstimate || void 0,
|
|
12161
|
+
virtualChainRecipient: q.virtualChainRecipient || void 0,
|
|
12162
|
+
virtualChainRefundRecipient: q.virtualChainRefundRecipient || void 0,
|
|
12163
|
+
customRecipientMsg: q.customRecipientMsg || void 0,
|
|
12164
|
+
refundFee: q.refundFee || void 0,
|
|
12165
|
+
withdrawFee: q.withdrawFee || void 0
|
|
12166
|
+
};
|
|
12167
|
+
}
|
|
12168
|
+
function canonicalPayloadString(resp) {
|
|
12169
|
+
return stable({ ...signedRequest(resp), ...signedQuote(resp), timestamp: resp.timestamp });
|
|
12170
|
+
}
|
|
12171
|
+
function verifyQuoteSignature(resp) {
|
|
12172
|
+
if (!resp?.signature) return { valid: false, reason: "no signature on the response" };
|
|
12173
|
+
const payload = { ...signedRequest(resp), ...signedQuote(resp), timestamp: resp.timestamp };
|
|
12174
|
+
const digest = (0, import_sha256.sha256)(new TextEncoder().encode(stable(payload)));
|
|
12175
|
+
const message = new TextEncoder().encode(b58.encode(new Uint8Array(digest)));
|
|
12176
|
+
const sig = resp.signature.replace(/^ed25519:/, "");
|
|
12177
|
+
try {
|
|
12178
|
+
return { valid: import_tweetnacl7.default.sign.detached.verify(message, b58.decode(sig), b58.decode(ONECLICK_PUBKEY_B58)) };
|
|
12179
|
+
} catch (e) {
|
|
12180
|
+
return { valid: false, reason: e instanceof Error ? e.message : String(e) };
|
|
12181
|
+
}
|
|
12182
|
+
}
|
|
12183
|
+
|
|
12184
|
+
// src/bridge/rails.ts
|
|
12185
|
+
async function parseBridgeResponse(res, what) {
|
|
12186
|
+
let body;
|
|
12187
|
+
try {
|
|
12188
|
+
body = await res.json();
|
|
12189
|
+
} catch {
|
|
12190
|
+
throw new Error(`${what} failed: ${res.status} ${res.statusText} (response body was not JSON)`);
|
|
12191
|
+
}
|
|
12192
|
+
if (!res.ok) {
|
|
12193
|
+
const b = body ?? {};
|
|
12194
|
+
throw new Error(
|
|
12195
|
+
`${what} failed: ${b.message ?? res.statusText} (${b.code ?? res.status}${b.retryable ? ", retryable" : ""})`
|
|
12196
|
+
);
|
|
12197
|
+
}
|
|
12198
|
+
return body;
|
|
12199
|
+
}
|
|
12200
|
+
function toAttestation(raw) {
|
|
12201
|
+
if (raw.kind === "ed25519") {
|
|
12202
|
+
return { kind: "ed25519", verified: raw.verified ?? false, signer: raw.signer ?? "" };
|
|
12203
|
+
}
|
|
12204
|
+
return { kind: "none", checks: raw.checks ?? [], note: raw.note ?? "" };
|
|
12205
|
+
}
|
|
12206
|
+
async function fetchBridgeQuote(relayUrl, req) {
|
|
12207
|
+
const body = {
|
|
12208
|
+
recipient: req.recipient,
|
|
12209
|
+
origin_chain: req.originChain,
|
|
12210
|
+
amount_base_units: req.amountBaseUnits.toString(),
|
|
12211
|
+
allocate: req.allocate ?? false
|
|
12212
|
+
};
|
|
12213
|
+
if (req.refundTo !== void 0) body.refund_to = req.refundTo;
|
|
12214
|
+
if (req.rails !== void 0) body.rails = req.rails;
|
|
12215
|
+
const res = await relayFetch(`${relayUrl}/bridge/quote`, {
|
|
12216
|
+
method: "POST",
|
|
12217
|
+
headers: { "Content-Type": "application/json" },
|
|
12218
|
+
body: JSON.stringify(body)
|
|
12219
|
+
});
|
|
12220
|
+
const raw = await parseBridgeResponse(res, "bridge quote");
|
|
12221
|
+
const options = [];
|
|
12222
|
+
const unavailable = (raw.unavailable ?? []).map((p) => ({
|
|
12223
|
+
rail: p.rail,
|
|
12224
|
+
reason: p.reason
|
|
12225
|
+
}));
|
|
12226
|
+
for (const opt of raw.options) {
|
|
12227
|
+
const attestation = toAttestation(opt.attestation);
|
|
12228
|
+
if (attestation.kind === "ed25519") {
|
|
12229
|
+
const verdict = opt.rail_response ? verifyQuoteSignature(opt.rail_response) : { valid: false, reason: "no rail_response to verify against" };
|
|
12230
|
+
if (!verdict.valid) {
|
|
12231
|
+
unavailable.push({
|
|
12232
|
+
rail: opt.rail,
|
|
12233
|
+
reason: `deposit address failed independent signature verification, refusing to display it (${verdict.reason ?? "signature did not verify"})`
|
|
12234
|
+
});
|
|
12235
|
+
continue;
|
|
12236
|
+
}
|
|
12237
|
+
}
|
|
12238
|
+
options.push({
|
|
12239
|
+
rail: opt.rail,
|
|
12240
|
+
refunds: opt.refunds,
|
|
12241
|
+
addressLifetime: opt.address_lifetime,
|
|
12242
|
+
amountOut: BigInt(opt.amount_out),
|
|
12243
|
+
minAmountOut: BigInt(opt.min_amount_out),
|
|
12244
|
+
timeEstimateSeconds: opt.time_estimate_s,
|
|
12245
|
+
expiresAt: opt.expires_at,
|
|
12246
|
+
depositAddress: opt.deposit_address,
|
|
12247
|
+
attestation
|
|
12248
|
+
});
|
|
12249
|
+
}
|
|
12250
|
+
return { options, unavailable };
|
|
12251
|
+
}
|
|
12252
|
+
var STATUS_STATES = [
|
|
12253
|
+
"pending",
|
|
12254
|
+
"delivered",
|
|
12255
|
+
"refunded",
|
|
12256
|
+
"expired",
|
|
12257
|
+
"unknown"
|
|
12258
|
+
];
|
|
12259
|
+
async function fetchBridgeStatus(relayUrl, depositAddress, rail) {
|
|
12260
|
+
const qs = new URLSearchParams({ deposit_address: depositAddress, rail });
|
|
12261
|
+
const res = await relayFetch(`${relayUrl}/bridge/status?${qs.toString()}`);
|
|
12262
|
+
const raw = await parseBridgeResponse(res, "bridge status");
|
|
12263
|
+
const state = STATUS_STATES.includes(raw.state ?? "") ? raw.state : "unknown";
|
|
12264
|
+
return { rail: raw.rail ?? rail, state, detail: raw.detail ?? "" };
|
|
12265
|
+
}
|
|
12266
|
+
function cloakBridgeRail(relayUrl) {
|
|
12267
|
+
assertAllowedRelayOrigin(relayUrl);
|
|
12268
|
+
return {
|
|
12269
|
+
id: "cloak-bridge",
|
|
12270
|
+
quote: (req) => fetchBridgeQuote(relayUrl, req),
|
|
12271
|
+
status: (depositAddress, rail) => fetchBridgeStatus(relayUrl, depositAddress, rail)
|
|
12272
|
+
};
|
|
12273
|
+
}
|
|
12274
|
+
|
|
12275
|
+
// src/bridge/paymaster-client.ts
|
|
12276
|
+
var import_web315 = require("@solana/web3.js");
|
|
12277
|
+
var import_spl_token3 = require("@solana/spl-token");
|
|
12278
|
+
function validatePaymasterTopUpTransaction(tx, expect) {
|
|
12279
|
+
const ixs = tx.instructions;
|
|
12280
|
+
if (ixs.length !== 1 && ixs.length !== 2) {
|
|
12281
|
+
throw new Error(
|
|
12282
|
+
`paymaster top-up must be exactly 1 or 2 instructions, got ${ixs.length} \u2014 refusing to sign a transaction whose shape does not match the documented top-up contract`
|
|
12283
|
+
);
|
|
12284
|
+
}
|
|
12285
|
+
const ix0 = ixs[0];
|
|
12286
|
+
if (!ix0.programId.equals(import_web315.SystemProgram.programId)) {
|
|
12287
|
+
throw new Error(
|
|
12288
|
+
`paymaster top-up instruction 0 is owned by ${ix0.programId.toBase58()}, not the System Program \u2014 refusing to sign an unrecognised first instruction`
|
|
12289
|
+
);
|
|
12290
|
+
}
|
|
12291
|
+
const transfer2 = import_web315.SystemInstruction.decodeTransfer(ix0);
|
|
12292
|
+
if (!transfer2.toPubkey.equals(expect.recipient)) {
|
|
12293
|
+
throw new Error(
|
|
12294
|
+
`paymaster top-up sends SOL to ${transfer2.toPubkey.toBase58()}, which is not our recipient ${expect.recipient.toBase58()} \u2014 refusing to sign a transaction that funds a key we do not control`
|
|
12295
|
+
);
|
|
12296
|
+
}
|
|
12297
|
+
if (BigInt(transfer2.lamports) > expect.maxGrantLamports) {
|
|
12298
|
+
throw new Error(
|
|
12299
|
+
`paymaster top-up grants ${transfer2.lamports} lamports, more than the ${expect.maxGrantLamports} we asked for \u2014 refusing a transaction that funds beyond our own request`
|
|
12300
|
+
);
|
|
12301
|
+
}
|
|
12302
|
+
if (ixs.length === 1) return;
|
|
12303
|
+
const ix1 = ixs[1];
|
|
12304
|
+
if (!ix1.programId.equals(import_spl_token3.TOKEN_PROGRAM_ID)) {
|
|
12305
|
+
throw new Error(
|
|
12306
|
+
`paymaster top-up instruction 1 is owned by ${ix1.programId.toBase58()}, not the SPL Token program \u2014 refusing to sign an unrecognised second instruction`
|
|
12307
|
+
);
|
|
12308
|
+
}
|
|
12309
|
+
const spl = (0, import_spl_token3.decodeTransferInstruction)(ix1, import_spl_token3.TOKEN_PROGRAM_ID);
|
|
12310
|
+
const authority = spl.keys.owner.pubkey;
|
|
12311
|
+
if (!authority.equals(expect.recipient)) {
|
|
12312
|
+
throw new Error(
|
|
12313
|
+
`paymaster top-up's fee transfer is authorised by ${authority.toBase58()}, not our recipient ${expect.recipient.toBase58()} \u2014 refusing to sign away tokens from an account we do not control`
|
|
12314
|
+
);
|
|
12315
|
+
}
|
|
12316
|
+
const expectedDestination = (0, import_spl_token3.getAssociatedTokenAddressSync)(
|
|
12317
|
+
expect.feeMint,
|
|
12318
|
+
expect.paymentAddress,
|
|
12319
|
+
false
|
|
12320
|
+
);
|
|
12321
|
+
const destination = spl.keys.destination.pubkey;
|
|
12322
|
+
if (!destination.equals(expectedDestination)) {
|
|
12323
|
+
throw new Error(
|
|
12324
|
+
`paymaster top-up's fee transfer is addressed to ${destination.toBase58()}, not the paymaster's own payment ATA ${expectedDestination.toBase58()} \u2014 refusing to sign a payment to an unverified destination`
|
|
12325
|
+
);
|
|
12326
|
+
}
|
|
12327
|
+
const paidAmount = BigInt(spl.data.amount);
|
|
12328
|
+
if (paidAmount !== expect.feeTokenAmount) {
|
|
12329
|
+
throw new Error(
|
|
12330
|
+
`paymaster top-up's fee amount is ${paidAmount}, not the ${expect.feeTokenAmount} Kora quoted at prepare time \u2014 refusing to sign a payment that does not match the quote`
|
|
12331
|
+
);
|
|
12332
|
+
}
|
|
12333
|
+
}
|
|
12334
|
+
async function parseBridgeResponse2(res, what) {
|
|
12335
|
+
let body;
|
|
12336
|
+
try {
|
|
12337
|
+
body = await res.json();
|
|
12338
|
+
} catch {
|
|
12339
|
+
throw new Error(`${what} failed: ${res.status} ${res.statusText} (response body was not JSON)`);
|
|
12340
|
+
}
|
|
12341
|
+
if (!res.ok) {
|
|
12342
|
+
const b = body ?? {};
|
|
12343
|
+
throw new Error(
|
|
12344
|
+
`${what} failed: ${b.message ?? res.statusText} (${b.code ?? res.status}${b.retryable ? ", retryable" : ""})`
|
|
12345
|
+
);
|
|
12346
|
+
}
|
|
12347
|
+
return body;
|
|
12348
|
+
}
|
|
12349
|
+
async function fundReceiverViaPaymaster(relayUrl, receiver, grantLamports) {
|
|
12350
|
+
assertAllowedRelayOrigin(relayUrl);
|
|
12351
|
+
if (grantLamports <= 0n || grantLamports > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
12352
|
+
throw new Error(
|
|
12353
|
+
`grantLamports must be a positive value representable as a JS number, got ${grantLamports}`
|
|
12354
|
+
);
|
|
12355
|
+
}
|
|
12356
|
+
const prepareRes = await relayFetch(`${relayUrl}/bridge/paymaster/prepare`, {
|
|
12357
|
+
method: "POST",
|
|
12358
|
+
headers: { "Content-Type": "application/json" },
|
|
12359
|
+
body: JSON.stringify({
|
|
12360
|
+
recipient: receiver.publicKey.toBase58(),
|
|
12361
|
+
grant_lamports: Number(grantLamports)
|
|
12362
|
+
})
|
|
12363
|
+
});
|
|
12364
|
+
const prepared = await parseBridgeResponse2(prepareRes, "paymaster prepare");
|
|
12365
|
+
if (Math.floor(Date.now() / 1e3) >= prepared.expires_at_unix) {
|
|
12366
|
+
throw new Error(
|
|
12367
|
+
"paymaster prepare returned a voucher that is already expired \u2014 refusing to sign a stale top-up rather than fail confusingly at cosign"
|
|
12368
|
+
);
|
|
12369
|
+
}
|
|
12370
|
+
const feeMint = new import_web315.PublicKey(prepared.fee_mint);
|
|
12371
|
+
const paymentAddress = new import_web315.PublicKey(prepared.payment_address);
|
|
12372
|
+
const feeTokenAmount = BigInt(prepared.fee_token_amount);
|
|
12373
|
+
const tx = import_web315.Transaction.from(Buffer.from(prepared.transaction, "base64"));
|
|
12374
|
+
validatePaymasterTopUpTransaction(tx, {
|
|
12375
|
+
recipient: receiver.publicKey,
|
|
12376
|
+
maxGrantLamports: grantLamports,
|
|
12377
|
+
feeMint,
|
|
12378
|
+
feeTokenAmount,
|
|
12379
|
+
paymentAddress
|
|
12380
|
+
});
|
|
12381
|
+
tx.partialSign(receiver);
|
|
12382
|
+
const cosignRes = await relayFetch(`${relayUrl}/bridge/paymaster/cosign`, {
|
|
12383
|
+
method: "POST",
|
|
12384
|
+
headers: { "Content-Type": "application/json" },
|
|
12385
|
+
body: JSON.stringify({
|
|
12386
|
+
transaction: tx.serialize({ requireAllSignatures: false, verifySignatures: false }).toString("base64"),
|
|
12387
|
+
voucher: prepared.voucher
|
|
12388
|
+
})
|
|
12389
|
+
});
|
|
12390
|
+
const cosigned = await parseBridgeResponse2(cosignRes, "paymaster cosign");
|
|
12391
|
+
return {
|
|
12392
|
+
transaction: import_web315.Transaction.from(Buffer.from(cosigned.transaction, "base64")),
|
|
12393
|
+
feeTokenAmount,
|
|
12394
|
+
feeMint: prepared.fee_mint,
|
|
12395
|
+
paymentAddress: prepared.payment_address
|
|
12396
|
+
};
|
|
12397
|
+
}
|
|
12398
|
+
|
|
12399
|
+
// src/bridge/derive.ts
|
|
12400
|
+
var import_web316 = require("@solana/web3.js");
|
|
12401
|
+
var import_hmac = require("@noble/hashes/hmac");
|
|
12402
|
+
var import_sha512 = require("@noble/hashes/sha512");
|
|
12403
|
+
var BRIDGE_ESCROW_LABEL = "cloak_bridge_escrow";
|
|
12404
|
+
var MAX_RECEIVER_INDEX = 1e4;
|
|
12405
|
+
function deriveBridgeReceiver(nk, index) {
|
|
12406
|
+
if (!Number.isInteger(index) || index < 0) {
|
|
12407
|
+
throw new Error(`index must be a non-negative integer, got ${index}`);
|
|
12408
|
+
}
|
|
12409
|
+
if (index > MAX_RECEIVER_INDEX) {
|
|
12410
|
+
throw new Error(
|
|
12411
|
+
`index ${index} exceeds MAX_RECEIVER_INDEX (${MAX_RECEIVER_INDEX}). Indices must be a small sequential counter \u2014 discovery derives 0\u2026N and reads each on chain, so a timestamp index is unreachable by any scan and the deposit would be invisible to every device but this one.`
|
|
12412
|
+
);
|
|
12413
|
+
}
|
|
12414
|
+
const h = (0, import_hmac.hmac)(import_sha512.sha512, new Uint8Array(nk), new TextEncoder().encode(`${BRIDGE_ESCROW_LABEL}:${index}`));
|
|
12415
|
+
return import_web316.Keypair.fromSeed(h.slice(0, 32));
|
|
12416
|
+
}
|
|
12417
|
+
|
|
12418
|
+
// src/bridge/discover.ts
|
|
12419
|
+
var import_web317 = require("@solana/web3.js");
|
|
12420
|
+
var import_spl_token4 = require("@solana/spl-token");
|
|
12421
|
+
function describeError(err) {
|
|
12422
|
+
return err instanceof Error ? err.message : String(err);
|
|
12423
|
+
}
|
|
12424
|
+
async function listBridgeDeposits(conn, nk, opts) {
|
|
12425
|
+
const scanDepth = Math.min(opts.scanDepth ?? 20, MAX_RECEIVER_INDEX + 1);
|
|
12426
|
+
const stopAfterUnused = opts.stopAfterUnused ?? 5;
|
|
12427
|
+
const out = [];
|
|
12428
|
+
let consecutiveUnused = 0;
|
|
12429
|
+
for (let index = 0; index < scanDepth; index++) {
|
|
12430
|
+
const kp = deriveBridgeReceiver(nk, index);
|
|
12431
|
+
const receiver = kp.publicKey;
|
|
12432
|
+
const tokenAccount = (0, import_spl_token4.getAssociatedTokenAddressSync)(opts.mint, receiver, false);
|
|
12433
|
+
try {
|
|
12434
|
+
const [recvInfo, ataInfo] = await conn.getMultipleAccountsInfo([receiver, tokenAccount]);
|
|
12435
|
+
const lamports = recvInfo?.lamports ?? 0;
|
|
12436
|
+
let tokenBalance = 0n;
|
|
12437
|
+
if (ataInfo) {
|
|
12438
|
+
try {
|
|
12439
|
+
tokenBalance = BigInt((await conn.getTokenAccountBalance(tokenAccount)).value.amount);
|
|
12440
|
+
} catch (err) {
|
|
12441
|
+
if (!(err instanceof import_web317.SolanaJSONRPCError && /could not find account/i.test(err.message))) {
|
|
12442
|
+
throw err;
|
|
12443
|
+
}
|
|
12444
|
+
}
|
|
12445
|
+
}
|
|
12446
|
+
if (lamports === 0 && !ataInfo) {
|
|
12447
|
+
consecutiveUnused++;
|
|
12448
|
+
if (consecutiveUnused >= stopAfterUnused) break;
|
|
12449
|
+
continue;
|
|
12450
|
+
}
|
|
12451
|
+
consecutiveUnused = 0;
|
|
12452
|
+
const shieldSignatures = [];
|
|
12453
|
+
const sigs = await conn.getSignaturesForAddress(receiver, { limit: 20 });
|
|
12454
|
+
for (const s of sigs) {
|
|
12455
|
+
if (s.err) continue;
|
|
12456
|
+
const tx = await conn.getTransaction(s.signature, { maxSupportedTransactionVersion: 0 });
|
|
12457
|
+
const keys = tx?.transaction.message.getAccountKeys({
|
|
12458
|
+
accountKeysFromLookups: tx.meta?.loadedAddresses
|
|
12459
|
+
});
|
|
12460
|
+
if (keys && [...Array(keys.length).keys()].some((i) => keys.get(i)?.equals(opts.programId))) {
|
|
12461
|
+
shieldSignatures.push(s.signature);
|
|
12462
|
+
}
|
|
12463
|
+
}
|
|
12464
|
+
const shieldSignature = shieldSignatures[0];
|
|
12465
|
+
const indexReused = shieldSignatures.length > 1;
|
|
12466
|
+
let state;
|
|
12467
|
+
if (tokenBalance > 0n) state = shieldSignature ? "needs-cleanup" : "arrived";
|
|
12468
|
+
else if (shieldSignature) state = ataInfo ? "needs-cleanup" : "complete";
|
|
12469
|
+
else state = "awaiting";
|
|
12470
|
+
out.push({
|
|
12471
|
+
index,
|
|
12472
|
+
receiver,
|
|
12473
|
+
tokenAccount,
|
|
12474
|
+
state,
|
|
12475
|
+
tokenBalance,
|
|
12476
|
+
lamports,
|
|
12477
|
+
shieldSignature,
|
|
12478
|
+
...indexReused ? { indexReused, shieldSignatures } : {}
|
|
12479
|
+
});
|
|
12480
|
+
} catch (err) {
|
|
12481
|
+
out.push({
|
|
12482
|
+
index,
|
|
12483
|
+
receiver,
|
|
12484
|
+
tokenAccount,
|
|
12485
|
+
state: "unknown",
|
|
12486
|
+
tokenBalance: 0n,
|
|
12487
|
+
lamports: 0,
|
|
12488
|
+
error: describeError(err)
|
|
12489
|
+
});
|
|
12490
|
+
}
|
|
12491
|
+
}
|
|
12492
|
+
return out;
|
|
12493
|
+
}
|
|
12494
|
+
|
|
12495
|
+
// src/bridge/deposit-core.ts
|
|
12496
|
+
var import_web319 = require("@solana/web3.js");
|
|
12497
|
+
var import_tweetnacl8 = __toESM(require("tweetnacl"), 1);
|
|
12498
|
+
init_utxo();
|
|
12499
|
+
|
|
12500
|
+
// src/bridge/funder.ts
|
|
12501
|
+
var import_web318 = require("@solana/web3.js");
|
|
12502
|
+
var import_spl_token5 = require("@solana/spl-token");
|
|
12503
|
+
var MAINNET_RENT_0 = 890880;
|
|
12504
|
+
var MAINNET_RENT_1 = 897840;
|
|
12505
|
+
var FEE_BUDGET = 13e4;
|
|
12506
|
+
var CLEANUP_FEE_BUDGET = 5e3;
|
|
12507
|
+
var pda = (seeds, programId) => import_web318.PublicKey.findProgramAddressSync(seeds.map((s) => Buffer.from(s)), programId)[0];
|
|
12508
|
+
function deriveFundingTargets(d) {
|
|
12509
|
+
const pool = pda([Buffer.from("pool"), d.mint.toBuffer()], d.programId);
|
|
12510
|
+
return {
|
|
12511
|
+
pool,
|
|
12512
|
+
// program: NullifierAccount::derive_pda_with_pool -> [b"nullifier", pool, nullifier]
|
|
12513
|
+
nullifier0: pda([Buffer.from("nullifier"), pool.toBuffer(), d.nullifiers[0]], d.programId),
|
|
12514
|
+
nullifier1: pda([Buffer.from("nullifier"), pool.toBuffer(), d.nullifiers[1]], d.programId),
|
|
12515
|
+
// relay: derive_risk_nonce_pda -> [b"risk_nonce", bind0]
|
|
12516
|
+
riskNonce: pda([Buffer.from("risk_nonce"), d.bind0], d.programId),
|
|
12517
|
+
depositorAta: (0, import_spl_token5.getAssociatedTokenAddressSync)(d.mint, d.depositor, false)
|
|
12518
|
+
};
|
|
12519
|
+
}
|
|
12520
|
+
async function readRent(conn) {
|
|
12521
|
+
const [zero, one] = await Promise.all([
|
|
12522
|
+
conn.getMinimumBalanceForRentExemption(0),
|
|
12523
|
+
conn.getMinimumBalanceForRentExemption(1)
|
|
12524
|
+
]);
|
|
12525
|
+
return { zero, one };
|
|
12526
|
+
}
|
|
12527
|
+
|
|
12528
|
+
// src/bridge/deposit-core.ts
|
|
12529
|
+
var DepositError = class extends Error {
|
|
12530
|
+
constructor(message, fundedAccounts, measuredTxSize) {
|
|
12531
|
+
super(message);
|
|
12532
|
+
this.fundedAccounts = fundedAccounts;
|
|
12533
|
+
this.measuredTxSize = measuredTxSize;
|
|
12534
|
+
this.name = "DepositError";
|
|
12535
|
+
}
|
|
12536
|
+
};
|
|
12537
|
+
async function depositFromDerivedKey(conn, R, funder, amount, log = console.log, grantOverride, opts) {
|
|
12538
|
+
if (!opts.noteSpendKey || opts.noteSpendKey.length !== 32) {
|
|
12539
|
+
throw new Error(
|
|
12540
|
+
"opts.noteSpendKey (32 bytes) is required. It derives the note's spending key, and a note whose key is not derived from something you can reproduce is unspendable forever. Pass the same Cloak seed you derived the receiving address from."
|
|
12541
|
+
);
|
|
12542
|
+
}
|
|
12543
|
+
if (!opts.relayUrl) {
|
|
12544
|
+
throw new Error(
|
|
12545
|
+
"opts.relayUrl is required. There is no default: a missing relay used to resolve silently to the local stack, which is how a live run built a deposit against infrastructure that only existed on someone's laptop. Say which relay, every call."
|
|
12546
|
+
);
|
|
12547
|
+
}
|
|
12548
|
+
if (!opts.programId) {
|
|
12549
|
+
throw new Error(
|
|
12550
|
+
"opts.programId is required. There is no default: this is the exact field a live mainnet run once inherited from this module's local-stack constant, built a deposit against a program that does not exist on mainnet, and failed hunting a merkle tree that was never there."
|
|
12551
|
+
);
|
|
12552
|
+
}
|
|
12553
|
+
if (!opts.mint) {
|
|
12554
|
+
throw new Error(
|
|
12555
|
+
"opts.mint is required. There is no default: a deposit built for the wrong mint produces a note for an asset that never arrived at R, and there is nothing to silently fall back to."
|
|
12556
|
+
);
|
|
12557
|
+
}
|
|
12558
|
+
assertAllowedRpcConnection(conn);
|
|
12559
|
+
const relayUrl = opts.relayUrl;
|
|
12560
|
+
const programId = opts.programId;
|
|
12561
|
+
const mint = opts.mint;
|
|
12562
|
+
setCircuitsPath(resolveCircuitsBase());
|
|
12563
|
+
const grantR = grantOverride ?? MAINNET_RENT_0 + FEE_BUDGET + CLEANUP_FEE_BUDGET;
|
|
12564
|
+
const heldByR = await conn.getBalance(R.publicKey);
|
|
12565
|
+
if (heldByR >= grantR) {
|
|
12566
|
+
log(` R already holds ${heldByR} (>= ${grantR}) \u2014 no top-up needed`);
|
|
12567
|
+
} else {
|
|
12568
|
+
await (0, import_web319.sendAndConfirmTransaction)(conn, new import_web319.Transaction().add(
|
|
12569
|
+
import_web319.SystemProgram.transfer({ fromPubkey: funder.publicKey, toPubkey: R.publicKey, lamports: grantR - heldByR })
|
|
12570
|
+
), [funder]);
|
|
12571
|
+
log(` funder topped R up by ${grantR - heldByR} to ${grantR}${grantOverride !== void 0 ? " (OVERRIDDEN for a deliberate test)" : " \u2014 floor + deposit fee + cleanup fee, no rent"}`);
|
|
12572
|
+
}
|
|
12573
|
+
let measuredSize = -1;
|
|
12574
|
+
let fundedAccounts = [];
|
|
12575
|
+
const signTransaction2 = async (tx) => {
|
|
12576
|
+
if (tx instanceof import_web319.VersionedTransaction) {
|
|
12577
|
+
measuredSize = tx.serialize().length;
|
|
12578
|
+
const alts = await Promise.all(
|
|
12579
|
+
tx.message.addressTableLookups.map(async (l) => (await conn.getAddressLookupTable(l.accountKey)).value)
|
|
12580
|
+
);
|
|
12581
|
+
const keys = tx.message.getAccountKeys({ addressLookupTableAccounts: alts });
|
|
12582
|
+
const ix = tx.message.compiledInstructions.find((i) => keys.get(i.programIdIndex).equals(programId));
|
|
12583
|
+
const at = (n) => keys.get(ix.accountKeyIndexes[n]);
|
|
12584
|
+
const targets = [
|
|
12585
|
+
{ a: at(12), need: MAINNET_RENT_0, name: "risk_nonce" },
|
|
12586
|
+
{ a: at(4), need: MAINNET_RENT_1, name: "nullifier_0" },
|
|
12587
|
+
{ a: at(5), need: MAINNET_RENT_1, name: "nullifier_1" }
|
|
12588
|
+
];
|
|
12589
|
+
const infos = await conn.getMultipleAccountsInfo(targets.map((t) => t.a));
|
|
12590
|
+
fundedAccounts = targets.map((t, i) => ({
|
|
12591
|
+
name: t.name,
|
|
12592
|
+
address: t.a.toBase58(),
|
|
12593
|
+
lamportsSent: Math.max(0, t.need - (infos[i]?.lamports ?? 0))
|
|
12594
|
+
}));
|
|
12595
|
+
const ixs = targets.flatMap((t, i) => (infos[i]?.lamports ?? 0) >= t.need ? [] : [import_web319.SystemProgram.transfer({
|
|
12596
|
+
fromPubkey: funder.publicKey,
|
|
12597
|
+
toPubkey: t.a,
|
|
12598
|
+
lamports: t.need - (infos[i]?.lamports ?? 0)
|
|
12599
|
+
})]);
|
|
12600
|
+
if (ixs.length) {
|
|
12601
|
+
const sig = await (0, import_web319.sendAndConfirmTransaction)(conn, new import_web319.Transaction().add(...ixs), [funder]);
|
|
12602
|
+
log(` seam: funded ${ixs.length} program accounts read off the built tx \u2014 ${sig.slice(0, 16)}\u2026`);
|
|
12603
|
+
}
|
|
12604
|
+
tx.sign([R]);
|
|
12605
|
+
return tx;
|
|
12606
|
+
}
|
|
12607
|
+
tx.partialSign(R);
|
|
12608
|
+
return tx;
|
|
12609
|
+
};
|
|
12610
|
+
const signMessage = async (m) => import_tweetnacl8.default.sign.detached(m, R.secretKey);
|
|
12611
|
+
const utxoKeypair = await deriveUtxoKeypairFromSpendKey(opts.noteSpendKey);
|
|
12612
|
+
const nk = getNkFromUtxoPrivateKey(utxoKeypair.privateKey);
|
|
12613
|
+
const { utxo, noteSalt } = await createRecoverableDepositUtxo(amount, nk, mint);
|
|
12614
|
+
const zeroInput = await createZeroUtxo(mint);
|
|
12615
|
+
const rBefore = await conn.getBalance(R.publicKey);
|
|
12616
|
+
let result;
|
|
12617
|
+
try {
|
|
12618
|
+
result = await transact(
|
|
12619
|
+
{ inputUtxos: [zeroInput], outputUtxos: [utxo], externalAmount: amount, depositor: R.publicKey },
|
|
12620
|
+
{
|
|
12621
|
+
connection: conn,
|
|
12622
|
+
programId,
|
|
12623
|
+
relayUrl,
|
|
12624
|
+
// NO depositorKeypair: the SDK branches `if (keypair) … else if (signTransaction)`
|
|
12625
|
+
// (transact.ts:2786, :2936), so passing one silently skips the funding seam.
|
|
12626
|
+
depositorPublicKey: R.publicKey,
|
|
12627
|
+
walletPublicKey: R.publicKey,
|
|
12628
|
+
signTransaction: signTransaction2,
|
|
12629
|
+
signMessage,
|
|
12630
|
+
chainNoteViewingKeyNk: nk,
|
|
12631
|
+
chainNoteSalt: noteSalt,
|
|
12632
|
+
relaySupplementalAlt: true,
|
|
12633
|
+
onProgress: (s) => log(` \xB7 ${s}`)
|
|
12634
|
+
}
|
|
12635
|
+
);
|
|
12636
|
+
} catch (e) {
|
|
12637
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
12638
|
+
throw new DepositError(msg, fundedAccounts, measuredSize);
|
|
12639
|
+
}
|
|
12640
|
+
const rAfter = await conn.getBalance(R.publicKey);
|
|
12641
|
+
const noteIndex = result.outputUtxos[0].index;
|
|
12642
|
+
if (noteIndex === void 0) throw new Error("deposit landed but the note has no index \u2014 it cannot be discovered later");
|
|
12643
|
+
return {
|
|
12644
|
+
signature: result.signature,
|
|
12645
|
+
noteIndex,
|
|
12646
|
+
amount: result.outputUtxos[0].amount,
|
|
12647
|
+
txSize: measuredSize,
|
|
12648
|
+
rBefore,
|
|
12649
|
+
rAfter,
|
|
12650
|
+
rentExempt: rAfter >= MAINNET_RENT_0,
|
|
12651
|
+
inputNullifiers: result.inputNullifiers,
|
|
12652
|
+
fundedAccounts
|
|
12653
|
+
};
|
|
12654
|
+
}
|
|
12655
|
+
|
|
12656
|
+
// src/bridge/cleanup.ts
|
|
12657
|
+
var import_web320 = require("@solana/web3.js");
|
|
12658
|
+
var import_spl_token6 = require("@solana/spl-token");
|
|
12659
|
+
var DEFAULT_MINT = new import_web320.PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
|
|
12660
|
+
async function cleanupReceivingAddress(conn, R, dustDestination, mint = DEFAULT_MINT) {
|
|
12661
|
+
assertAllowedRpcConnection(conn);
|
|
12662
|
+
const rAta = (0, import_spl_token6.getAssociatedTokenAddressSync)(mint, R.publicKey);
|
|
12663
|
+
const info = await conn.getAccountInfo(rAta);
|
|
12664
|
+
if (!info) {
|
|
12665
|
+
return {
|
|
12666
|
+
closed: false,
|
|
12667
|
+
dustSwept: 0n,
|
|
12668
|
+
rentReturned: 0,
|
|
12669
|
+
destination: null,
|
|
12670
|
+
signature: null,
|
|
12671
|
+
note: "no token account \u2014 nothing to recover"
|
|
12672
|
+
};
|
|
12673
|
+
}
|
|
12674
|
+
const bal = BigInt((await conn.getTokenAccountBalance(rAta)).value.amount);
|
|
12675
|
+
const rentHeld = info.lamports;
|
|
12676
|
+
const before = await conn.getBalance(R.publicKey);
|
|
12677
|
+
const ixs = [];
|
|
12678
|
+
let destination = null;
|
|
12679
|
+
if (bal > 0n && !dustDestination) {
|
|
12680
|
+
return {
|
|
12681
|
+
closed: false,
|
|
12682
|
+
dustSwept: 0n,
|
|
12683
|
+
rentReturned: 0,
|
|
12684
|
+
destination: null,
|
|
12685
|
+
signature: null,
|
|
12686
|
+
note: `dusted with ${bal} base units after the deposit; leaving the account open and ${rentHeld.toLocaleString()} lamports of rent unrecovered, because sweeping it would publish a link from this deposit to whoever receives it`
|
|
12687
|
+
};
|
|
12688
|
+
}
|
|
12689
|
+
if (bal > 0n && dustDestination) {
|
|
12690
|
+
const destAta = (0, import_spl_token6.getAssociatedTokenAddressSync)(mint, dustDestination);
|
|
12691
|
+
const destInfo = await conn.getAccountInfo(destAta);
|
|
12692
|
+
if (!destInfo) {
|
|
12693
|
+
const ataRent = await (0, import_spl_token6.getMinimumBalanceForRentExemptAccount)(conn);
|
|
12694
|
+
if (before < ataRent + CLEANUP_FEE_BUDGET) {
|
|
12695
|
+
throw new Error(
|
|
12696
|
+
`dustDestination ${dustDestination.toBase58()} has no token account for mint ${mint.toBase58()}, and creating one costs ${ataRent} lamports of rent that R does not have: R holds ${before}, which deposit-core.ts reserved only for this transaction's own fee (CLEANUP_FEE_BUDGET = ${CLEANUP_FEE_BUDGET} in funder.ts), not for a fresh account's rent. Fund R with at least ${ataRent + CLEANUP_FEE_BUDGET - before} more lamports first, or choose a destination that already has a token account for this mint.`
|
|
12697
|
+
);
|
|
12698
|
+
}
|
|
12699
|
+
ixs.push((0, import_spl_token6.createAssociatedTokenAccountIdempotentInstruction)(R.publicKey, destAta, dustDestination, mint));
|
|
12700
|
+
}
|
|
12701
|
+
ixs.push((0, import_spl_token6.createTransferInstruction)(rAta, destAta, R.publicKey, bal));
|
|
12702
|
+
destination = destAta.toBase58();
|
|
12703
|
+
}
|
|
12704
|
+
ixs.push((0, import_spl_token6.createCloseAccountInstruction)(rAta, R.publicKey, R.publicKey));
|
|
12705
|
+
const signature = await (0, import_web320.sendAndConfirmTransaction)(conn, new import_web320.Transaction().add(...ixs), [R], {
|
|
12706
|
+
commitment: "confirmed"
|
|
12707
|
+
});
|
|
12708
|
+
const after = await conn.getBalance(R.publicKey);
|
|
12709
|
+
const gone = await conn.getAccountInfo(rAta) === null;
|
|
12710
|
+
return {
|
|
12711
|
+
closed: gone,
|
|
12712
|
+
dustSwept: bal,
|
|
12713
|
+
rentReturned: after - before,
|
|
12714
|
+
destination,
|
|
12715
|
+
signature,
|
|
12716
|
+
note: bal > 0n ? `swept ${bal} base units of dust to the caller-chosen destination and closed, in one transaction \u2014 note this publishes a link from the deposit to that destination` : `closed cleanly, nothing to sweep`
|
|
12717
|
+
};
|
|
12718
|
+
}
|
|
12719
|
+
|
|
12720
|
+
// src/bridge/quote.ts
|
|
12721
|
+
var MIN_DEPOSIT_SPL_BASE_UNITS = 1000000n;
|
|
12722
|
+
var WITHDRAW_FIXED_FEE = 450000n;
|
|
12723
|
+
var WITHDRAW_FEE_BPS = 30n;
|
|
12724
|
+
function withdrawFeeAt(amount, fixedFee, feeBps) {
|
|
12725
|
+
return fixedFee + amount * feeBps / 10000n;
|
|
12726
|
+
}
|
|
12727
|
+
var MAX_FEE_FRACTION = 0.063;
|
|
12728
|
+
var FIXED_ROUND_TRIP = 903000n;
|
|
12729
|
+
var ECONOMIC_MINIMUM = FIXED_ROUND_TRIP * 1000000n / BigInt(Math.round((MAX_FEE_FRACTION - 3e-3) * 1e6));
|
|
12730
|
+
function withdrawFeeFor(amount) {
|
|
12731
|
+
return withdrawFeeAt(amount, WITHDRAW_FIXED_FEE, WITHDRAW_FEE_BPS);
|
|
12732
|
+
}
|
|
12733
|
+
function assessBridgeQuote(q) {
|
|
12734
|
+
if (q.sent < 0n) {
|
|
12735
|
+
throw new Error(`assessBridgeQuote: sent must not be negative (got ${q.sent}).`);
|
|
12736
|
+
}
|
|
12737
|
+
if (q.arrivesMin > q.sent) {
|
|
12738
|
+
throw new Error(
|
|
12739
|
+
`assessBridgeQuote: arrivesMin (${q.arrivesMin}) exceeds sent (${q.sent}) \u2014 no rail returns more than it was given, so this input did not come from a real quote.`
|
|
12740
|
+
);
|
|
12741
|
+
}
|
|
12742
|
+
const shieldedMinRaw = q.arrivesMin - q.paymasterFee;
|
|
12743
|
+
const shieldedTargetRaw = q.arrivesTarget - q.paymasterFee;
|
|
12744
|
+
const shieldedMin = shieldedMinRaw > 0n ? shieldedMinRaw : 0n;
|
|
12745
|
+
const shieldedTarget = shieldedTargetRaw > 0n ? shieldedTargetRaw : 0n;
|
|
12746
|
+
const viable = shieldedMinRaw >= MIN_DEPOSIT_SPL_BASE_UNITS;
|
|
12747
|
+
const economic = q.sent >= ECONOMIC_MINIMUM;
|
|
12748
|
+
const withdrawFee = shieldedMin > 0n ? withdrawFeeAt(shieldedMin, q.liveWithdrawFixedFee ?? WITHDRAW_FIXED_FEE, q.liveWithdrawFeeBps ?? WITHDRAW_FEE_BPS) : 0n;
|
|
12749
|
+
const roundTripCost = q.sent - (shieldedMin > withdrawFee ? shieldedMin - withdrawFee : 0n);
|
|
12750
|
+
const roundTripFraction = q.sent > 0n ? Number(roundTripCost) / Number(q.sent) : 0;
|
|
12751
|
+
const reasons = [];
|
|
12752
|
+
if (!viable) {
|
|
12753
|
+
const short = MIN_DEPOSIT_SPL_BASE_UNITS - shieldedMinRaw;
|
|
12754
|
+
reasons.push(
|
|
12755
|
+
(shieldedMinRaw <= 0n ? `The ${fmt(q.paymasterFee)} paymaster fee alone is ${shieldedMinRaw < 0n ? `${fmt(-shieldedMinRaw)} more than` : "all of"} the ${fmt(q.arrivesMin)} the rail guarantees, so nothing would be left to shield.` : `Only ${fmt(shieldedMinRaw)} would be left to shield after the ${fmt(q.paymasterFee)} paymaster fee.`) + ` The program's minimum deposit is ${fmt(MIN_DEPOSIT_SPL_BASE_UNITS)}. Send at least ${fmt(q.sent + short)} to clear it.`
|
|
12756
|
+
);
|
|
12757
|
+
}
|
|
12758
|
+
if (viable && !economic) {
|
|
12759
|
+
reasons.push(
|
|
12760
|
+
`This works, but ${fmt(roundTripCost)} of ${fmt(q.sent)} goes to fees (${(roundTripFraction * 100).toFixed(0)}%). The costs are almost all fixed, so bridging ${fmt(ECONOMIC_MINIMUM)} or more spreads them much further.`
|
|
12761
|
+
);
|
|
12762
|
+
}
|
|
12763
|
+
return {
|
|
12764
|
+
...q,
|
|
12765
|
+
shieldedMin,
|
|
12766
|
+
shieldedTarget,
|
|
12767
|
+
viable,
|
|
12768
|
+
economic,
|
|
12769
|
+
withdrawFee,
|
|
12770
|
+
roundTripCost,
|
|
12771
|
+
roundTripFraction,
|
|
12772
|
+
reasons
|
|
12773
|
+
};
|
|
12774
|
+
}
|
|
12775
|
+
var fmt = (v) => `${(Number(v) / 1e6).toFixed(6)} USDC`;
|
|
12776
|
+
function renderAssessment(a) {
|
|
12777
|
+
const L = [];
|
|
12778
|
+
L.push(` you send ${fmt(a.sent)}`);
|
|
12779
|
+
L.push(` arrives at least ${fmt(a.arrivesMin)} (target ${fmt(a.arrivesTarget)}, not a promise)`);
|
|
12780
|
+
L.push(` paymaster fee ${fmt(a.paymasterFee)} buys the receiver its SOL, so your wallet stays off chain`);
|
|
12781
|
+
L.push(` SHIELDED ${fmt(a.shieldedMin)} at least \u2014 this is what you end up with`);
|
|
12782
|
+
L.push(` to withdraw later ${fmt(a.withdrawFee)} the program's fee, whenever you take it out`);
|
|
12783
|
+
for (const r of a.reasons) L.push(`
|
|
12784
|
+
${a.viable ? "NOTE" : "REFUSED"}: ${r}`);
|
|
12785
|
+
return L.join("\n");
|
|
12786
|
+
}
|
|
12787
|
+
|
|
12788
|
+
// src/bridge/retry.ts
|
|
12789
|
+
var TERMINAL_FAILURE = /DepositTooSmall|minimum is 1\.000000|insufficient|holds only|below the program|kora wants|over the .* ceiling/i;
|
|
12790
|
+
function isTerminalFailure(message) {
|
|
12791
|
+
return TERMINAL_FAILURE.test(message);
|
|
12792
|
+
}
|
|
12793
|
+
function classifyPostFailure(attempted, after) {
|
|
12794
|
+
if (after === null) return "unknown";
|
|
12795
|
+
if (after === 0n) return "landed";
|
|
12796
|
+
if (after >= attempted) return "did-not-land";
|
|
12797
|
+
return "unknown";
|
|
12798
|
+
}
|
|
12799
|
+
function mayRetry(verdict, message) {
|
|
12800
|
+
return verdict === "did-not-land" && !isTerminalFailure(message);
|
|
12801
|
+
}
|
|
12802
|
+
function retryDelayMs(attempt) {
|
|
12803
|
+
return 15e3 * Math.max(0, attempt - 1);
|
|
12804
|
+
}
|
|
12805
|
+
var DoNotRetry = class extends Error {
|
|
12806
|
+
constructor(message) {
|
|
12807
|
+
super(message);
|
|
12808
|
+
this.name = "DoNotRetry";
|
|
12809
|
+
}
|
|
12810
|
+
};
|
|
12811
|
+
async function withShieldRetries(attempt, hooks = {}) {
|
|
12812
|
+
const attempts = hooks.attempts ?? 3;
|
|
12813
|
+
const sleep4 = hooks.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
12814
|
+
let last = "";
|
|
12815
|
+
for (let n = 1; n <= attempts; n++) {
|
|
12816
|
+
if (n > 1) {
|
|
12817
|
+
const delay = retryDelayMs(n);
|
|
12818
|
+
hooks.onRetry?.(n, delay, last);
|
|
12819
|
+
await sleep4(delay);
|
|
12820
|
+
}
|
|
12821
|
+
try {
|
|
12822
|
+
return await attempt(n);
|
|
12823
|
+
} catch (e) {
|
|
12824
|
+
if (e instanceof DoNotRetry) throw e;
|
|
12825
|
+
last = e instanceof Error ? e.message : String(e);
|
|
12826
|
+
if (isTerminalFailure(last) || n === attempts) throw e;
|
|
12827
|
+
}
|
|
12828
|
+
}
|
|
12829
|
+
throw new Error("unreachable");
|
|
12830
|
+
}
|
|
12831
|
+
|
|
11635
12832
|
// src/index.ts
|
|
11636
|
-
var VERSION = "0.2.
|
|
12833
|
+
var VERSION = "0.2.3";
|
|
11637
12834
|
var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
11638
12835
|
// Annotate the CommonJS export names for ESM import in node:
|
|
11639
12836
|
0 && (module.exports = {
|
|
12837
|
+
BRIDGE_ESCROW_LABEL,
|
|
11640
12838
|
BUILD_ALLOWS_LOCAL_ENDPOINTS,
|
|
11641
12839
|
CHAIN_NOTE_SALT_BITS,
|
|
12840
|
+
CLEANUP_FEE_BUDGET,
|
|
11642
12841
|
CLOAK_PRODUCTION_RELAY_URL,
|
|
11643
12842
|
CLOAK_PROGRAM_ID,
|
|
11644
12843
|
CloakError,
|
|
@@ -11646,25 +12845,38 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
|
11646
12845
|
DEFAULT_TRANSACTION_CIRCUITS_URL,
|
|
11647
12846
|
DELIVERY_MEMO_TAG,
|
|
11648
12847
|
DELIVERY_REGISTRY_SEED,
|
|
12848
|
+
DepositError,
|
|
12849
|
+
DoNotRetry,
|
|
12850
|
+
ECONOMIC_MINIMUM,
|
|
11649
12851
|
EXPECTED_CIRCUIT_HASHES,
|
|
12852
|
+
FEE_BUDGET,
|
|
11650
12853
|
FIXED_FEE_LAMPORTS,
|
|
11651
12854
|
InsecureRandomnessError,
|
|
11652
12855
|
LAMPORTS_PER_SOL,
|
|
11653
12856
|
LocalStorageAdapter,
|
|
12857
|
+
MAINNET_RENT_0,
|
|
12858
|
+
MAINNET_RENT_1,
|
|
12859
|
+
MAX_RECEIVER_INDEX,
|
|
11654
12860
|
MERKLE_TREE_HEIGHT,
|
|
11655
12861
|
MIN_DEPOSIT_LAMPORTS,
|
|
12862
|
+
MIN_DEPOSIT_SPL_BASE_UNITS,
|
|
11656
12863
|
MemoryStorageAdapter,
|
|
11657
12864
|
MerkleTree,
|
|
11658
12865
|
NATIVE_SOL_MINT,
|
|
12866
|
+
ONECLICK_PUBKEY_B58,
|
|
11659
12867
|
RECIPIENT_DELIVERY_CIPHERTEXT_LEN,
|
|
11660
12868
|
RECIPIENT_DELIVERY_EPHEMERAL_PK_LEN,
|
|
11661
12869
|
RECIPIENT_DELIVERY_NONCE_LEN,
|
|
11662
12870
|
RECIPIENT_DELIVERY_NOTE_BYTES,
|
|
11663
12871
|
RECIPIENT_DELIVERY_PLAINTEXT_LEN,
|
|
11664
12872
|
RECIPIENT_DELIVERY_TAG_LEN,
|
|
12873
|
+
RELAY_BATCH_AUTH_MAX_ITEMS,
|
|
11665
12874
|
RELAY_ORIGIN_ALLOWLIST,
|
|
12875
|
+
REQUEST_AUTH_BATCH_DOMAIN,
|
|
12876
|
+
REQUEST_AUTH_BATCH_MAX_AGE_SECONDS,
|
|
11666
12877
|
REQUEST_AUTH_MAX_AGE_SECONDS,
|
|
11667
12878
|
REQUEST_AUTH_MAX_FUTURE_SKEW_SECONDS,
|
|
12879
|
+
RelayBatchAuthCoordinator,
|
|
11668
12880
|
RelayInternalError,
|
|
11669
12881
|
RelayService,
|
|
11670
12882
|
RootNotFoundError,
|
|
@@ -11674,6 +12886,7 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
|
11674
12886
|
SettlementVerificationError,
|
|
11675
12887
|
ShieldPoolErrors,
|
|
11676
12888
|
SimpleWallet,
|
|
12889
|
+
TERMINAL_FAILURE,
|
|
11677
12890
|
TRANSACTION_CIRCUITS_VERSION,
|
|
11678
12891
|
TRANSACT_AUTH_FIELDS,
|
|
11679
12892
|
TRANSACT_SWAP_AUTH_FIELDS,
|
|
@@ -11683,15 +12896,21 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
|
11683
12896
|
VARIABLE_FEE_NUMERATOR,
|
|
11684
12897
|
VARIABLE_FEE_RATE,
|
|
11685
12898
|
VERSION,
|
|
12899
|
+
WITHDRAW_FEE_BPS,
|
|
12900
|
+
WITHDRAW_FIXED_FEE,
|
|
11686
12901
|
assertDirectSubmissionLanded,
|
|
12902
|
+
assertInputMints,
|
|
11687
12903
|
assertTransactionCircuitIntegrity,
|
|
12904
|
+
assessBridgeQuote,
|
|
11688
12905
|
bigintToBytes32,
|
|
11689
12906
|
bigintToHex,
|
|
12907
|
+
buildAuthTransactionMessage,
|
|
11690
12908
|
buildMerkleTree,
|
|
11691
12909
|
buildMerkleTreeFromChain,
|
|
11692
12910
|
buildMerkleTreeFromRelay,
|
|
11693
12911
|
buildRecipientDeliveryNotes,
|
|
11694
12912
|
buildRelayAuthPreimage,
|
|
12913
|
+
buildRelayBatchAuthMessage,
|
|
11695
12914
|
buildTransactRequestBody,
|
|
11696
12915
|
bytesToHex,
|
|
11697
12916
|
calculateFee,
|
|
@@ -11699,12 +12918,16 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
|
11699
12918
|
calculateRelayFee,
|
|
11700
12919
|
canRebuildMerkleTreeFromChain,
|
|
11701
12920
|
canonicalJson,
|
|
12921
|
+
canonicalPayloadString,
|
|
11702
12922
|
chainNoteFromBase64,
|
|
11703
12923
|
chainNoteToBase64,
|
|
12924
|
+
classifyPostFailure,
|
|
11704
12925
|
classifyRelayError,
|
|
12926
|
+
cleanupReceivingAddress,
|
|
11705
12927
|
cleanupStalePendingOperations,
|
|
11706
12928
|
clearPendingDeposits,
|
|
11707
12929
|
clearPendingWithdrawals,
|
|
12930
|
+
cloakBridgeRail,
|
|
11708
12931
|
computeChainNoteHash,
|
|
11709
12932
|
computeExtDataHash,
|
|
11710
12933
|
computeMerkleRoot,
|
|
@@ -11721,15 +12944,19 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
|
11721
12944
|
createLogger,
|
|
11722
12945
|
createRecoverableChangeUtxo,
|
|
11723
12946
|
createRecoverableDepositUtxo,
|
|
12947
|
+
createRelayBatchAuthCoordinator,
|
|
11724
12948
|
createUtxo,
|
|
11725
12949
|
createZeroUtxo,
|
|
11726
12950
|
decryptCompactChainNote,
|
|
11727
12951
|
decryptComplianceMetadataWithMasterKey,
|
|
11728
12952
|
decryptTransactionMetadata,
|
|
12953
|
+
depositFromDerivedKey,
|
|
12954
|
+
deriveBridgeReceiver,
|
|
11729
12955
|
deriveChangeNoteBlinding,
|
|
11730
12956
|
deriveDepositNoteSecrets,
|
|
11731
12957
|
deriveDiversifiedViewingKey,
|
|
11732
12958
|
deriveDiversifier,
|
|
12959
|
+
deriveFundingTargets,
|
|
11733
12960
|
deriveInputNullifierPdas,
|
|
11734
12961
|
derivePublicKey,
|
|
11735
12962
|
deriveSpendKey,
|
|
@@ -11768,6 +12995,7 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
|
11768
12995
|
formatErrorForLogging,
|
|
11769
12996
|
formatSol,
|
|
11770
12997
|
fullWithdraw,
|
|
12998
|
+
fundReceiverViaPaymaster,
|
|
11771
12999
|
generateCloakKeys,
|
|
11772
13000
|
generateCommitmentAsync,
|
|
11773
13001
|
generateMasterSeed,
|
|
@@ -11803,18 +13031,21 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
|
11803
13031
|
isReactNative,
|
|
11804
13032
|
isRootNotFoundError,
|
|
11805
13033
|
isSubmissionOutcomeUnknownResponse,
|
|
13034
|
+
isTerminalFailure,
|
|
11806
13035
|
isValidHex,
|
|
11807
13036
|
isValidRpcUrl,
|
|
11808
13037
|
isValidSolanaAddress,
|
|
11809
13038
|
isWithdrawAmountSufficient,
|
|
11810
13039
|
isWithdrawable,
|
|
11811
13040
|
keypairToAdapter,
|
|
13041
|
+
listBridgeDeposits,
|
|
11812
13042
|
loadPendingDeposits,
|
|
11813
13043
|
loadPendingWithdrawals,
|
|
11814
13044
|
loadVerifiedCircuitArtifacts,
|
|
11815
13045
|
matchChangeNote,
|
|
11816
13046
|
matchDepositNote,
|
|
11817
13047
|
matchSwapRefundLeaf,
|
|
13048
|
+
mayRetry,
|
|
11818
13049
|
openRecipientDeliveryNote,
|
|
11819
13050
|
parseAmount,
|
|
11820
13051
|
parseDeliveryCarrierMemo,
|
|
@@ -11837,11 +13068,15 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
|
11837
13068
|
randomDepositNoteSalt,
|
|
11838
13069
|
randomFieldElement,
|
|
11839
13070
|
readMerkleTreeState,
|
|
13071
|
+
readRent,
|
|
11840
13072
|
recipientDeliveryNoteToBase64,
|
|
11841
13073
|
registerViewingKey,
|
|
13074
|
+
relayRequestDigestHex,
|
|
11842
13075
|
removePendingDeposit,
|
|
11843
13076
|
removePendingWithdrawal,
|
|
13077
|
+
renderAssessment,
|
|
11844
13078
|
resolveCircuitsBase,
|
|
13079
|
+
retryDelayMs,
|
|
11845
13080
|
savePendingDeposit,
|
|
11846
13081
|
savePendingWithdrawal,
|
|
11847
13082
|
scanNotesForWallet,
|
|
@@ -11850,10 +13085,12 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
|
11850
13085
|
sdkLogger,
|
|
11851
13086
|
selectUtxos,
|
|
11852
13087
|
sendTransaction,
|
|
13088
|
+
serializeAuthTransactionMessage,
|
|
11853
13089
|
serializeNote,
|
|
11854
13090
|
serializeUtxo,
|
|
11855
13091
|
setCircuitsPath,
|
|
11856
13092
|
setDebugMode,
|
|
13093
|
+
signRelayAuthPayload,
|
|
11857
13094
|
signTransaction,
|
|
11858
13095
|
splitTo2Limbs,
|
|
11859
13096
|
submitTransactToRelay,
|
|
@@ -11862,6 +13099,7 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
|
11862
13099
|
swapWithChange,
|
|
11863
13100
|
toComplianceReport,
|
|
11864
13101
|
transact,
|
|
13102
|
+
transactBatch,
|
|
11865
13103
|
transfer,
|
|
11866
13104
|
truncate,
|
|
11867
13105
|
tryDecryptNote,
|
|
@@ -11874,13 +13112,17 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
|
11874
13112
|
validateDepositParams,
|
|
11875
13113
|
validateNote,
|
|
11876
13114
|
validateOutputsSum,
|
|
13115
|
+
validatePaymasterTopUpTransaction,
|
|
11877
13116
|
validateRoot,
|
|
11878
13117
|
validateTransfers,
|
|
11879
13118
|
validateWalletConnected,
|
|
11880
13119
|
validateWithdrawableNote,
|
|
11881
13120
|
verifyAllCircuits,
|
|
11882
13121
|
verifyCircuitIntegrity,
|
|
13122
|
+
verifyQuoteSignature,
|
|
11883
13123
|
verifyUtxos,
|
|
11884
13124
|
waitForRoot,
|
|
11885
|
-
|
|
13125
|
+
withShieldRetries,
|
|
13126
|
+
withTiming,
|
|
13127
|
+
withdrawFeeFor
|
|
11886
13128
|
});
|