@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/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
- withTiming: () => withTiming
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 = [CLOAK_PRODUCTION_RELAY_URL];
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 [pda] = getNullifierPDA(pool, nullifierBytes, programId);
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 [pda] = getNullifierPDA(pool, bigintToBytes324(nullifier), programId);
3004
- pdas.push(pda);
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 = retryDelayMs * Math.pow(2, attempt) + Math.random() * 500;
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(retryDelayMs * Math.pow(1.5, submissionAttempts - 1), 5e3);
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(retryDelayMs * Math.pow(2, Math.min(submissionAttempts, 4)), 5e3);
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(retryDelayMs * Math.pow(1.5, attempt - 1), 5e3);
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(retryDelayMs * Math.pow(2, Math.min(attempt, 4)), 5e3);
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.1";
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
- withTiming
12741
+ withShieldRetries,
12742
+ withTiming,
12743
+ withdrawFeeFor
11886
12744
  });