@cloak.dev/sdk 0.1.3 → 0.1.5

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
@@ -301,12 +301,15 @@ __export(index_exports, {
301
301
  MemoryStorageAdapter: () => MemoryStorageAdapter,
302
302
  MerkleTree: () => MerkleTree,
303
303
  NATIVE_SOL_MINT: () => NATIVE_SOL_MINT,
304
+ RelayInternalError: () => RelayInternalError,
304
305
  RelayService: () => RelayService,
305
306
  RootNotFoundError: () => RootNotFoundError,
306
307
  SCANNER_SUPPORTS_TRANSACT_SWAP: () => SCANNER_SUPPORTS_TRANSACT_SWAP,
307
308
  SIGN_IN_MESSAGE: () => SIGN_IN_MESSAGE,
309
+ SanctionsQuoteError: () => SanctionsQuoteError,
308
310
  ShieldPoolErrors: () => ShieldPoolErrors,
309
311
  SimpleWallet: () => SimpleWallet,
312
+ UtxoAlreadySpentError: () => UtxoAlreadySpentError,
310
313
  UtxoWallet: () => UtxoWallet,
311
314
  VARIABLE_FEE_DENOMINATOR: () => VARIABLE_FEE_DENOMINATOR,
312
315
  VARIABLE_FEE_NUMERATOR: () => VARIABLE_FEE_NUMERATOR,
@@ -325,6 +328,7 @@ __export(index_exports, {
325
328
  calculateRelayFee: () => calculateRelayFee,
326
329
  chainNoteFromBase64: () => chainNoteFromBase64,
327
330
  chainNoteToBase64: () => chainNoteToBase64,
331
+ classifyRelayError: () => classifyRelayError,
328
332
  cleanupStalePendingOperations: () => cleanupStalePendingOperations,
329
333
  clearPendingDeposits: () => clearPendingDeposits,
330
334
  clearPendingWithdrawals: () => clearPendingWithdrawals,
@@ -437,6 +441,7 @@ __export(index_exports, {
437
441
  partialWithdraw: () => partialWithdraw,
438
442
  poseidonHash: () => poseidonHash,
439
443
  preflightCheck: () => preflightCheck,
444
+ preflightNullifiers: () => preflightNullifiers,
440
445
  prepareEncryptedOutput: () => prepareEncryptedOutput,
441
446
  prepareEncryptedOutputForRecipient: () => prepareEncryptedOutputForRecipient,
442
447
  proofToBytes: () => proofToBytes,
@@ -484,6 +489,7 @@ __export(index_exports, {
484
489
  validateWithdrawableNote: () => validateWithdrawableNote,
485
490
  verifyAllCircuits: () => verifyAllCircuits,
486
491
  verifyCircuitIntegrity: () => verifyCircuitIntegrity,
492
+ verifyUtxos: () => verifyUtxos,
487
493
  waitForRoot: () => waitForRoot,
488
494
  withTiming: () => withTiming
489
495
  });
@@ -1339,6 +1345,71 @@ function parseRelayErrorResponse(responseText) {
1339
1345
  return { isRootNotFound: false };
1340
1346
  }
1341
1347
  }
1348
+ var UtxoAlreadySpentError = class extends Error {
1349
+ constructor(message, spentUtxoCommitments = []) {
1350
+ super(message);
1351
+ this.spentUtxoCommitments = spentUtxoCommitments;
1352
+ this.name = "UtxoAlreadySpentError";
1353
+ }
1354
+ };
1355
+ var SanctionsQuoteError = class extends Error {
1356
+ constructor(message, onChainCode, subKind) {
1357
+ super(message);
1358
+ this.onChainCode = onChainCode;
1359
+ this.subKind = subKind;
1360
+ this.name = "SanctionsQuoteError";
1361
+ }
1362
+ };
1363
+ var RelayInternalError = class extends Error {
1364
+ constructor(message, relayMessage, httpStatus, cachedTreeFromChain) {
1365
+ super(message);
1366
+ this.relayMessage = relayMessage;
1367
+ this.httpStatus = httpStatus;
1368
+ this.cachedTreeFromChain = cachedTreeFromChain;
1369
+ this.name = "RelayInternalError";
1370
+ }
1371
+ };
1372
+ function classifyRelayError(responseText, httpStatus) {
1373
+ let inner = "";
1374
+ try {
1375
+ const parsed = JSON.parse(responseText);
1376
+ inner = parsed?.message ?? "";
1377
+ } catch {
1378
+ inner = responseText;
1379
+ }
1380
+ const haystack = inner.toLowerCase();
1381
+ if (haystack.includes("0x1020") || haystack.includes("doublespend")) {
1382
+ return new UtxoAlreadySpentError(
1383
+ "This shielded balance was already spent. The UTXO's nullifier is registered on-chain \u2014 typically means you spent it via another session or device. Run verifyUtxos() to reconcile local state."
1384
+ );
1385
+ }
1386
+ if (haystack.includes("0x10b0") || haystack.includes("rangequoteexpired")) {
1387
+ return new SanctionsQuoteError(
1388
+ "Range sanctions quote expired before the on-chain program processed the transaction. The relay's quote-expiry window is too short relative to proof-gen + confirmation latency, or on-chain clock drifted. Retry the send; the relay will issue a fresh quote.",
1389
+ 4272,
1390
+ "expired"
1391
+ );
1392
+ }
1393
+ if (haystack.includes("0x10b2") || haystack.includes("rangequotewalletmismatch")) {
1394
+ return new SanctionsQuoteError(
1395
+ "Range sanctions quote's wallet doesn't match the on-chain expected wallet. Indicates a relay config drift \u2014 the sender used for quote signing doesn't match the program's pool.range_signer / fee payer. Relay operators must fix; caller retry will not help.",
1396
+ 4274,
1397
+ "wallet_mismatch"
1398
+ );
1399
+ }
1400
+ if (haystack.includes("0x10b3") || haystack.includes("rangequotemissinged25519")) {
1401
+ return new SanctionsQuoteError(
1402
+ "Transaction reached the on-chain program without the required Ed25519 sanctions quote instruction. This is a relay bug \u2014 the relay constructed the tx without attaching the signed quote. Caller retry will not help.",
1403
+ 4275,
1404
+ "missing_ix"
1405
+ );
1406
+ }
1407
+ return new RelayInternalError(
1408
+ `Relay returned ${httpStatus ?? "an error"}: ${inner || responseText}`,
1409
+ inner || responseText,
1410
+ httpStatus
1411
+ );
1412
+ }
1342
1413
  var ShieldPoolErrors = {
1343
1414
  // Root management errors
1344
1415
  4096: "Invalid Merkle root",
@@ -4635,6 +4706,64 @@ async function decryptComplianceMetadataWithMasterKey(encrypted, masterComplianc
4635
4706
  return decryptWithSharedSecret(payload, sharedSecret);
4636
4707
  }
4637
4708
 
4709
+ // src/utils/verify-utxos.ts
4710
+ init_utxo();
4711
+ async function verifyUtxos(utxos, connection, programId, commitment = "confirmed") {
4712
+ const checkable = [];
4713
+ const skipped = [];
4714
+ for (const utxo of utxos) {
4715
+ if (utxo.commitment === void 0 || utxo.mintAddress === void 0 || utxo.keypair === void 0 || utxo.keypair.privateKey === void 0) {
4716
+ skipped.push(utxo);
4717
+ continue;
4718
+ }
4719
+ try {
4720
+ const nullifierBig = await computeNullifier2(utxo);
4721
+ const nullifierHex = nullifierBig.toString(16).padStart(64, "0");
4722
+ const nullifierBytes = Buffer.from(nullifierHex, "hex");
4723
+ const { pool } = getShieldPoolPDAs(programId, utxo.mintAddress);
4724
+ const [pda] = getNullifierPDA(pool, nullifierBytes, programId);
4725
+ checkable.push({ utxo, pda });
4726
+ } catch {
4727
+ skipped.push(utxo);
4728
+ }
4729
+ }
4730
+ if (checkable.length === 0) {
4731
+ return { spent: [], unspent: [], skipped };
4732
+ }
4733
+ const CHUNK = 100;
4734
+ const infos = [];
4735
+ for (let i = 0; i < checkable.length; i += CHUNK) {
4736
+ const page = checkable.slice(i, i + CHUNK).map((e) => e.pda);
4737
+ const pageInfos = await connection.getMultipleAccountsInfo(page, commitment);
4738
+ infos.push(...pageInfos);
4739
+ }
4740
+ const spent = [];
4741
+ const unspent = [];
4742
+ for (let i = 0; i < checkable.length; i++) {
4743
+ if (infos[i] !== null) {
4744
+ spent.push(checkable[i].utxo);
4745
+ } else {
4746
+ unspent.push(checkable[i].utxo);
4747
+ }
4748
+ }
4749
+ return { spent, unspent, skipped };
4750
+ }
4751
+ async function preflightNullifiers(utxos, connection, programId, commitment = "confirmed") {
4752
+ if (utxos.length === 0) return;
4753
+ const result = await verifyUtxos(utxos, connection, programId, commitment);
4754
+ if (result.spent.length > 0) {
4755
+ const allCommitments = result.spent.map(
4756
+ (u) => u.commitment !== void 0 ? u.commitment.toString(16).padStart(64, "0") : "<unknown>"
4757
+ );
4758
+ const preview = allCommitments.slice(0, 5);
4759
+ const extra = allCommitments.length > preview.length ? ` (+${allCommitments.length - preview.length} more)` : "";
4760
+ throw new UtxoAlreadySpentError(
4761
+ `Pre-flight detected ${allCommitments.length} already-spent UTXO(s). First spent commitment(s): ${preview.join(", ")}${extra}. Call verifyUtxos() and remove these from local state before retrying.`,
4762
+ allCommitments
4763
+ );
4764
+ }
4765
+ }
4766
+
4638
4767
  // src/services/risk-oracle.ts
4639
4768
  function buildRangeRiskScoreJob(OracleJob, walletAddress) {
4640
4769
  return OracleJob.fromObject({
@@ -4978,12 +5107,18 @@ async function buildMerkleTree(commitments, height = MERKLE_TREE_HEIGHT2) {
4978
5107
 
4979
5108
  // src/utils/relay-client.ts
4980
5109
  var import_bs58 = __toESM(require("bs58"), 1);
5110
+ var bs58 = import_bs58.default.default || import_bs58.default;
4981
5111
  var TRANSACT_TAG = 0;
4982
5112
  var TRANSACT_SWAP_TAG = 1;
4983
- var DEPOSIT_TAG = 1;
4984
5113
  var PROOF_LEN = 256;
4985
5114
  var PUBLIC_INPUTS_LEN = 264;
4986
5115
  var COMMITMENTS_OFFSET = 168;
5116
+ function isAllZero(b) {
5117
+ for (let i = 0; i < b.length; i++) {
5118
+ if (b[i] !== 0) return false;
5119
+ }
5120
+ return true;
5121
+ }
4987
5122
  var DEFAULT_TIMEOUT_MS = 3e4;
4988
5123
  var DEFAULT_MAX_RETRIES = 3;
4989
5124
  async function fetchWithRetry(url, options = {}) {
@@ -5121,24 +5256,20 @@ async function buildMerkleTreeFromRelay(relayUrl, options) {
5121
5256
  function extractCommitmentsFromInstruction(data) {
5122
5257
  if (data.length === 0) return [];
5123
5258
  const tag = data[0];
5124
- if (tag === TRANSACT_TAG || tag === TRANSACT_SWAP_TAG) {
5125
- const publicInputsStart = 1 + PROOF_LEN;
5126
- const publicInputsEnd = publicInputsStart + PUBLIC_INPUTS_LEN;
5127
- if (data.length < publicInputsEnd) return [];
5128
- const publicInputs = data.slice(publicInputsStart, publicInputsEnd);
5129
- const commitments = [];
5130
- const c0 = publicInputs.slice(COMMITMENTS_OFFSET, COMMITMENTS_OFFSET + 32);
5131
- const c1 = publicInputs.slice(COMMITMENTS_OFFSET + 32, COMMITMENTS_OFFSET + 64);
5132
- commitments.push(c0);
5133
- commitments.push(c1);
5134
- return commitments;
5135
- }
5136
- if (tag === DEPOSIT_TAG) {
5137
- if (data.length < 1 + 8 + 32) return [];
5138
- const commitment = data.slice(1 + 8, 1 + 8 + 32);
5139
- return [commitment];
5140
- }
5141
- return [];
5259
+ if (tag !== TRANSACT_TAG && tag !== TRANSACT_SWAP_TAG) return [];
5260
+ const publicInputsStart = 1 + PROOF_LEN;
5261
+ const publicInputsEnd = publicInputsStart + PUBLIC_INPUTS_LEN;
5262
+ if (data.length < publicInputsEnd) return [];
5263
+ const publicInputs = data.slice(publicInputsStart, publicInputsEnd);
5264
+ const c0 = publicInputs.slice(COMMITMENTS_OFFSET, COMMITMENTS_OFFSET + 32);
5265
+ const c1 = publicInputs.slice(
5266
+ COMMITMENTS_OFFSET + 32,
5267
+ COMMITMENTS_OFFSET + 64
5268
+ );
5269
+ const out = [];
5270
+ if (!isAllZero(c0)) out.push(c0);
5271
+ if (!isAllZero(c1)) out.push(c1);
5272
+ return out;
5142
5273
  }
5143
5274
  function isBrowser() {
5144
5275
  return typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
@@ -5177,7 +5308,7 @@ async function buildMerkleTreeFromChain(connection, programId, merkleTree, onPro
5177
5308
  for (const ix of tx.transaction.message.instructions) {
5178
5309
  if (!("programId" in ix) || !("data" in ix)) continue;
5179
5310
  if (ix.programId.toBase58() !== programIdBase58) continue;
5180
- const data = import_bs58.default.decode(ix.data);
5311
+ const data = bs58.decode(ix.data);
5181
5312
  const extracted = extractCommitmentsFromInstruction(data);
5182
5313
  for (const commitment of extracted) {
5183
5314
  commitments.push(BigInt("0x" + Buffer.from(commitment).toString("hex")));
@@ -6745,6 +6876,7 @@ async function transact(params, options) {
6745
6876
  if (outputUtxos.length > 2) {
6746
6877
  throw new Error("Maximum 2 output UTXOs allowed");
6747
6878
  }
6879
+ await preflightNullifiers(inputUtxos, connection, programId);
6748
6880
  const mint = inputUtxos.find((u) => u.amount > 0)?.mintAddress ?? outputUtxos.find((u) => u.amount > 0)?.mintAddress ?? NATIVE_SOL_MINT;
6749
6881
  const paddedInputs = [...inputUtxos];
6750
6882
  while (paddedInputs.length < 2) {
@@ -6815,7 +6947,7 @@ async function transact(params, options) {
6815
6947
  }
6816
6948
  merkleTree = null;
6817
6949
  }
6818
- if (needsTreeForProof && relayUrl && !relayTreeDisabledForMint && !forceChainIndexing && (forceRefresh || !merkleTree)) {
6950
+ if (needsTreeForProof && relayUrl && !relayTreeDisabledForMint && !forceChainIndexing && (forceRefresh || !merkleTree) && !hasCachedTree) {
6819
6951
  try {
6820
6952
  const shouldRequestRelaySync = forceSync || requiredRelayIndex >= 0;
6821
6953
  const syncTimeoutMs = shouldRequestRelaySync ? Math.min(3e4, relayRequestTimeoutMs()) : 3500;
@@ -6840,7 +6972,7 @@ async function transact(params, options) {
6840
6972
  merkleTree = null;
6841
6973
  }
6842
6974
  }
6843
- if (merkleTree && treeState) {
6975
+ if (merkleTree && treeState && !hasCachedTree) {
6844
6976
  const relayRoot = merkleTree.root().toString(16).padStart(64, "0");
6845
6977
  const lengthDiff = treeState.nextIndex - merkleTree.length;
6846
6978
  if (merkleTree.length === 0) {
@@ -6971,7 +7103,7 @@ async function transact(params, options) {
6971
7103
  const disableNearTipFallbackEnv = typeof process !== "undefined" && !!process.env && (process.env.CLOAK_DISABLE_NEAR_TIP_ONCHAIN_PROOF === "1" || process.env.CLOAK_DISABLE_NEAR_TIP_ONCHAIN_PROOF === "true");
6972
7104
  const hasSiblingHints = !!utxo.siblingCommitment || !!utxo.leftSiblingCommitment;
6973
7105
  const baseNearTipEligible = chainDistanceFromTip !== null && chainDistanceFromTip >= 0 && chainDistanceFromTip <= nearTipMaxDistance;
6974
- const allowNearTipOnChainFallback = relayTreeDisabledForMint && !disableNearTipFallbackEnv && baseNearTipEligible && (forceEnableNearTipFallbackEnv || useSiblingInfo && hasSiblingHints);
7106
+ let allowNearTipOnChainFallback = relayTreeDisabledForMint && !disableNearTipFallbackEnv && baseNearTipEligible && (forceEnableNearTipFallbackEnv || useSiblingInfo && hasSiblingHints);
6975
7107
  if (!relayTreeDisabledForMint && !useSiblingInfo) {
6976
7108
  onProgress?.(`Relay tree missing index ${utxo.index} (length: ${treeLength}) with sibling cache disabled; proof may require retry after relay sync.`);
6977
7109
  } else if (!relayTreeDisabledForMint && relayUrl && !forceChainIndexing && utxo.index >= treeLength && !allowNearTipOnChainFallback) {
@@ -6982,9 +7114,10 @@ async function transact(params, options) {
6982
7114
  onProgress?.(`Relay tree is lagging (missing index ${utxo.index}, length: ${treeLength}); using guarded on-chain proof fallback.`);
6983
7115
  }
6984
7116
  if (relayUrl && !relayTreeDisabledForMint && !forceChainIndexing && utxo.index >= treeLength && !allowNearTipOnChainFallback) {
6985
- throw new Error(
6986
- `Relay tree missing required commitment index ${utxo.index} (tree length: ${treeLength}); waiting for relay sync is required.`
7117
+ onProgress?.(
7118
+ `Relay tree missing index ${utxo.index} (tree length: ${treeLength}) \u2014 falling through to on-chain proof.`
6987
7119
  );
7120
+ allowNearTipOnChainFallback = true;
6988
7121
  }
6989
7122
  if (allowNearTipOnChainFallback && relayTreeDisabledForMint) {
6990
7123
  onProgress?.(
@@ -7218,11 +7351,19 @@ async function transact(params, options) {
7218
7351
  onProgress?.(`Merkle proof error (stale sibling data), will regenerate with fresh tree...`);
7219
7352
  lastError = new Error(`Circuit assertion failed: ${errorMsg}`);
7220
7353
  if (forceChainIndexing) {
7221
- onProgress?.("On-chain fallback proof failed; returning to relay-sync mode.");
7222
- forceChainIndexing = false;
7354
+ onProgress?.("On-chain frontier proof failed; escalating to full chain tree rebuild...");
7355
+ try {
7356
+ merkleTree = await buildMerkleTreeFromChain(connection, programId, pdas.merkleTree, onProgress);
7357
+ onProgress?.(`Chain tree rebuilt with ${merkleTree.length} leaves.`);
7358
+ } catch (chainBuildErr) {
7359
+ onProgress?.(`Chain tree rebuild failed (${chainBuildErr?.message?.slice(0, 80)}); returning to relay-sync mode.`);
7360
+ forceChainIndexing = false;
7361
+ merkleTree = null;
7362
+ }
7363
+ } else {
7364
+ merkleTree = null;
7223
7365
  }
7224
7366
  treeState = null;
7225
- merkleTree = null;
7226
7367
  useSiblingInfo = false;
7227
7368
  const waitMs = Math.min(retryDelayMs * Math.pow(2, Math.min(submissionAttempts, 4)), 5e3);
7228
7369
  if (waitMs > 0) {
@@ -7563,25 +7704,68 @@ async function transact(params, options) {
7563
7704
  }
7564
7705
  }
7565
7706
  if (lastError) {
7707
+ const classified = classifyRelayError(lastError.message);
7708
+ const isMerkleClass = !(classified instanceof UtxoAlreadySpentError) && !(classified instanceof SanctionsQuoteError) && !isRootNotFoundError2(lastError);
7709
+ if (!IS_BROWSER2 && isMerkleClass && relayUrl) {
7710
+ onProgress?.(
7711
+ "All relay-tree attempts failed. Rebuilding merkle tree from chain as last resort..."
7712
+ );
7713
+ try {
7714
+ const [merkleTreePda] = import_web37.PublicKey.findProgramAddressSync(
7715
+ [Buffer.from("merkle_tree"), mint.toBuffer()],
7716
+ programId
7717
+ );
7718
+ const chainTree = await buildMerkleTreeFromChain(
7719
+ connection,
7720
+ programId,
7721
+ merkleTreePda,
7722
+ onProgress
7723
+ );
7724
+ const chainRootHex = chainTree.root().toString(16).padStart(64, "0");
7725
+ onProgress?.(
7726
+ `Chain-rebuilt tree root: ${chainRootHex.substring(0, 16)}... \u2014 attached to error for caller retry`
7727
+ );
7728
+ throw new RelayInternalError(
7729
+ `All relay-tree attempts failed. Chain-rebuilt tree available (root ${chainRootHex.substring(0, 16)}...). Retry this call with options.cachedMerkleTree = err.cachedTreeFromChain. Original error: ${lastError.message}`,
7730
+ lastError.message,
7731
+ void 0,
7732
+ chainTree
7733
+ );
7734
+ } catch (e) {
7735
+ if (e instanceof RelayInternalError) throw e;
7736
+ onProgress?.(
7737
+ `Chain-replay fallback failed: ${e instanceof Error ? e.message : String(e)}`
7738
+ );
7739
+ }
7740
+ }
7566
7741
  if (isRootNotFoundError2(lastError)) {
7567
- throw new Error(`Transaction failed after ${maxRootRetries + 1} retries: ${lastError.message}`);
7742
+ throw new Error(
7743
+ `Transaction failed after ${maxRootRetries + 1} retries: ${lastError.message}`
7744
+ );
7568
7745
  }
7569
- throw new Error(`Transaction failed: ${lastError.message}`);
7746
+ throw classified;
7570
7747
  }
7571
7748
  if (relayUrl && outputCommitments.length >= 2) {
7572
- const reconcileSeedIndices = commitmentIndices[0] >= 0 && commitmentIndices[1] >= 0 ? commitmentIndices : lastObservedNextIndexForOutputs !== null ? [lastObservedNextIndexForOutputs, lastObservedNextIndexForOutputs + 1] : commitmentIndices;
7573
- const reconciledIndices = await reconcileCommitmentIndicesWithRelay(
7574
- relayUrl,
7575
- mint,
7576
- reconcileSeedIndices,
7577
- [outputCommitments[0], outputCommitments[1]],
7578
- onProgress
7579
- );
7580
- if (reconciledIndices[0] !== commitmentIndices[0] || reconciledIndices[1] !== commitmentIndices[1]) {
7749
+ const hasOnChainIndices = commitmentIndices[0] >= 0 && commitmentIndices[1] >= 0;
7750
+ if (!hasOnChainIndices) {
7751
+ const reconcileSeedIndices = lastObservedNextIndexForOutputs !== null ? [lastObservedNextIndexForOutputs, lastObservedNextIndexForOutputs + 1] : commitmentIndices;
7752
+ const reconciledIndices = await reconcileCommitmentIndicesWithRelay(
7753
+ relayUrl,
7754
+ mint,
7755
+ reconcileSeedIndices,
7756
+ [outputCommitments[0], outputCommitments[1]],
7757
+ onProgress
7758
+ );
7759
+ if (reconciledIndices[0] !== commitmentIndices[0] || reconciledIndices[1] !== commitmentIndices[1]) {
7760
+ onProgress?.(
7761
+ `Adjusted commitment indices after relay reconciliation: [${commitmentIndices[0]}, ${commitmentIndices[1]}] -> [${reconciledIndices[0]}, ${reconciledIndices[1]}]`
7762
+ );
7763
+ commitmentIndices = reconciledIndices;
7764
+ }
7765
+ } else {
7581
7766
  onProgress?.(
7582
- `Adjusted commitment indices after relay reconciliation: [${commitmentIndices[0]}, ${commitmentIndices[1]}] -> [${reconciledIndices[0]}, ${reconciledIndices[1]}]`
7767
+ `Using on-chain commitment indices [${commitmentIndices[0]}, ${commitmentIndices[1]}] (skipping relay reconciliation).`
7583
7768
  );
7584
- commitmentIndices = reconciledIndices;
7585
7769
  }
7586
7770
  }
7587
7771
  const siblingCommitments = [
@@ -7863,6 +8047,7 @@ async function swapUtxo(params, options) {
7863
8047
  if (inputUtxos.length > 2) {
7864
8048
  throw new Error("Maximum 2 input UTXOs allowed");
7865
8049
  }
8050
+ await preflightNullifiers(inputUtxos, connection, programId);
7866
8051
  const outputMintAccount = await connection.getAccountInfo(outputMint, {
7867
8052
  commitment: "confirmed"
7868
8053
  });
@@ -7934,7 +8119,7 @@ async function swapUtxo(params, options) {
7934
8119
  }
7935
8120
  merkleTree = null;
7936
8121
  }
7937
- if (needsTreeForProof && relayUrl && !relayTreeDisabledForMint && !forceChainIndexing && (forceRefresh || !merkleTree)) {
8122
+ if (needsTreeForProof && relayUrl && !relayTreeDisabledForMint && !forceChainIndexing && (forceRefresh || !merkleTree) && !hasCachedTree) {
7938
8123
  try {
7939
8124
  const shouldRequestRelaySync = forceSync || requiredRelayIndex >= 0;
7940
8125
  const syncTimeoutMs = shouldRequestRelaySync ? Math.min(3e4, relayRequestTimeoutMs()) : 3500;
@@ -7959,7 +8144,7 @@ async function swapUtxo(params, options) {
7959
8144
  merkleTree = null;
7960
8145
  }
7961
8146
  }
7962
- if (merkleTree && merkleState) {
8147
+ if (merkleTree && merkleState && !hasCachedTree) {
7963
8148
  const relayRoot = merkleTree.root().toString(16).padStart(64, "0");
7964
8149
  const lengthDiff = merkleState.nextIndex - merkleTree.length;
7965
8150
  if (merkleTree.length === 0) {
@@ -8087,7 +8272,7 @@ async function swapUtxo(params, options) {
8087
8272
  const disableNearTipFallbackEnv = typeof process !== "undefined" && !!process.env && (process.env.CLOAK_DISABLE_NEAR_TIP_ONCHAIN_PROOF === "1" || process.env.CLOAK_DISABLE_NEAR_TIP_ONCHAIN_PROOF === "true");
8088
8273
  const hasSiblingHints = !!utxo.siblingCommitment || !!utxo.leftSiblingCommitment;
8089
8274
  const baseNearTipEligible = chainDistanceFromTip !== null && chainDistanceFromTip >= 0 && chainDistanceFromTip <= nearTipMaxDistance;
8090
- const allowNearTipOnChainFallback = relayTreeDisabledForMint && !disableNearTipFallbackEnv && baseNearTipEligible && (forceEnableNearTipFallbackEnv || useSiblingInfo && hasSiblingHints);
8275
+ let allowNearTipOnChainFallback = relayTreeDisabledForMint && !disableNearTipFallbackEnv && baseNearTipEligible && (forceEnableNearTipFallbackEnv || useSiblingInfo && hasSiblingHints);
8091
8276
  if (!relayTreeDisabledForMint && !useSiblingInfo) {
8092
8277
  onProgress?.(`Relay tree missing index ${utxo.index} (length: ${treeLength}) with sibling cache disabled; proof may require retry after relay sync.`);
8093
8278
  } else if (!relayTreeDisabledForMint && relayUrl && !forceChainIndexing && utxo.index >= treeLength && !allowNearTipOnChainFallback) {
@@ -8098,9 +8283,10 @@ async function swapUtxo(params, options) {
8098
8283
  onProgress?.(`Relay tree is lagging (missing index ${utxo.index}, length: ${treeLength}); using guarded on-chain proof fallback.`);
8099
8284
  }
8100
8285
  if (relayUrl && !relayTreeDisabledForMint && !forceChainIndexing && utxo.index >= treeLength && !allowNearTipOnChainFallback) {
8101
- throw new Error(
8102
- `Relay tree missing required commitment index ${utxo.index} (tree length: ${treeLength}); waiting for relay sync is required.`
8286
+ onProgress?.(
8287
+ `Relay tree missing index ${utxo.index} (tree length: ${treeLength}) \u2014 falling through to on-chain proof.`
8103
8288
  );
8289
+ allowNearTipOnChainFallback = true;
8104
8290
  }
8105
8291
  if (allowNearTipOnChainFallback && relayTreeDisabledForMint) {
8106
8292
  onProgress?.(
@@ -8304,10 +8490,18 @@ async function swapUtxo(params, options) {
8304
8490
  onProgress?.(`Merkle proof error (stale sibling data), will regenerate with fresh tree...`);
8305
8491
  lastError = new Error(`Circuit assertion failed: ${errorMsg}`);
8306
8492
  if (forceChainIndexing) {
8307
- onProgress?.("On-chain fallback proof failed; returning to relay-sync mode.");
8308
- forceChainIndexing = false;
8493
+ onProgress?.("On-chain frontier proof failed; escalating to full chain tree rebuild...");
8494
+ try {
8495
+ merkleTree = await buildMerkleTreeFromChain(connection, programId, pdas.merkleTree, onProgress);
8496
+ onProgress?.(`Chain tree rebuilt with ${merkleTree.length} leaves.`);
8497
+ } catch (chainBuildErr) {
8498
+ onProgress?.(`Chain tree rebuild failed (${chainBuildErr?.message?.slice(0, 80)}); returning to relay-sync mode.`);
8499
+ forceChainIndexing = false;
8500
+ merkleTree = null;
8501
+ }
8502
+ } else {
8503
+ merkleTree = null;
8309
8504
  }
8310
- merkleTree = null;
8311
8505
  merkleState = null;
8312
8506
  useSiblingInfo = false;
8313
8507
  const waitMs = Math.min(retryDelayMs * Math.pow(2, Math.min(attempt, 4)), 5e3);
@@ -8502,19 +8696,26 @@ async function swapUtxo(params, options) {
8502
8696
  throw new Error(`Swap transaction failed: ${lastError.message}`);
8503
8697
  }
8504
8698
  if (relayUrl && outputCommitments.length >= 2) {
8505
- const reconcileSeedIndices = commitmentIndices[0] >= 0 && commitmentIndices[1] >= 0 ? commitmentIndices : lastObservedNextIndexForOutputs !== null ? [lastObservedNextIndexForOutputs, lastObservedNextIndexForOutputs + 1] : commitmentIndices;
8506
- const reconciledIndices = await reconcileCommitmentIndicesWithRelay(
8507
- relayUrl,
8508
- swapPoolMint,
8509
- reconcileSeedIndices,
8510
- [outputCommitments[0], outputCommitments[1]],
8511
- onProgress
8512
- );
8513
- if (reconciledIndices[0] !== commitmentIndices[0] || reconciledIndices[1] !== commitmentIndices[1]) {
8699
+ const hasOnChainIndices = commitmentIndices[0] >= 0 && commitmentIndices[1] >= 0;
8700
+ if (!hasOnChainIndices) {
8701
+ const reconcileSeedIndices = lastObservedNextIndexForOutputs !== null ? [lastObservedNextIndexForOutputs, lastObservedNextIndexForOutputs + 1] : commitmentIndices;
8702
+ const reconciledIndices = await reconcileCommitmentIndicesWithRelay(
8703
+ relayUrl,
8704
+ swapPoolMint,
8705
+ reconcileSeedIndices,
8706
+ [outputCommitments[0], outputCommitments[1]],
8707
+ onProgress
8708
+ );
8709
+ if (reconciledIndices[0] !== commitmentIndices[0] || reconciledIndices[1] !== commitmentIndices[1]) {
8710
+ onProgress?.(
8711
+ `Adjusted swap commitment indices after relay reconciliation: [${commitmentIndices[0]}, ${commitmentIndices[1]}] -> [${reconciledIndices[0]}, ${reconciledIndices[1]}]`
8712
+ );
8713
+ commitmentIndices = reconciledIndices;
8714
+ }
8715
+ } else {
8514
8716
  onProgress?.(
8515
- `Adjusted swap commitment indices after relay reconciliation: [${commitmentIndices[0]}, ${commitmentIndices[1]}] -> [${reconciledIndices[0]}, ${reconciledIndices[1]}]`
8717
+ `Using on-chain swap commitment indices [${commitmentIndices[0]}, ${commitmentIndices[1]}] (skipping relay reconciliation).`
8516
8718
  );
8517
- commitmentIndices = reconciledIndices;
8518
8719
  }
8519
8720
  }
8520
8721
  const siblingCommitments = [
@@ -8580,6 +8781,7 @@ async function swapWithChange(inputUtxos, swapAmount, outputMint, recipientAta,
8580
8781
  var import_bs582 = __toESM(require("bs58"), 1);
8581
8782
  var import_web38 = require("@solana/web3.js");
8582
8783
  var import_spl_token2 = require("@solana/spl-token");
8784
+ var bs582 = import_bs582.default.default || import_bs582.default;
8583
8785
  var TRANSACT_TAG2 = 0;
8584
8786
  var TRANSACT_SWAP_TAG2 = 1;
8585
8787
  var PROOF_LEN2 = 256;
@@ -8763,7 +8965,7 @@ function normalizeIxData(rawData) {
8763
8965
  if (rawData instanceof Uint8Array) return rawData;
8764
8966
  if (typeof rawData === "string") {
8765
8967
  try {
8766
- const decoded = import_bs582.default.decode(rawData);
8968
+ const decoded = bs582.decode(rawData);
8767
8969
  return new Uint8Array(decoded);
8768
8970
  } catch {
8769
8971
  if (typeof Buffer !== "undefined") {
@@ -9669,7 +9871,7 @@ var SimpleWallet = class {
9669
9871
  };
9670
9872
 
9671
9873
  // src/index.ts
9672
- var VERSION = "1.0.0";
9874
+ var VERSION = "0.1.5";
9673
9875
  var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
9674
9876
  // Annotate the CommonJS export names for ESM import in node:
9675
9877
  0 && (module.exports = {
@@ -9687,12 +9889,15 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
9687
9889
  MemoryStorageAdapter,
9688
9890
  MerkleTree,
9689
9891
  NATIVE_SOL_MINT,
9892
+ RelayInternalError,
9690
9893
  RelayService,
9691
9894
  RootNotFoundError,
9692
9895
  SCANNER_SUPPORTS_TRANSACT_SWAP,
9693
9896
  SIGN_IN_MESSAGE,
9897
+ SanctionsQuoteError,
9694
9898
  ShieldPoolErrors,
9695
9899
  SimpleWallet,
9900
+ UtxoAlreadySpentError,
9696
9901
  UtxoWallet,
9697
9902
  VARIABLE_FEE_DENOMINATOR,
9698
9903
  VARIABLE_FEE_NUMERATOR,
@@ -9711,6 +9916,7 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
9711
9916
  calculateRelayFee,
9712
9917
  chainNoteFromBase64,
9713
9918
  chainNoteToBase64,
9919
+ classifyRelayError,
9714
9920
  cleanupStalePendingOperations,
9715
9921
  clearPendingDeposits,
9716
9922
  clearPendingWithdrawals,
@@ -9823,6 +10029,7 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
9823
10029
  partialWithdraw,
9824
10030
  poseidonHash,
9825
10031
  preflightCheck,
10032
+ preflightNullifiers,
9826
10033
  prepareEncryptedOutput,
9827
10034
  prepareEncryptedOutputForRecipient,
9828
10035
  proofToBytes,
@@ -9870,6 +10077,7 @@ var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
9870
10077
  validateWithdrawableNote,
9871
10078
  verifyAllCircuits,
9872
10079
  verifyCircuitIntegrity,
10080
+ verifyUtxos,
9873
10081
  waitForRoot,
9874
10082
  withTiming
9875
10083
  });