@cloak.dev/sdk 0.2.2 → 0.2.3-staging.f2d7f2a
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 +874 -16
- package/dist/index.d.cts +725 -2
- package/dist/index.d.ts +725 -2
- package/dist/index.js +858 -15
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -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,16 +553,25 @@ __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,
|
|
@@ -579,6 +590,7 @@ __export(index_exports, {
|
|
|
579
590
|
SettlementVerificationError: () => SettlementVerificationError,
|
|
580
591
|
ShieldPoolErrors: () => ShieldPoolErrors,
|
|
581
592
|
SimpleWallet: () => SimpleWallet,
|
|
593
|
+
TERMINAL_FAILURE: () => TERMINAL_FAILURE,
|
|
582
594
|
TRANSACTION_CIRCUITS_VERSION: () => TRANSACTION_CIRCUITS_VERSION,
|
|
583
595
|
TRANSACT_AUTH_FIELDS: () => TRANSACT_AUTH_FIELDS,
|
|
584
596
|
TRANSACT_SWAP_AUTH_FIELDS: () => TRANSACT_SWAP_AUTH_FIELDS,
|
|
@@ -588,8 +600,12 @@ __export(index_exports, {
|
|
|
588
600
|
VARIABLE_FEE_NUMERATOR: () => VARIABLE_FEE_NUMERATOR,
|
|
589
601
|
VARIABLE_FEE_RATE: () => VARIABLE_FEE_RATE,
|
|
590
602
|
VERSION: () => VERSION,
|
|
603
|
+
WITHDRAW_FEE_BPS: () => WITHDRAW_FEE_BPS,
|
|
604
|
+
WITHDRAW_FIXED_FEE: () => WITHDRAW_FIXED_FEE,
|
|
591
605
|
assertDirectSubmissionLanded: () => assertDirectSubmissionLanded,
|
|
606
|
+
assertInputMints: () => assertInputMints,
|
|
592
607
|
assertTransactionCircuitIntegrity: () => assertTransactionCircuitIntegrity,
|
|
608
|
+
assessBridgeQuote: () => assessBridgeQuote,
|
|
593
609
|
bigintToBytes32: () => bigintToBytes32,
|
|
594
610
|
bigintToHex: () => bigintToHex,
|
|
595
611
|
buildMerkleTree: () => buildMerkleTree,
|
|
@@ -604,12 +620,16 @@ __export(index_exports, {
|
|
|
604
620
|
calculateRelayFee: () => calculateRelayFee,
|
|
605
621
|
canRebuildMerkleTreeFromChain: () => canRebuildMerkleTreeFromChain,
|
|
606
622
|
canonicalJson: () => canonicalJson,
|
|
623
|
+
canonicalPayloadString: () => canonicalPayloadString,
|
|
607
624
|
chainNoteFromBase64: () => chainNoteFromBase64,
|
|
608
625
|
chainNoteToBase64: () => chainNoteToBase64,
|
|
626
|
+
classifyPostFailure: () => classifyPostFailure,
|
|
609
627
|
classifyRelayError: () => classifyRelayError,
|
|
628
|
+
cleanupReceivingAddress: () => cleanupReceivingAddress,
|
|
610
629
|
cleanupStalePendingOperations: () => cleanupStalePendingOperations,
|
|
611
630
|
clearPendingDeposits: () => clearPendingDeposits,
|
|
612
631
|
clearPendingWithdrawals: () => clearPendingWithdrawals,
|
|
632
|
+
cloakBridgeRail: () => cloakBridgeRail,
|
|
613
633
|
computeChainNoteHash: () => computeChainNoteHash,
|
|
614
634
|
computeExtDataHash: () => computeExtDataHash,
|
|
615
635
|
computeMerkleRoot: () => computeMerkleRoot,
|
|
@@ -631,10 +651,13 @@ __export(index_exports, {
|
|
|
631
651
|
decryptCompactChainNote: () => decryptCompactChainNote,
|
|
632
652
|
decryptComplianceMetadataWithMasterKey: () => decryptComplianceMetadataWithMasterKey,
|
|
633
653
|
decryptTransactionMetadata: () => decryptTransactionMetadata,
|
|
654
|
+
depositFromDerivedKey: () => depositFromDerivedKey,
|
|
655
|
+
deriveBridgeReceiver: () => deriveBridgeReceiver,
|
|
634
656
|
deriveChangeNoteBlinding: () => deriveChangeNoteBlinding,
|
|
635
657
|
deriveDepositNoteSecrets: () => deriveDepositNoteSecrets,
|
|
636
658
|
deriveDiversifiedViewingKey: () => deriveDiversifiedViewingKey,
|
|
637
659
|
deriveDiversifier: () => deriveDiversifier,
|
|
660
|
+
deriveFundingTargets: () => deriveFundingTargets,
|
|
638
661
|
deriveInputNullifierPdas: () => deriveInputNullifierPdas,
|
|
639
662
|
derivePublicKey: () => derivePublicKey,
|
|
640
663
|
deriveSpendKey: () => deriveSpendKey,
|
|
@@ -673,6 +696,7 @@ __export(index_exports, {
|
|
|
673
696
|
formatErrorForLogging: () => formatErrorForLogging,
|
|
674
697
|
formatSol: () => formatSol,
|
|
675
698
|
fullWithdraw: () => fullWithdraw,
|
|
699
|
+
fundReceiverViaPaymaster: () => fundReceiverViaPaymaster,
|
|
676
700
|
generateCloakKeys: () => generateCloakKeys,
|
|
677
701
|
generateCommitmentAsync: () => generateCommitmentAsync,
|
|
678
702
|
generateMasterSeed: () => generateMasterSeed,
|
|
@@ -708,18 +732,21 @@ __export(index_exports, {
|
|
|
708
732
|
isReactNative: () => isReactNative,
|
|
709
733
|
isRootNotFoundError: () => isRootNotFoundError,
|
|
710
734
|
isSubmissionOutcomeUnknownResponse: () => isSubmissionOutcomeUnknownResponse,
|
|
735
|
+
isTerminalFailure: () => isTerminalFailure,
|
|
711
736
|
isValidHex: () => isValidHex,
|
|
712
737
|
isValidRpcUrl: () => isValidRpcUrl,
|
|
713
738
|
isValidSolanaAddress: () => isValidSolanaAddress,
|
|
714
739
|
isWithdrawAmountSufficient: () => isWithdrawAmountSufficient,
|
|
715
740
|
isWithdrawable: () => isWithdrawable,
|
|
716
741
|
keypairToAdapter: () => keypairToAdapter,
|
|
742
|
+
listBridgeDeposits: () => listBridgeDeposits,
|
|
717
743
|
loadPendingDeposits: () => loadPendingDeposits,
|
|
718
744
|
loadPendingWithdrawals: () => loadPendingWithdrawals,
|
|
719
745
|
loadVerifiedCircuitArtifacts: () => loadVerifiedCircuitArtifacts,
|
|
720
746
|
matchChangeNote: () => matchChangeNote,
|
|
721
747
|
matchDepositNote: () => matchDepositNote,
|
|
722
748
|
matchSwapRefundLeaf: () => matchSwapRefundLeaf,
|
|
749
|
+
mayRetry: () => mayRetry,
|
|
723
750
|
openRecipientDeliveryNote: () => openRecipientDeliveryNote,
|
|
724
751
|
parseAmount: () => parseAmount,
|
|
725
752
|
parseDeliveryCarrierMemo: () => parseDeliveryCarrierMemo,
|
|
@@ -742,11 +769,14 @@ __export(index_exports, {
|
|
|
742
769
|
randomDepositNoteSalt: () => randomDepositNoteSalt,
|
|
743
770
|
randomFieldElement: () => randomFieldElement,
|
|
744
771
|
readMerkleTreeState: () => readMerkleTreeState,
|
|
772
|
+
readRent: () => readRent,
|
|
745
773
|
recipientDeliveryNoteToBase64: () => recipientDeliveryNoteToBase64,
|
|
746
774
|
registerViewingKey: () => registerViewingKey,
|
|
747
775
|
removePendingDeposit: () => removePendingDeposit,
|
|
748
776
|
removePendingWithdrawal: () => removePendingWithdrawal,
|
|
777
|
+
renderAssessment: () => renderAssessment,
|
|
749
778
|
resolveCircuitsBase: () => resolveCircuitsBase,
|
|
779
|
+
retryDelayMs: () => retryDelayMs,
|
|
750
780
|
savePendingDeposit: () => savePendingDeposit,
|
|
751
781
|
savePendingWithdrawal: () => savePendingWithdrawal,
|
|
752
782
|
scanNotesForWallet: () => scanNotesForWallet,
|
|
@@ -779,15 +809,19 @@ __export(index_exports, {
|
|
|
779
809
|
validateDepositParams: () => validateDepositParams,
|
|
780
810
|
validateNote: () => validateNote,
|
|
781
811
|
validateOutputsSum: () => validateOutputsSum,
|
|
812
|
+
validatePaymasterTopUpTransaction: () => validatePaymasterTopUpTransaction,
|
|
782
813
|
validateRoot: () => validateRoot,
|
|
783
814
|
validateTransfers: () => validateTransfers,
|
|
784
815
|
validateWalletConnected: () => validateWalletConnected,
|
|
785
816
|
validateWithdrawableNote: () => validateWithdrawableNote,
|
|
786
817
|
verifyAllCircuits: () => verifyAllCircuits,
|
|
787
818
|
verifyCircuitIntegrity: () => verifyCircuitIntegrity,
|
|
819
|
+
verifyQuoteSignature: () => verifyQuoteSignature,
|
|
788
820
|
verifyUtxos: () => verifyUtxos,
|
|
789
821
|
waitForRoot: () => waitForRoot,
|
|
790
|
-
|
|
822
|
+
withShieldRetries: () => withShieldRetries,
|
|
823
|
+
withTiming: () => withTiming,
|
|
824
|
+
withdrawFeeFor: () => withdrawFeeFor
|
|
791
825
|
});
|
|
792
826
|
module.exports = __toCommonJS(index_exports);
|
|
793
827
|
|
|
@@ -1391,7 +1425,7 @@ function parseStrictViewingKeyChallenge(response, userPubkey, viewingKeyHex) {
|
|
|
1391
1425
|
|
|
1392
1426
|
// src/config/relay.ts
|
|
1393
1427
|
var CLOAK_PRODUCTION_RELAY_URL = "https://api.cloak.ag";
|
|
1394
|
-
var RELAY_ORIGIN_ALLOWLIST = [
|
|
1428
|
+
var RELAY_ORIGIN_ALLOWLIST = ["https://staging-api.cloak.ag"];
|
|
1395
1429
|
var ABSOLUTE_URL = /^[a-z][a-z0-9+.-]*:\/\//i;
|
|
1396
1430
|
var LOOPBACK_IPV4 = /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
|
|
1397
1431
|
var THIS_NETWORK_IPV4 = /^0\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
|
|
@@ -2929,8 +2963,8 @@ async function verifyUtxos(utxos, connection, programId, commitment = "confirmed
|
|
|
2929
2963
|
const nullifierHex = nullifierBig.toString(16).padStart(64, "0");
|
|
2930
2964
|
const nullifierBytes = Buffer.from(nullifierHex, "hex");
|
|
2931
2965
|
const { pool } = getShieldPoolPDAs(programId, utxo.mintAddress);
|
|
2932
|
-
const [
|
|
2933
|
-
checkable.push({ utxo, pda });
|
|
2966
|
+
const [pda2] = getNullifierPDA(pool, nullifierBytes, programId);
|
|
2967
|
+
checkable.push({ utxo, pda: pda2 });
|
|
2934
2968
|
} catch {
|
|
2935
2969
|
skipped.push(utxo);
|
|
2936
2970
|
}
|
|
@@ -3000,8 +3034,8 @@ function deriveInputNullifierPdas(programId, mint, inputNullifiers) {
|
|
|
3000
3034
|
const pdas = [];
|
|
3001
3035
|
for (const nullifier of inputNullifiers) {
|
|
3002
3036
|
if (nullifier === BigInt(0)) continue;
|
|
3003
|
-
const [
|
|
3004
|
-
pdas.push(
|
|
3037
|
+
const [pda2] = getNullifierPDA(pool, bigintToBytes324(nullifier), programId);
|
|
3038
|
+
pdas.push(pda2);
|
|
3005
3039
|
}
|
|
3006
3040
|
return pdas;
|
|
3007
3041
|
}
|
|
@@ -4320,7 +4354,7 @@ async function fetchWithRetry(url, options = {}) {
|
|
|
4320
4354
|
const {
|
|
4321
4355
|
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
4322
4356
|
maxRetries = DEFAULT_MAX_RETRIES,
|
|
4323
|
-
retryDelayMs = 1e3
|
|
4357
|
+
retryDelayMs: retryDelayMs2 = 1e3
|
|
4324
4358
|
} = options;
|
|
4325
4359
|
assertAllowedRelayOrigin(url);
|
|
4326
4360
|
let lastError = null;
|
|
@@ -4344,7 +4378,7 @@ async function fetchWithRetry(url, options = {}) {
|
|
|
4344
4378
|
`Relay request failed after ${attempt + 1} attempts: ${lastError.message}`
|
|
4345
4379
|
);
|
|
4346
4380
|
}
|
|
4347
|
-
const delay =
|
|
4381
|
+
const delay = retryDelayMs2 * Math.pow(2, attempt) + Math.random() * 500;
|
|
4348
4382
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
4349
4383
|
}
|
|
4350
4384
|
}
|
|
@@ -6220,6 +6254,23 @@ async function computeSwapExtDataHash(outputMint, recipientAta, minOutputAmount,
|
|
|
6220
6254
|
]);
|
|
6221
6255
|
return poseidonHasher.F.toObject(hash);
|
|
6222
6256
|
}
|
|
6257
|
+
function assertInputMints(inputUtxos, expectedMint) {
|
|
6258
|
+
const funded = inputUtxos.filter((u) => u.amount > BigInt(0));
|
|
6259
|
+
if (funded.length === 0) return;
|
|
6260
|
+
const first = funded[0].mintAddress;
|
|
6261
|
+
for (const utxo of funded) {
|
|
6262
|
+
if (!utxo.mintAddress.equals(first)) {
|
|
6263
|
+
throw new Error(
|
|
6264
|
+
`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.`
|
|
6265
|
+
);
|
|
6266
|
+
}
|
|
6267
|
+
}
|
|
6268
|
+
if (expectedMint && !first.equals(expectedMint)) {
|
|
6269
|
+
throw new Error(
|
|
6270
|
+
`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.`
|
|
6271
|
+
);
|
|
6272
|
+
}
|
|
6273
|
+
}
|
|
6223
6274
|
function parseNkInput(input) {
|
|
6224
6275
|
if (typeof input !== "string") {
|
|
6225
6276
|
if (input.length !== 32) {
|
|
@@ -7831,7 +7882,7 @@ async function transact(params, options) {
|
|
|
7831
7882
|
// names is refused.
|
|
7832
7883
|
maxRootRetries = 40,
|
|
7833
7884
|
// Increased to handle prolonged relay sync recovery under high concurrency
|
|
7834
|
-
retryDelayMs = 500,
|
|
7885
|
+
retryDelayMs: retryDelayMs2 = 500,
|
|
7835
7886
|
// Start with short delay, will exponentially back off
|
|
7836
7887
|
riskOracleQueue,
|
|
7837
7888
|
riskQuoteUrl: riskQuoteUrlOption,
|
|
@@ -7886,6 +7937,7 @@ async function transact(params, options) {
|
|
|
7886
7937
|
}
|
|
7887
7938
|
}
|
|
7888
7939
|
assertAllowedRpcConnection(connection);
|
|
7940
|
+
assertInputMints(inputUtxos, options.expectedMint);
|
|
7889
7941
|
await ensureViewingKeyRegistered(options, onProgress, fallbackNk);
|
|
7890
7942
|
const resolvedChainNoteNk = explicitChainNoteNk ?? fallbackNk ?? null;
|
|
7891
7943
|
if (inputUtxos.length > 2) {
|
|
@@ -8252,7 +8304,7 @@ async function transact(params, options) {
|
|
|
8252
8304
|
while (submissionAttempts <= maxRootRetries) {
|
|
8253
8305
|
const isRetry = submissionAttempts > 0;
|
|
8254
8306
|
if (isRetry) {
|
|
8255
|
-
const exponentialDelay = Math.min(
|
|
8307
|
+
const exponentialDelay = Math.min(retryDelayMs2 * Math.pow(1.5, submissionAttempts - 1), 5e3);
|
|
8256
8308
|
const jitter = Math.random() * 500;
|
|
8257
8309
|
const totalDelay = Math.floor(exponentialDelay + jitter);
|
|
8258
8310
|
await sleep2(totalDelay);
|
|
@@ -8415,7 +8467,7 @@ async function transact(params, options) {
|
|
|
8415
8467
|
}
|
|
8416
8468
|
treeState = null;
|
|
8417
8469
|
useSiblingInfo = false;
|
|
8418
|
-
const waitMs = Math.min(
|
|
8470
|
+
const waitMs = Math.min(retryDelayMs2 * Math.pow(2, Math.min(submissionAttempts, 4)), 5e3);
|
|
8419
8471
|
if (waitMs > 0) {
|
|
8420
8472
|
onProgress?.(`Waiting ${waitMs}ms for relay sync before retry...`);
|
|
8421
8473
|
await new Promise((r) => setTimeout(r, waitMs));
|
|
@@ -9109,7 +9161,7 @@ async function swapUtxo(params, options) {
|
|
|
9109
9161
|
riskQuoteUrl: riskQuoteUrlOption,
|
|
9110
9162
|
maxRootRetries = 40,
|
|
9111
9163
|
// Increased to handle prolonged relay sync recovery under high concurrency
|
|
9112
|
-
retryDelayMs = 500,
|
|
9164
|
+
retryDelayMs: retryDelayMs2 = 500,
|
|
9113
9165
|
// Start with short delay, will exponentially back off
|
|
9114
9166
|
useUniqueNullifiers,
|
|
9115
9167
|
useChainRootForProof = true
|
|
@@ -9128,6 +9180,12 @@ async function swapUtxo(params, options) {
|
|
|
9128
9180
|
if (inputUtxos.length > 2) {
|
|
9129
9181
|
throw new Error("Maximum 2 input UTXOs allowed");
|
|
9130
9182
|
}
|
|
9183
|
+
assertInputMints(inputUtxos, NATIVE_SOL_MINT);
|
|
9184
|
+
if (options.expectedMint && !options.expectedMint.equals(NATIVE_SOL_MINT)) {
|
|
9185
|
+
throw new Error(
|
|
9186
|
+
`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.`
|
|
9187
|
+
);
|
|
9188
|
+
}
|
|
9131
9189
|
await preflightNullifiers(inputUtxos, connection, programId);
|
|
9132
9190
|
const outputMintAccount = await connection.getAccountInfo(outputMint, {
|
|
9133
9191
|
commitment: "confirmed"
|
|
@@ -9514,7 +9572,7 @@ async function swapUtxo(params, options) {
|
|
|
9514
9572
|
let walletApprovals = 0;
|
|
9515
9573
|
for (let attempt = 0; attempt <= maxRootRetries; attempt++) {
|
|
9516
9574
|
if (attempt > 0) {
|
|
9517
|
-
const exponentialDelay = Math.min(
|
|
9575
|
+
const exponentialDelay = Math.min(retryDelayMs2 * Math.pow(1.5, attempt - 1), 5e3);
|
|
9518
9576
|
const jitter = Math.random() * 500;
|
|
9519
9577
|
const totalDelay = Math.floor(exponentialDelay + jitter);
|
|
9520
9578
|
await sleep2(totalDelay);
|
|
@@ -9652,7 +9710,7 @@ async function swapUtxo(params, options) {
|
|
|
9652
9710
|
}
|
|
9653
9711
|
merkleState = null;
|
|
9654
9712
|
useSiblingInfo = false;
|
|
9655
|
-
const waitMs = Math.min(
|
|
9713
|
+
const waitMs = Math.min(retryDelayMs2 * Math.pow(2, Math.min(attempt, 4)), 5e3);
|
|
9656
9714
|
if (waitMs > 0) {
|
|
9657
9715
|
onProgress?.(`Waiting ${waitMs}ms for relay sync before retry...`);
|
|
9658
9716
|
await new Promise((r) => setTimeout(r, waitMs));
|
|
@@ -11632,13 +11690,781 @@ var SimpleWallet = class {
|
|
|
11632
11690
|
}
|
|
11633
11691
|
};
|
|
11634
11692
|
|
|
11693
|
+
// src/bridge/rail-verify.ts
|
|
11694
|
+
var import_tweetnacl7 = __toESM(require("tweetnacl"), 1);
|
|
11695
|
+
var import_sha256 = require("@noble/hashes/sha256");
|
|
11696
|
+
var ONECLICK_PUBKEY_B58 = "reYaWhvwu8Jzo3WUM3zhn6VrhuMEF4eADL17qtRVifc";
|
|
11697
|
+
var b58 = /* @__PURE__ */ (() => {
|
|
11698
|
+
const A = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
11699
|
+
return {
|
|
11700
|
+
decode(s) {
|
|
11701
|
+
let n = 0n;
|
|
11702
|
+
for (const c of s) {
|
|
11703
|
+
const i = A.indexOf(c);
|
|
11704
|
+
if (i < 0) throw new Error("bad base58");
|
|
11705
|
+
n = n * 58n + BigInt(i);
|
|
11706
|
+
}
|
|
11707
|
+
const bytes = [];
|
|
11708
|
+
while (n > 0n) {
|
|
11709
|
+
bytes.unshift(Number(n & 255n));
|
|
11710
|
+
n >>= 8n;
|
|
11711
|
+
}
|
|
11712
|
+
for (const c of s) {
|
|
11713
|
+
if (c === "1") bytes.unshift(0);
|
|
11714
|
+
else break;
|
|
11715
|
+
}
|
|
11716
|
+
return Uint8Array.from(bytes);
|
|
11717
|
+
},
|
|
11718
|
+
encode(b) {
|
|
11719
|
+
let n = 0n;
|
|
11720
|
+
for (const x of b) n = n * 256n + BigInt(x);
|
|
11721
|
+
let s = "";
|
|
11722
|
+
while (n > 0n) {
|
|
11723
|
+
s = A[Number(n % 58n)] + s;
|
|
11724
|
+
n /= 58n;
|
|
11725
|
+
}
|
|
11726
|
+
for (const x of b) {
|
|
11727
|
+
if (x === 0) s = "1" + s;
|
|
11728
|
+
else break;
|
|
11729
|
+
}
|
|
11730
|
+
return s;
|
|
11731
|
+
}
|
|
11732
|
+
};
|
|
11733
|
+
})();
|
|
11734
|
+
function stable(v) {
|
|
11735
|
+
if (v === null || typeof v !== "object") return JSON.stringify(v) ?? "null";
|
|
11736
|
+
if (Array.isArray(v)) return `[${v.map(stable).join(",")}]`;
|
|
11737
|
+
const o = v;
|
|
11738
|
+
const parts = Object.keys(o).sort().filter((k) => o[k] !== void 0).map((k) => `${JSON.stringify(k)}:${stable(o[k])}`);
|
|
11739
|
+
return `{${parts.join(",")}}`;
|
|
11740
|
+
}
|
|
11741
|
+
function signedRequest(r) {
|
|
11742
|
+
const q = r.quoteRequest ?? {};
|
|
11743
|
+
return {
|
|
11744
|
+
dry: q.dry,
|
|
11745
|
+
swapType: q.swapType,
|
|
11746
|
+
slippageTolerance: q.slippageTolerance,
|
|
11747
|
+
originAsset: q.originAsset,
|
|
11748
|
+
depositType: q.depositType,
|
|
11749
|
+
destinationAsset: q.destinationAsset,
|
|
11750
|
+
amount: q.amount,
|
|
11751
|
+
refundTo: q.refundTo,
|
|
11752
|
+
refundType: q.refundType,
|
|
11753
|
+
recipient: q.recipient,
|
|
11754
|
+
recipientType: q.recipientType,
|
|
11755
|
+
deadline: q.deadline,
|
|
11756
|
+
quoteWaitingTimeMs: q.quoteWaitingTimeMs || void 0,
|
|
11757
|
+
referral: q.referral || void 0,
|
|
11758
|
+
virtualChainRecipient: q.virtualChainRecipient || void 0,
|
|
11759
|
+
virtualChainRefundRecipient: q.virtualChainRefundRecipient || void 0,
|
|
11760
|
+
customRecipientMsg: q.customRecipientMsg || void 0
|
|
11761
|
+
// Deliberately unsigned by the rail: sessionId, connectedWallets, correlationId, appFees,
|
|
11762
|
+
// partnerId, userAccountId, depositMode. APP FEES ARE NOT AUTHENTICATED — never display a fee
|
|
11763
|
+
// caption as if the signature vouched for it.
|
|
11764
|
+
};
|
|
11765
|
+
}
|
|
11766
|
+
function signedQuote(r) {
|
|
11767
|
+
const q = r.quote ?? {};
|
|
11768
|
+
const base = {
|
|
11769
|
+
amountIn: q.amountIn,
|
|
11770
|
+
amountInFormatted: q.amountInFormatted,
|
|
11771
|
+
amountInUsd: q.amountInUsd,
|
|
11772
|
+
minAmountIn: q.minAmountIn,
|
|
11773
|
+
amountOut: q.amountOut,
|
|
11774
|
+
amountOutFormatted: q.amountOutFormatted,
|
|
11775
|
+
amountOutUsd: q.amountOutUsd,
|
|
11776
|
+
minAmountOut: q.minAmountOut
|
|
11777
|
+
};
|
|
11778
|
+
if (r.quoteRequest?.dry) return base;
|
|
11779
|
+
return {
|
|
11780
|
+
...base,
|
|
11781
|
+
depositAddress: q.depositAddress || void 0,
|
|
11782
|
+
// <- the field that makes substitution detectable
|
|
11783
|
+
depositMemo: q.depositMemo || void 0,
|
|
11784
|
+
deadline: q.deadline || void 0,
|
|
11785
|
+
// <- a real, SIGNED expiry
|
|
11786
|
+
timeWhenInactive: q.timeWhenInactive || void 0,
|
|
11787
|
+
timeEstimate: q.timeEstimate || void 0,
|
|
11788
|
+
virtualChainRecipient: q.virtualChainRecipient || void 0,
|
|
11789
|
+
virtualChainRefundRecipient: q.virtualChainRefundRecipient || void 0,
|
|
11790
|
+
customRecipientMsg: q.customRecipientMsg || void 0,
|
|
11791
|
+
refundFee: q.refundFee || void 0,
|
|
11792
|
+
withdrawFee: q.withdrawFee || void 0
|
|
11793
|
+
};
|
|
11794
|
+
}
|
|
11795
|
+
function canonicalPayloadString(resp) {
|
|
11796
|
+
return stable({ ...signedRequest(resp), ...signedQuote(resp), timestamp: resp.timestamp });
|
|
11797
|
+
}
|
|
11798
|
+
function verifyQuoteSignature(resp) {
|
|
11799
|
+
if (!resp?.signature) return { valid: false, reason: "no signature on the response" };
|
|
11800
|
+
const payload = { ...signedRequest(resp), ...signedQuote(resp), timestamp: resp.timestamp };
|
|
11801
|
+
const digest = (0, import_sha256.sha256)(new TextEncoder().encode(stable(payload)));
|
|
11802
|
+
const message = new TextEncoder().encode(b58.encode(new Uint8Array(digest)));
|
|
11803
|
+
const sig = resp.signature.replace(/^ed25519:/, "");
|
|
11804
|
+
try {
|
|
11805
|
+
return { valid: import_tweetnacl7.default.sign.detached.verify(message, b58.decode(sig), b58.decode(ONECLICK_PUBKEY_B58)) };
|
|
11806
|
+
} catch (e) {
|
|
11807
|
+
return { valid: false, reason: e instanceof Error ? e.message : String(e) };
|
|
11808
|
+
}
|
|
11809
|
+
}
|
|
11810
|
+
|
|
11811
|
+
// src/bridge/rails.ts
|
|
11812
|
+
async function parseBridgeResponse(res, what) {
|
|
11813
|
+
let body;
|
|
11814
|
+
try {
|
|
11815
|
+
body = await res.json();
|
|
11816
|
+
} catch {
|
|
11817
|
+
throw new Error(`${what} failed: ${res.status} ${res.statusText} (response body was not JSON)`);
|
|
11818
|
+
}
|
|
11819
|
+
if (!res.ok) {
|
|
11820
|
+
const b = body ?? {};
|
|
11821
|
+
throw new Error(
|
|
11822
|
+
`${what} failed: ${b.message ?? res.statusText} (${b.code ?? res.status}${b.retryable ? ", retryable" : ""})`
|
|
11823
|
+
);
|
|
11824
|
+
}
|
|
11825
|
+
return body;
|
|
11826
|
+
}
|
|
11827
|
+
function toAttestation(raw) {
|
|
11828
|
+
if (raw.kind === "ed25519") {
|
|
11829
|
+
return { kind: "ed25519", verified: raw.verified ?? false, signer: raw.signer ?? "" };
|
|
11830
|
+
}
|
|
11831
|
+
return { kind: "none", checks: raw.checks ?? [], note: raw.note ?? "" };
|
|
11832
|
+
}
|
|
11833
|
+
async function fetchBridgeQuote(relayUrl, req) {
|
|
11834
|
+
const body = {
|
|
11835
|
+
recipient: req.recipient,
|
|
11836
|
+
origin_chain: req.originChain,
|
|
11837
|
+
amount_base_units: req.amountBaseUnits.toString(),
|
|
11838
|
+
allocate: req.allocate ?? false
|
|
11839
|
+
};
|
|
11840
|
+
if (req.refundTo !== void 0) body.refund_to = req.refundTo;
|
|
11841
|
+
if (req.rails !== void 0) body.rails = req.rails;
|
|
11842
|
+
const res = await relayFetch(`${relayUrl}/bridge/quote`, {
|
|
11843
|
+
method: "POST",
|
|
11844
|
+
headers: { "Content-Type": "application/json" },
|
|
11845
|
+
body: JSON.stringify(body)
|
|
11846
|
+
});
|
|
11847
|
+
const raw = await parseBridgeResponse(res, "bridge quote");
|
|
11848
|
+
const options = [];
|
|
11849
|
+
const unavailable = (raw.unavailable ?? []).map((p) => ({
|
|
11850
|
+
rail: p.rail,
|
|
11851
|
+
reason: p.reason
|
|
11852
|
+
}));
|
|
11853
|
+
for (const opt of raw.options) {
|
|
11854
|
+
const attestation = toAttestation(opt.attestation);
|
|
11855
|
+
if (attestation.kind === "ed25519") {
|
|
11856
|
+
const verdict = opt.rail_response ? verifyQuoteSignature(opt.rail_response) : { valid: false, reason: "no rail_response to verify against" };
|
|
11857
|
+
if (!verdict.valid) {
|
|
11858
|
+
unavailable.push({
|
|
11859
|
+
rail: opt.rail,
|
|
11860
|
+
reason: `deposit address failed independent signature verification, refusing to display it (${verdict.reason ?? "signature did not verify"})`
|
|
11861
|
+
});
|
|
11862
|
+
continue;
|
|
11863
|
+
}
|
|
11864
|
+
}
|
|
11865
|
+
options.push({
|
|
11866
|
+
rail: opt.rail,
|
|
11867
|
+
refunds: opt.refunds,
|
|
11868
|
+
addressLifetime: opt.address_lifetime,
|
|
11869
|
+
amountOut: BigInt(opt.amount_out),
|
|
11870
|
+
minAmountOut: BigInt(opt.min_amount_out),
|
|
11871
|
+
timeEstimateSeconds: opt.time_estimate_s,
|
|
11872
|
+
expiresAt: opt.expires_at,
|
|
11873
|
+
depositAddress: opt.deposit_address,
|
|
11874
|
+
attestation
|
|
11875
|
+
});
|
|
11876
|
+
}
|
|
11877
|
+
return { options, unavailable };
|
|
11878
|
+
}
|
|
11879
|
+
var STATUS_STATES = [
|
|
11880
|
+
"pending",
|
|
11881
|
+
"delivered",
|
|
11882
|
+
"refunded",
|
|
11883
|
+
"expired",
|
|
11884
|
+
"unknown"
|
|
11885
|
+
];
|
|
11886
|
+
async function fetchBridgeStatus(relayUrl, depositAddress, rail) {
|
|
11887
|
+
const qs = new URLSearchParams({ deposit_address: depositAddress, rail });
|
|
11888
|
+
const res = await relayFetch(`${relayUrl}/bridge/status?${qs.toString()}`);
|
|
11889
|
+
const raw = await parseBridgeResponse(res, "bridge status");
|
|
11890
|
+
const state = STATUS_STATES.includes(raw.state ?? "") ? raw.state : "unknown";
|
|
11891
|
+
return { rail: raw.rail ?? rail, state, detail: raw.detail ?? "" };
|
|
11892
|
+
}
|
|
11893
|
+
function cloakBridgeRail(relayUrl) {
|
|
11894
|
+
assertAllowedRelayOrigin(relayUrl);
|
|
11895
|
+
return {
|
|
11896
|
+
id: "cloak-bridge",
|
|
11897
|
+
quote: (req) => fetchBridgeQuote(relayUrl, req),
|
|
11898
|
+
status: (depositAddress, rail) => fetchBridgeStatus(relayUrl, depositAddress, rail)
|
|
11899
|
+
};
|
|
11900
|
+
}
|
|
11901
|
+
|
|
11902
|
+
// src/bridge/paymaster-client.ts
|
|
11903
|
+
var import_web312 = require("@solana/web3.js");
|
|
11904
|
+
var import_spl_token3 = require("@solana/spl-token");
|
|
11905
|
+
function validatePaymasterTopUpTransaction(tx, expect) {
|
|
11906
|
+
const ixs = tx.instructions;
|
|
11907
|
+
if (ixs.length !== 1 && ixs.length !== 2) {
|
|
11908
|
+
throw new Error(
|
|
11909
|
+
`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`
|
|
11910
|
+
);
|
|
11911
|
+
}
|
|
11912
|
+
const ix0 = ixs[0];
|
|
11913
|
+
if (!ix0.programId.equals(import_web312.SystemProgram.programId)) {
|
|
11914
|
+
throw new Error(
|
|
11915
|
+
`paymaster top-up instruction 0 is owned by ${ix0.programId.toBase58()}, not the System Program \u2014 refusing to sign an unrecognised first instruction`
|
|
11916
|
+
);
|
|
11917
|
+
}
|
|
11918
|
+
const transfer2 = import_web312.SystemInstruction.decodeTransfer(ix0);
|
|
11919
|
+
if (!transfer2.toPubkey.equals(expect.recipient)) {
|
|
11920
|
+
throw new Error(
|
|
11921
|
+
`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`
|
|
11922
|
+
);
|
|
11923
|
+
}
|
|
11924
|
+
if (BigInt(transfer2.lamports) > expect.maxGrantLamports) {
|
|
11925
|
+
throw new Error(
|
|
11926
|
+
`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`
|
|
11927
|
+
);
|
|
11928
|
+
}
|
|
11929
|
+
if (ixs.length === 1) return;
|
|
11930
|
+
const ix1 = ixs[1];
|
|
11931
|
+
if (!ix1.programId.equals(import_spl_token3.TOKEN_PROGRAM_ID)) {
|
|
11932
|
+
throw new Error(
|
|
11933
|
+
`paymaster top-up instruction 1 is owned by ${ix1.programId.toBase58()}, not the SPL Token program \u2014 refusing to sign an unrecognised second instruction`
|
|
11934
|
+
);
|
|
11935
|
+
}
|
|
11936
|
+
const spl = (0, import_spl_token3.decodeTransferInstruction)(ix1, import_spl_token3.TOKEN_PROGRAM_ID);
|
|
11937
|
+
const authority = spl.keys.owner.pubkey;
|
|
11938
|
+
if (!authority.equals(expect.recipient)) {
|
|
11939
|
+
throw new Error(
|
|
11940
|
+
`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`
|
|
11941
|
+
);
|
|
11942
|
+
}
|
|
11943
|
+
const expectedDestination = (0, import_spl_token3.getAssociatedTokenAddressSync)(
|
|
11944
|
+
expect.feeMint,
|
|
11945
|
+
expect.paymentAddress,
|
|
11946
|
+
false
|
|
11947
|
+
);
|
|
11948
|
+
const destination = spl.keys.destination.pubkey;
|
|
11949
|
+
if (!destination.equals(expectedDestination)) {
|
|
11950
|
+
throw new Error(
|
|
11951
|
+
`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`
|
|
11952
|
+
);
|
|
11953
|
+
}
|
|
11954
|
+
const paidAmount = BigInt(spl.data.amount);
|
|
11955
|
+
if (paidAmount !== expect.feeTokenAmount) {
|
|
11956
|
+
throw new Error(
|
|
11957
|
+
`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`
|
|
11958
|
+
);
|
|
11959
|
+
}
|
|
11960
|
+
}
|
|
11961
|
+
async function parseBridgeResponse2(res, what) {
|
|
11962
|
+
let body;
|
|
11963
|
+
try {
|
|
11964
|
+
body = await res.json();
|
|
11965
|
+
} catch {
|
|
11966
|
+
throw new Error(`${what} failed: ${res.status} ${res.statusText} (response body was not JSON)`);
|
|
11967
|
+
}
|
|
11968
|
+
if (!res.ok) {
|
|
11969
|
+
const b = body ?? {};
|
|
11970
|
+
throw new Error(
|
|
11971
|
+
`${what} failed: ${b.message ?? res.statusText} (${b.code ?? res.status}${b.retryable ? ", retryable" : ""})`
|
|
11972
|
+
);
|
|
11973
|
+
}
|
|
11974
|
+
return body;
|
|
11975
|
+
}
|
|
11976
|
+
async function fundReceiverViaPaymaster(relayUrl, receiver, grantLamports) {
|
|
11977
|
+
assertAllowedRelayOrigin(relayUrl);
|
|
11978
|
+
if (grantLamports <= 0n || grantLamports > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
11979
|
+
throw new Error(
|
|
11980
|
+
`grantLamports must be a positive value representable as a JS number, got ${grantLamports}`
|
|
11981
|
+
);
|
|
11982
|
+
}
|
|
11983
|
+
const prepareRes = await relayFetch(`${relayUrl}/bridge/paymaster/prepare`, {
|
|
11984
|
+
method: "POST",
|
|
11985
|
+
headers: { "Content-Type": "application/json" },
|
|
11986
|
+
body: JSON.stringify({
|
|
11987
|
+
recipient: receiver.publicKey.toBase58(),
|
|
11988
|
+
grant_lamports: Number(grantLamports)
|
|
11989
|
+
})
|
|
11990
|
+
});
|
|
11991
|
+
const prepared = await parseBridgeResponse2(prepareRes, "paymaster prepare");
|
|
11992
|
+
if (Math.floor(Date.now() / 1e3) >= prepared.expires_at_unix) {
|
|
11993
|
+
throw new Error(
|
|
11994
|
+
"paymaster prepare returned a voucher that is already expired \u2014 refusing to sign a stale top-up rather than fail confusingly at cosign"
|
|
11995
|
+
);
|
|
11996
|
+
}
|
|
11997
|
+
const feeMint = new import_web312.PublicKey(prepared.fee_mint);
|
|
11998
|
+
const paymentAddress = new import_web312.PublicKey(prepared.payment_address);
|
|
11999
|
+
const feeTokenAmount = BigInt(prepared.fee_token_amount);
|
|
12000
|
+
const tx = import_web312.Transaction.from(Buffer.from(prepared.transaction, "base64"));
|
|
12001
|
+
validatePaymasterTopUpTransaction(tx, {
|
|
12002
|
+
recipient: receiver.publicKey,
|
|
12003
|
+
maxGrantLamports: grantLamports,
|
|
12004
|
+
feeMint,
|
|
12005
|
+
feeTokenAmount,
|
|
12006
|
+
paymentAddress
|
|
12007
|
+
});
|
|
12008
|
+
tx.partialSign(receiver);
|
|
12009
|
+
const cosignRes = await relayFetch(`${relayUrl}/bridge/paymaster/cosign`, {
|
|
12010
|
+
method: "POST",
|
|
12011
|
+
headers: { "Content-Type": "application/json" },
|
|
12012
|
+
body: JSON.stringify({
|
|
12013
|
+
transaction: tx.serialize({ requireAllSignatures: false, verifySignatures: false }).toString("base64"),
|
|
12014
|
+
voucher: prepared.voucher
|
|
12015
|
+
})
|
|
12016
|
+
});
|
|
12017
|
+
const cosigned = await parseBridgeResponse2(cosignRes, "paymaster cosign");
|
|
12018
|
+
return {
|
|
12019
|
+
transaction: import_web312.Transaction.from(Buffer.from(cosigned.transaction, "base64")),
|
|
12020
|
+
feeTokenAmount,
|
|
12021
|
+
feeMint: prepared.fee_mint,
|
|
12022
|
+
paymentAddress: prepared.payment_address
|
|
12023
|
+
};
|
|
12024
|
+
}
|
|
12025
|
+
|
|
12026
|
+
// src/bridge/derive.ts
|
|
12027
|
+
var import_web313 = require("@solana/web3.js");
|
|
12028
|
+
var import_hmac = require("@noble/hashes/hmac");
|
|
12029
|
+
var import_sha512 = require("@noble/hashes/sha512");
|
|
12030
|
+
var BRIDGE_ESCROW_LABEL = "cloak_bridge_escrow";
|
|
12031
|
+
var MAX_RECEIVER_INDEX = 1e4;
|
|
12032
|
+
function deriveBridgeReceiver(nk, index) {
|
|
12033
|
+
if (!Number.isInteger(index) || index < 0) {
|
|
12034
|
+
throw new Error(`index must be a non-negative integer, got ${index}`);
|
|
12035
|
+
}
|
|
12036
|
+
if (index > MAX_RECEIVER_INDEX) {
|
|
12037
|
+
throw new Error(
|
|
12038
|
+
`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.`
|
|
12039
|
+
);
|
|
12040
|
+
}
|
|
12041
|
+
const h = (0, import_hmac.hmac)(import_sha512.sha512, new Uint8Array(nk), new TextEncoder().encode(`${BRIDGE_ESCROW_LABEL}:${index}`));
|
|
12042
|
+
return import_web313.Keypair.fromSeed(h.slice(0, 32));
|
|
12043
|
+
}
|
|
12044
|
+
|
|
12045
|
+
// src/bridge/discover.ts
|
|
12046
|
+
var import_web314 = require("@solana/web3.js");
|
|
12047
|
+
var import_spl_token4 = require("@solana/spl-token");
|
|
12048
|
+
function describeError(err) {
|
|
12049
|
+
return err instanceof Error ? err.message : String(err);
|
|
12050
|
+
}
|
|
12051
|
+
async function listBridgeDeposits(conn, nk, opts) {
|
|
12052
|
+
const scanDepth = Math.min(opts.scanDepth ?? 20, MAX_RECEIVER_INDEX + 1);
|
|
12053
|
+
const stopAfterUnused = opts.stopAfterUnused ?? 5;
|
|
12054
|
+
const out = [];
|
|
12055
|
+
let consecutiveUnused = 0;
|
|
12056
|
+
for (let index = 0; index < scanDepth; index++) {
|
|
12057
|
+
const kp = deriveBridgeReceiver(nk, index);
|
|
12058
|
+
const receiver = kp.publicKey;
|
|
12059
|
+
const tokenAccount = (0, import_spl_token4.getAssociatedTokenAddressSync)(opts.mint, receiver, false);
|
|
12060
|
+
try {
|
|
12061
|
+
const [recvInfo, ataInfo] = await conn.getMultipleAccountsInfo([receiver, tokenAccount]);
|
|
12062
|
+
const lamports = recvInfo?.lamports ?? 0;
|
|
12063
|
+
let tokenBalance = 0n;
|
|
12064
|
+
if (ataInfo) {
|
|
12065
|
+
try {
|
|
12066
|
+
tokenBalance = BigInt((await conn.getTokenAccountBalance(tokenAccount)).value.amount);
|
|
12067
|
+
} catch (err) {
|
|
12068
|
+
if (!(err instanceof import_web314.SolanaJSONRPCError && /could not find account/i.test(err.message))) {
|
|
12069
|
+
throw err;
|
|
12070
|
+
}
|
|
12071
|
+
}
|
|
12072
|
+
}
|
|
12073
|
+
if (lamports === 0 && !ataInfo) {
|
|
12074
|
+
consecutiveUnused++;
|
|
12075
|
+
if (consecutiveUnused >= stopAfterUnused) break;
|
|
12076
|
+
continue;
|
|
12077
|
+
}
|
|
12078
|
+
consecutiveUnused = 0;
|
|
12079
|
+
const shieldSignatures = [];
|
|
12080
|
+
const sigs = await conn.getSignaturesForAddress(receiver, { limit: 20 });
|
|
12081
|
+
for (const s of sigs) {
|
|
12082
|
+
if (s.err) continue;
|
|
12083
|
+
const tx = await conn.getTransaction(s.signature, { maxSupportedTransactionVersion: 0 });
|
|
12084
|
+
const keys = tx?.transaction.message.getAccountKeys({
|
|
12085
|
+
accountKeysFromLookups: tx.meta?.loadedAddresses
|
|
12086
|
+
});
|
|
12087
|
+
if (keys && [...Array(keys.length).keys()].some((i) => keys.get(i)?.equals(opts.programId))) {
|
|
12088
|
+
shieldSignatures.push(s.signature);
|
|
12089
|
+
}
|
|
12090
|
+
}
|
|
12091
|
+
const shieldSignature = shieldSignatures[0];
|
|
12092
|
+
const indexReused = shieldSignatures.length > 1;
|
|
12093
|
+
let state;
|
|
12094
|
+
if (tokenBalance > 0n) state = shieldSignature ? "needs-cleanup" : "arrived";
|
|
12095
|
+
else if (shieldSignature) state = ataInfo ? "needs-cleanup" : "complete";
|
|
12096
|
+
else state = "awaiting";
|
|
12097
|
+
out.push({
|
|
12098
|
+
index,
|
|
12099
|
+
receiver,
|
|
12100
|
+
tokenAccount,
|
|
12101
|
+
state,
|
|
12102
|
+
tokenBalance,
|
|
12103
|
+
lamports,
|
|
12104
|
+
shieldSignature,
|
|
12105
|
+
...indexReused ? { indexReused, shieldSignatures } : {}
|
|
12106
|
+
});
|
|
12107
|
+
} catch (err) {
|
|
12108
|
+
out.push({
|
|
12109
|
+
index,
|
|
12110
|
+
receiver,
|
|
12111
|
+
tokenAccount,
|
|
12112
|
+
state: "unknown",
|
|
12113
|
+
tokenBalance: 0n,
|
|
12114
|
+
lamports: 0,
|
|
12115
|
+
error: describeError(err)
|
|
12116
|
+
});
|
|
12117
|
+
}
|
|
12118
|
+
}
|
|
12119
|
+
return out;
|
|
12120
|
+
}
|
|
12121
|
+
|
|
12122
|
+
// src/bridge/deposit-core.ts
|
|
12123
|
+
var import_web316 = require("@solana/web3.js");
|
|
12124
|
+
var import_tweetnacl8 = __toESM(require("tweetnacl"), 1);
|
|
12125
|
+
init_utxo();
|
|
12126
|
+
|
|
12127
|
+
// src/bridge/funder.ts
|
|
12128
|
+
var import_web315 = require("@solana/web3.js");
|
|
12129
|
+
var import_spl_token5 = require("@solana/spl-token");
|
|
12130
|
+
var MAINNET_RENT_0 = 890880;
|
|
12131
|
+
var MAINNET_RENT_1 = 897840;
|
|
12132
|
+
var FEE_BUDGET = 13e4;
|
|
12133
|
+
var CLEANUP_FEE_BUDGET = 5e3;
|
|
12134
|
+
var pda = (seeds, programId) => import_web315.PublicKey.findProgramAddressSync(seeds.map((s) => Buffer.from(s)), programId)[0];
|
|
12135
|
+
function deriveFundingTargets(d) {
|
|
12136
|
+
const pool = pda([Buffer.from("pool"), d.mint.toBuffer()], d.programId);
|
|
12137
|
+
return {
|
|
12138
|
+
pool,
|
|
12139
|
+
// program: NullifierAccount::derive_pda_with_pool -> [b"nullifier", pool, nullifier]
|
|
12140
|
+
nullifier0: pda([Buffer.from("nullifier"), pool.toBuffer(), d.nullifiers[0]], d.programId),
|
|
12141
|
+
nullifier1: pda([Buffer.from("nullifier"), pool.toBuffer(), d.nullifiers[1]], d.programId),
|
|
12142
|
+
// relay: derive_risk_nonce_pda -> [b"risk_nonce", bind0]
|
|
12143
|
+
riskNonce: pda([Buffer.from("risk_nonce"), d.bind0], d.programId),
|
|
12144
|
+
depositorAta: (0, import_spl_token5.getAssociatedTokenAddressSync)(d.mint, d.depositor, false)
|
|
12145
|
+
};
|
|
12146
|
+
}
|
|
12147
|
+
async function readRent(conn) {
|
|
12148
|
+
const [zero, one] = await Promise.all([
|
|
12149
|
+
conn.getMinimumBalanceForRentExemption(0),
|
|
12150
|
+
conn.getMinimumBalanceForRentExemption(1)
|
|
12151
|
+
]);
|
|
12152
|
+
return { zero, one };
|
|
12153
|
+
}
|
|
12154
|
+
|
|
12155
|
+
// src/bridge/deposit-core.ts
|
|
12156
|
+
var DepositError = class extends Error {
|
|
12157
|
+
constructor(message, fundedAccounts, measuredTxSize) {
|
|
12158
|
+
super(message);
|
|
12159
|
+
this.fundedAccounts = fundedAccounts;
|
|
12160
|
+
this.measuredTxSize = measuredTxSize;
|
|
12161
|
+
this.name = "DepositError";
|
|
12162
|
+
}
|
|
12163
|
+
};
|
|
12164
|
+
async function depositFromDerivedKey(conn, R, funder, amount, log = console.log, grantOverride, opts) {
|
|
12165
|
+
if (!opts.noteSpendKey || opts.noteSpendKey.length !== 32) {
|
|
12166
|
+
throw new Error(
|
|
12167
|
+
"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."
|
|
12168
|
+
);
|
|
12169
|
+
}
|
|
12170
|
+
if (!opts.relayUrl) {
|
|
12171
|
+
throw new Error(
|
|
12172
|
+
"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."
|
|
12173
|
+
);
|
|
12174
|
+
}
|
|
12175
|
+
if (!opts.programId) {
|
|
12176
|
+
throw new Error(
|
|
12177
|
+
"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."
|
|
12178
|
+
);
|
|
12179
|
+
}
|
|
12180
|
+
if (!opts.mint) {
|
|
12181
|
+
throw new Error(
|
|
12182
|
+
"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."
|
|
12183
|
+
);
|
|
12184
|
+
}
|
|
12185
|
+
assertAllowedRpcConnection(conn);
|
|
12186
|
+
const relayUrl = opts.relayUrl;
|
|
12187
|
+
const programId = opts.programId;
|
|
12188
|
+
const mint = opts.mint;
|
|
12189
|
+
setCircuitsPath(resolveCircuitsBase());
|
|
12190
|
+
const grantR = grantOverride ?? MAINNET_RENT_0 + FEE_BUDGET + CLEANUP_FEE_BUDGET;
|
|
12191
|
+
const heldByR = await conn.getBalance(R.publicKey);
|
|
12192
|
+
if (heldByR >= grantR) {
|
|
12193
|
+
log(` R already holds ${heldByR} (>= ${grantR}) \u2014 no top-up needed`);
|
|
12194
|
+
} else {
|
|
12195
|
+
await (0, import_web316.sendAndConfirmTransaction)(conn, new import_web316.Transaction().add(
|
|
12196
|
+
import_web316.SystemProgram.transfer({ fromPubkey: funder.publicKey, toPubkey: R.publicKey, lamports: grantR - heldByR })
|
|
12197
|
+
), [funder]);
|
|
12198
|
+
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"}`);
|
|
12199
|
+
}
|
|
12200
|
+
let measuredSize = -1;
|
|
12201
|
+
let fundedAccounts = [];
|
|
12202
|
+
const signTransaction2 = async (tx) => {
|
|
12203
|
+
if (tx instanceof import_web316.VersionedTransaction) {
|
|
12204
|
+
measuredSize = tx.serialize().length;
|
|
12205
|
+
const alts = await Promise.all(
|
|
12206
|
+
tx.message.addressTableLookups.map(async (l) => (await conn.getAddressLookupTable(l.accountKey)).value)
|
|
12207
|
+
);
|
|
12208
|
+
const keys = tx.message.getAccountKeys({ addressLookupTableAccounts: alts });
|
|
12209
|
+
const ix = tx.message.compiledInstructions.find((i) => keys.get(i.programIdIndex).equals(programId));
|
|
12210
|
+
const at = (n) => keys.get(ix.accountKeyIndexes[n]);
|
|
12211
|
+
const targets = [
|
|
12212
|
+
{ a: at(12), need: MAINNET_RENT_0, name: "risk_nonce" },
|
|
12213
|
+
{ a: at(4), need: MAINNET_RENT_1, name: "nullifier_0" },
|
|
12214
|
+
{ a: at(5), need: MAINNET_RENT_1, name: "nullifier_1" }
|
|
12215
|
+
];
|
|
12216
|
+
const infos = await conn.getMultipleAccountsInfo(targets.map((t) => t.a));
|
|
12217
|
+
fundedAccounts = targets.map((t, i) => ({
|
|
12218
|
+
name: t.name,
|
|
12219
|
+
address: t.a.toBase58(),
|
|
12220
|
+
lamportsSent: Math.max(0, t.need - (infos[i]?.lamports ?? 0))
|
|
12221
|
+
}));
|
|
12222
|
+
const ixs = targets.flatMap((t, i) => (infos[i]?.lamports ?? 0) >= t.need ? [] : [import_web316.SystemProgram.transfer({
|
|
12223
|
+
fromPubkey: funder.publicKey,
|
|
12224
|
+
toPubkey: t.a,
|
|
12225
|
+
lamports: t.need - (infos[i]?.lamports ?? 0)
|
|
12226
|
+
})]);
|
|
12227
|
+
if (ixs.length) {
|
|
12228
|
+
const sig = await (0, import_web316.sendAndConfirmTransaction)(conn, new import_web316.Transaction().add(...ixs), [funder]);
|
|
12229
|
+
log(` seam: funded ${ixs.length} program accounts read off the built tx \u2014 ${sig.slice(0, 16)}\u2026`);
|
|
12230
|
+
}
|
|
12231
|
+
tx.sign([R]);
|
|
12232
|
+
return tx;
|
|
12233
|
+
}
|
|
12234
|
+
tx.partialSign(R);
|
|
12235
|
+
return tx;
|
|
12236
|
+
};
|
|
12237
|
+
const signMessage = async (m) => import_tweetnacl8.default.sign.detached(m, R.secretKey);
|
|
12238
|
+
const utxoKeypair = await deriveUtxoKeypairFromSpendKey(opts.noteSpendKey);
|
|
12239
|
+
const nk = getNkFromUtxoPrivateKey(utxoKeypair.privateKey);
|
|
12240
|
+
const { utxo, noteSalt } = await createRecoverableDepositUtxo(amount, nk, mint);
|
|
12241
|
+
const zeroInput = await createZeroUtxo(mint);
|
|
12242
|
+
const rBefore = await conn.getBalance(R.publicKey);
|
|
12243
|
+
let result;
|
|
12244
|
+
try {
|
|
12245
|
+
result = await transact(
|
|
12246
|
+
{ inputUtxos: [zeroInput], outputUtxos: [utxo], externalAmount: amount, depositor: R.publicKey },
|
|
12247
|
+
{
|
|
12248
|
+
connection: conn,
|
|
12249
|
+
programId,
|
|
12250
|
+
relayUrl,
|
|
12251
|
+
// NO depositorKeypair: the SDK branches `if (keypair) … else if (signTransaction)`
|
|
12252
|
+
// (transact.ts:2786, :2936), so passing one silently skips the funding seam.
|
|
12253
|
+
depositorPublicKey: R.publicKey,
|
|
12254
|
+
walletPublicKey: R.publicKey,
|
|
12255
|
+
signTransaction: signTransaction2,
|
|
12256
|
+
signMessage,
|
|
12257
|
+
chainNoteViewingKeyNk: nk,
|
|
12258
|
+
chainNoteSalt: noteSalt,
|
|
12259
|
+
relaySupplementalAlt: true,
|
|
12260
|
+
onProgress: (s) => log(` \xB7 ${s}`)
|
|
12261
|
+
}
|
|
12262
|
+
);
|
|
12263
|
+
} catch (e) {
|
|
12264
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
12265
|
+
throw new DepositError(msg, fundedAccounts, measuredSize);
|
|
12266
|
+
}
|
|
12267
|
+
const rAfter = await conn.getBalance(R.publicKey);
|
|
12268
|
+
const noteIndex = result.outputUtxos[0].index;
|
|
12269
|
+
if (noteIndex === void 0) throw new Error("deposit landed but the note has no index \u2014 it cannot be discovered later");
|
|
12270
|
+
return {
|
|
12271
|
+
signature: result.signature,
|
|
12272
|
+
noteIndex,
|
|
12273
|
+
amount: result.outputUtxos[0].amount,
|
|
12274
|
+
txSize: measuredSize,
|
|
12275
|
+
rBefore,
|
|
12276
|
+
rAfter,
|
|
12277
|
+
rentExempt: rAfter >= MAINNET_RENT_0,
|
|
12278
|
+
inputNullifiers: result.inputNullifiers,
|
|
12279
|
+
fundedAccounts
|
|
12280
|
+
};
|
|
12281
|
+
}
|
|
12282
|
+
|
|
12283
|
+
// src/bridge/cleanup.ts
|
|
12284
|
+
var import_web317 = require("@solana/web3.js");
|
|
12285
|
+
var import_spl_token6 = require("@solana/spl-token");
|
|
12286
|
+
var DEFAULT_MINT = new import_web317.PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
|
|
12287
|
+
async function cleanupReceivingAddress(conn, R, dustDestination, mint = DEFAULT_MINT) {
|
|
12288
|
+
assertAllowedRpcConnection(conn);
|
|
12289
|
+
const rAta = (0, import_spl_token6.getAssociatedTokenAddressSync)(mint, R.publicKey);
|
|
12290
|
+
const info = await conn.getAccountInfo(rAta);
|
|
12291
|
+
if (!info) {
|
|
12292
|
+
return {
|
|
12293
|
+
closed: false,
|
|
12294
|
+
dustSwept: 0n,
|
|
12295
|
+
rentReturned: 0,
|
|
12296
|
+
destination: null,
|
|
12297
|
+
signature: null,
|
|
12298
|
+
note: "no token account \u2014 nothing to recover"
|
|
12299
|
+
};
|
|
12300
|
+
}
|
|
12301
|
+
const bal = BigInt((await conn.getTokenAccountBalance(rAta)).value.amount);
|
|
12302
|
+
const rentHeld = info.lamports;
|
|
12303
|
+
const before = await conn.getBalance(R.publicKey);
|
|
12304
|
+
const ixs = [];
|
|
12305
|
+
let destination = null;
|
|
12306
|
+
if (bal > 0n && !dustDestination) {
|
|
12307
|
+
return {
|
|
12308
|
+
closed: false,
|
|
12309
|
+
dustSwept: 0n,
|
|
12310
|
+
rentReturned: 0,
|
|
12311
|
+
destination: null,
|
|
12312
|
+
signature: null,
|
|
12313
|
+
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`
|
|
12314
|
+
};
|
|
12315
|
+
}
|
|
12316
|
+
if (bal > 0n && dustDestination) {
|
|
12317
|
+
const destAta = (0, import_spl_token6.getAssociatedTokenAddressSync)(mint, dustDestination);
|
|
12318
|
+
const destInfo = await conn.getAccountInfo(destAta);
|
|
12319
|
+
if (!destInfo) {
|
|
12320
|
+
const ataRent = await (0, import_spl_token6.getMinimumBalanceForRentExemptAccount)(conn);
|
|
12321
|
+
if (before < ataRent + CLEANUP_FEE_BUDGET) {
|
|
12322
|
+
throw new Error(
|
|
12323
|
+
`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.`
|
|
12324
|
+
);
|
|
12325
|
+
}
|
|
12326
|
+
ixs.push((0, import_spl_token6.createAssociatedTokenAccountIdempotentInstruction)(R.publicKey, destAta, dustDestination, mint));
|
|
12327
|
+
}
|
|
12328
|
+
ixs.push((0, import_spl_token6.createTransferInstruction)(rAta, destAta, R.publicKey, bal));
|
|
12329
|
+
destination = destAta.toBase58();
|
|
12330
|
+
}
|
|
12331
|
+
ixs.push((0, import_spl_token6.createCloseAccountInstruction)(rAta, R.publicKey, R.publicKey));
|
|
12332
|
+
const signature = await (0, import_web317.sendAndConfirmTransaction)(conn, new import_web317.Transaction().add(...ixs), [R], {
|
|
12333
|
+
commitment: "confirmed"
|
|
12334
|
+
});
|
|
12335
|
+
const after = await conn.getBalance(R.publicKey);
|
|
12336
|
+
const gone = await conn.getAccountInfo(rAta) === null;
|
|
12337
|
+
return {
|
|
12338
|
+
closed: gone,
|
|
12339
|
+
dustSwept: bal,
|
|
12340
|
+
rentReturned: after - before,
|
|
12341
|
+
destination,
|
|
12342
|
+
signature,
|
|
12343
|
+
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`
|
|
12344
|
+
};
|
|
12345
|
+
}
|
|
12346
|
+
|
|
12347
|
+
// src/bridge/quote.ts
|
|
12348
|
+
var MIN_DEPOSIT_SPL_BASE_UNITS = 1000000n;
|
|
12349
|
+
var WITHDRAW_FIXED_FEE = 450000n;
|
|
12350
|
+
var WITHDRAW_FEE_BPS = 30n;
|
|
12351
|
+
function withdrawFeeAt(amount, fixedFee, feeBps) {
|
|
12352
|
+
return fixedFee + amount * feeBps / 10000n;
|
|
12353
|
+
}
|
|
12354
|
+
var MAX_FEE_FRACTION = 0.063;
|
|
12355
|
+
var FIXED_ROUND_TRIP = 903000n;
|
|
12356
|
+
var ECONOMIC_MINIMUM = FIXED_ROUND_TRIP * 1000000n / BigInt(Math.round((MAX_FEE_FRACTION - 3e-3) * 1e6));
|
|
12357
|
+
function withdrawFeeFor(amount) {
|
|
12358
|
+
return withdrawFeeAt(amount, WITHDRAW_FIXED_FEE, WITHDRAW_FEE_BPS);
|
|
12359
|
+
}
|
|
12360
|
+
function assessBridgeQuote(q) {
|
|
12361
|
+
if (q.sent < 0n) {
|
|
12362
|
+
throw new Error(`assessBridgeQuote: sent must not be negative (got ${q.sent}).`);
|
|
12363
|
+
}
|
|
12364
|
+
if (q.arrivesMin > q.sent) {
|
|
12365
|
+
throw new Error(
|
|
12366
|
+
`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.`
|
|
12367
|
+
);
|
|
12368
|
+
}
|
|
12369
|
+
const shieldedMinRaw = q.arrivesMin - q.paymasterFee;
|
|
12370
|
+
const shieldedTargetRaw = q.arrivesTarget - q.paymasterFee;
|
|
12371
|
+
const shieldedMin = shieldedMinRaw > 0n ? shieldedMinRaw : 0n;
|
|
12372
|
+
const shieldedTarget = shieldedTargetRaw > 0n ? shieldedTargetRaw : 0n;
|
|
12373
|
+
const viable = shieldedMinRaw >= MIN_DEPOSIT_SPL_BASE_UNITS;
|
|
12374
|
+
const economic = q.sent >= ECONOMIC_MINIMUM;
|
|
12375
|
+
const withdrawFee = shieldedMin > 0n ? withdrawFeeAt(shieldedMin, q.liveWithdrawFixedFee ?? WITHDRAW_FIXED_FEE, q.liveWithdrawFeeBps ?? WITHDRAW_FEE_BPS) : 0n;
|
|
12376
|
+
const roundTripCost = q.sent - (shieldedMin > withdrawFee ? shieldedMin - withdrawFee : 0n);
|
|
12377
|
+
const roundTripFraction = q.sent > 0n ? Number(roundTripCost) / Number(q.sent) : 0;
|
|
12378
|
+
const reasons = [];
|
|
12379
|
+
if (!viable) {
|
|
12380
|
+
const short = MIN_DEPOSIT_SPL_BASE_UNITS - shieldedMinRaw;
|
|
12381
|
+
reasons.push(
|
|
12382
|
+
(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.`
|
|
12383
|
+
);
|
|
12384
|
+
}
|
|
12385
|
+
if (viable && !economic) {
|
|
12386
|
+
reasons.push(
|
|
12387
|
+
`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.`
|
|
12388
|
+
);
|
|
12389
|
+
}
|
|
12390
|
+
return {
|
|
12391
|
+
...q,
|
|
12392
|
+
shieldedMin,
|
|
12393
|
+
shieldedTarget,
|
|
12394
|
+
viable,
|
|
12395
|
+
economic,
|
|
12396
|
+
withdrawFee,
|
|
12397
|
+
roundTripCost,
|
|
12398
|
+
roundTripFraction,
|
|
12399
|
+
reasons
|
|
12400
|
+
};
|
|
12401
|
+
}
|
|
12402
|
+
var fmt = (v) => `${(Number(v) / 1e6).toFixed(6)} USDC`;
|
|
12403
|
+
function renderAssessment(a) {
|
|
12404
|
+
const L = [];
|
|
12405
|
+
L.push(` you send ${fmt(a.sent)}`);
|
|
12406
|
+
L.push(` arrives at least ${fmt(a.arrivesMin)} (target ${fmt(a.arrivesTarget)}, not a promise)`);
|
|
12407
|
+
L.push(` paymaster fee ${fmt(a.paymasterFee)} buys the receiver its SOL, so your wallet stays off chain`);
|
|
12408
|
+
L.push(` SHIELDED ${fmt(a.shieldedMin)} at least \u2014 this is what you end up with`);
|
|
12409
|
+
L.push(` to withdraw later ${fmt(a.withdrawFee)} the program's fee, whenever you take it out`);
|
|
12410
|
+
for (const r of a.reasons) L.push(`
|
|
12411
|
+
${a.viable ? "NOTE" : "REFUSED"}: ${r}`);
|
|
12412
|
+
return L.join("\n");
|
|
12413
|
+
}
|
|
12414
|
+
|
|
12415
|
+
// src/bridge/retry.ts
|
|
12416
|
+
var TERMINAL_FAILURE = /DepositTooSmall|minimum is 1\.000000|insufficient|holds only|below the program|kora wants|over the .* ceiling/i;
|
|
12417
|
+
function isTerminalFailure(message) {
|
|
12418
|
+
return TERMINAL_FAILURE.test(message);
|
|
12419
|
+
}
|
|
12420
|
+
function classifyPostFailure(attempted, after) {
|
|
12421
|
+
if (after === null) return "unknown";
|
|
12422
|
+
if (after === 0n) return "landed";
|
|
12423
|
+
if (after >= attempted) return "did-not-land";
|
|
12424
|
+
return "unknown";
|
|
12425
|
+
}
|
|
12426
|
+
function mayRetry(verdict, message) {
|
|
12427
|
+
return verdict === "did-not-land" && !isTerminalFailure(message);
|
|
12428
|
+
}
|
|
12429
|
+
function retryDelayMs(attempt) {
|
|
12430
|
+
return 15e3 * Math.max(0, attempt - 1);
|
|
12431
|
+
}
|
|
12432
|
+
var DoNotRetry = class extends Error {
|
|
12433
|
+
constructor(message) {
|
|
12434
|
+
super(message);
|
|
12435
|
+
this.name = "DoNotRetry";
|
|
12436
|
+
}
|
|
12437
|
+
};
|
|
12438
|
+
async function withShieldRetries(attempt, hooks = {}) {
|
|
12439
|
+
const attempts = hooks.attempts ?? 3;
|
|
12440
|
+
const sleep4 = hooks.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
12441
|
+
let last = "";
|
|
12442
|
+
for (let n = 1; n <= attempts; n++) {
|
|
12443
|
+
if (n > 1) {
|
|
12444
|
+
const delay = retryDelayMs(n);
|
|
12445
|
+
hooks.onRetry?.(n, delay, last);
|
|
12446
|
+
await sleep4(delay);
|
|
12447
|
+
}
|
|
12448
|
+
try {
|
|
12449
|
+
return await attempt(n);
|
|
12450
|
+
} catch (e) {
|
|
12451
|
+
if (e instanceof DoNotRetry) throw e;
|
|
12452
|
+
last = e instanceof Error ? e.message : String(e);
|
|
12453
|
+
if (isTerminalFailure(last) || n === attempts) throw e;
|
|
12454
|
+
}
|
|
12455
|
+
}
|
|
12456
|
+
throw new Error("unreachable");
|
|
12457
|
+
}
|
|
12458
|
+
|
|
11635
12459
|
// src/index.ts
|
|
11636
|
-
var VERSION = "0.2.
|
|
12460
|
+
var VERSION = "0.2.3";
|
|
11637
12461
|
var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
11638
12462
|
// Annotate the CommonJS export names for ESM import in node:
|
|
11639
12463
|
0 && (module.exports = {
|
|
12464
|
+
BRIDGE_ESCROW_LABEL,
|
|
11640
12465
|
BUILD_ALLOWS_LOCAL_ENDPOINTS,
|
|
11641
12466
|
CHAIN_NOTE_SALT_BITS,
|
|
12467
|
+
CLEANUP_FEE_BUDGET,
|
|
11642
12468
|
CLOAK_PRODUCTION_RELAY_URL,
|
|
11643
12469
|
CLOAK_PROGRAM_ID,
|
|
11644
12470
|
CloakError,
|
|
@@ -11646,16 +12472,25 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
|
11646
12472
|
DEFAULT_TRANSACTION_CIRCUITS_URL,
|
|
11647
12473
|
DELIVERY_MEMO_TAG,
|
|
11648
12474
|
DELIVERY_REGISTRY_SEED,
|
|
12475
|
+
DepositError,
|
|
12476
|
+
DoNotRetry,
|
|
12477
|
+
ECONOMIC_MINIMUM,
|
|
11649
12478
|
EXPECTED_CIRCUIT_HASHES,
|
|
12479
|
+
FEE_BUDGET,
|
|
11650
12480
|
FIXED_FEE_LAMPORTS,
|
|
11651
12481
|
InsecureRandomnessError,
|
|
11652
12482
|
LAMPORTS_PER_SOL,
|
|
11653
12483
|
LocalStorageAdapter,
|
|
12484
|
+
MAINNET_RENT_0,
|
|
12485
|
+
MAINNET_RENT_1,
|
|
12486
|
+
MAX_RECEIVER_INDEX,
|
|
11654
12487
|
MERKLE_TREE_HEIGHT,
|
|
11655
12488
|
MIN_DEPOSIT_LAMPORTS,
|
|
12489
|
+
MIN_DEPOSIT_SPL_BASE_UNITS,
|
|
11656
12490
|
MemoryStorageAdapter,
|
|
11657
12491
|
MerkleTree,
|
|
11658
12492
|
NATIVE_SOL_MINT,
|
|
12493
|
+
ONECLICK_PUBKEY_B58,
|
|
11659
12494
|
RECIPIENT_DELIVERY_CIPHERTEXT_LEN,
|
|
11660
12495
|
RECIPIENT_DELIVERY_EPHEMERAL_PK_LEN,
|
|
11661
12496
|
RECIPIENT_DELIVERY_NONCE_LEN,
|
|
@@ -11674,6 +12509,7 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
|
11674
12509
|
SettlementVerificationError,
|
|
11675
12510
|
ShieldPoolErrors,
|
|
11676
12511
|
SimpleWallet,
|
|
12512
|
+
TERMINAL_FAILURE,
|
|
11677
12513
|
TRANSACTION_CIRCUITS_VERSION,
|
|
11678
12514
|
TRANSACT_AUTH_FIELDS,
|
|
11679
12515
|
TRANSACT_SWAP_AUTH_FIELDS,
|
|
@@ -11683,8 +12519,12 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
|
11683
12519
|
VARIABLE_FEE_NUMERATOR,
|
|
11684
12520
|
VARIABLE_FEE_RATE,
|
|
11685
12521
|
VERSION,
|
|
12522
|
+
WITHDRAW_FEE_BPS,
|
|
12523
|
+
WITHDRAW_FIXED_FEE,
|
|
11686
12524
|
assertDirectSubmissionLanded,
|
|
12525
|
+
assertInputMints,
|
|
11687
12526
|
assertTransactionCircuitIntegrity,
|
|
12527
|
+
assessBridgeQuote,
|
|
11688
12528
|
bigintToBytes32,
|
|
11689
12529
|
bigintToHex,
|
|
11690
12530
|
buildMerkleTree,
|
|
@@ -11699,12 +12539,16 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
|
11699
12539
|
calculateRelayFee,
|
|
11700
12540
|
canRebuildMerkleTreeFromChain,
|
|
11701
12541
|
canonicalJson,
|
|
12542
|
+
canonicalPayloadString,
|
|
11702
12543
|
chainNoteFromBase64,
|
|
11703
12544
|
chainNoteToBase64,
|
|
12545
|
+
classifyPostFailure,
|
|
11704
12546
|
classifyRelayError,
|
|
12547
|
+
cleanupReceivingAddress,
|
|
11705
12548
|
cleanupStalePendingOperations,
|
|
11706
12549
|
clearPendingDeposits,
|
|
11707
12550
|
clearPendingWithdrawals,
|
|
12551
|
+
cloakBridgeRail,
|
|
11708
12552
|
computeChainNoteHash,
|
|
11709
12553
|
computeExtDataHash,
|
|
11710
12554
|
computeMerkleRoot,
|
|
@@ -11726,10 +12570,13 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
|
11726
12570
|
decryptCompactChainNote,
|
|
11727
12571
|
decryptComplianceMetadataWithMasterKey,
|
|
11728
12572
|
decryptTransactionMetadata,
|
|
12573
|
+
depositFromDerivedKey,
|
|
12574
|
+
deriveBridgeReceiver,
|
|
11729
12575
|
deriveChangeNoteBlinding,
|
|
11730
12576
|
deriveDepositNoteSecrets,
|
|
11731
12577
|
deriveDiversifiedViewingKey,
|
|
11732
12578
|
deriveDiversifier,
|
|
12579
|
+
deriveFundingTargets,
|
|
11733
12580
|
deriveInputNullifierPdas,
|
|
11734
12581
|
derivePublicKey,
|
|
11735
12582
|
deriveSpendKey,
|
|
@@ -11768,6 +12615,7 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
|
11768
12615
|
formatErrorForLogging,
|
|
11769
12616
|
formatSol,
|
|
11770
12617
|
fullWithdraw,
|
|
12618
|
+
fundReceiverViaPaymaster,
|
|
11771
12619
|
generateCloakKeys,
|
|
11772
12620
|
generateCommitmentAsync,
|
|
11773
12621
|
generateMasterSeed,
|
|
@@ -11803,18 +12651,21 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
|
11803
12651
|
isReactNative,
|
|
11804
12652
|
isRootNotFoundError,
|
|
11805
12653
|
isSubmissionOutcomeUnknownResponse,
|
|
12654
|
+
isTerminalFailure,
|
|
11806
12655
|
isValidHex,
|
|
11807
12656
|
isValidRpcUrl,
|
|
11808
12657
|
isValidSolanaAddress,
|
|
11809
12658
|
isWithdrawAmountSufficient,
|
|
11810
12659
|
isWithdrawable,
|
|
11811
12660
|
keypairToAdapter,
|
|
12661
|
+
listBridgeDeposits,
|
|
11812
12662
|
loadPendingDeposits,
|
|
11813
12663
|
loadPendingWithdrawals,
|
|
11814
12664
|
loadVerifiedCircuitArtifacts,
|
|
11815
12665
|
matchChangeNote,
|
|
11816
12666
|
matchDepositNote,
|
|
11817
12667
|
matchSwapRefundLeaf,
|
|
12668
|
+
mayRetry,
|
|
11818
12669
|
openRecipientDeliveryNote,
|
|
11819
12670
|
parseAmount,
|
|
11820
12671
|
parseDeliveryCarrierMemo,
|
|
@@ -11837,11 +12688,14 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
|
11837
12688
|
randomDepositNoteSalt,
|
|
11838
12689
|
randomFieldElement,
|
|
11839
12690
|
readMerkleTreeState,
|
|
12691
|
+
readRent,
|
|
11840
12692
|
recipientDeliveryNoteToBase64,
|
|
11841
12693
|
registerViewingKey,
|
|
11842
12694
|
removePendingDeposit,
|
|
11843
12695
|
removePendingWithdrawal,
|
|
12696
|
+
renderAssessment,
|
|
11844
12697
|
resolveCircuitsBase,
|
|
12698
|
+
retryDelayMs,
|
|
11845
12699
|
savePendingDeposit,
|
|
11846
12700
|
savePendingWithdrawal,
|
|
11847
12701
|
scanNotesForWallet,
|
|
@@ -11874,13 +12728,17 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
|
11874
12728
|
validateDepositParams,
|
|
11875
12729
|
validateNote,
|
|
11876
12730
|
validateOutputsSum,
|
|
12731
|
+
validatePaymasterTopUpTransaction,
|
|
11877
12732
|
validateRoot,
|
|
11878
12733
|
validateTransfers,
|
|
11879
12734
|
validateWalletConnected,
|
|
11880
12735
|
validateWithdrawableNote,
|
|
11881
12736
|
verifyAllCircuits,
|
|
11882
12737
|
verifyCircuitIntegrity,
|
|
12738
|
+
verifyQuoteSignature,
|
|
11883
12739
|
verifyUtxos,
|
|
11884
12740
|
waitForRoot,
|
|
11885
|
-
|
|
12741
|
+
withShieldRetries,
|
|
12742
|
+
withTiming,
|
|
12743
|
+
withdrawFeeFor
|
|
11886
12744
|
});
|