@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.js CHANGED
@@ -877,6 +877,71 @@ function parseRelayErrorResponse(responseText) {
877
877
  return { isRootNotFound: false };
878
878
  }
879
879
  }
880
+ var UtxoAlreadySpentError = class extends Error {
881
+ constructor(message, spentUtxoCommitments = []) {
882
+ super(message);
883
+ this.spentUtxoCommitments = spentUtxoCommitments;
884
+ this.name = "UtxoAlreadySpentError";
885
+ }
886
+ };
887
+ var SanctionsQuoteError = class extends Error {
888
+ constructor(message, onChainCode, subKind) {
889
+ super(message);
890
+ this.onChainCode = onChainCode;
891
+ this.subKind = subKind;
892
+ this.name = "SanctionsQuoteError";
893
+ }
894
+ };
895
+ var RelayInternalError = class extends Error {
896
+ constructor(message, relayMessage, httpStatus, cachedTreeFromChain) {
897
+ super(message);
898
+ this.relayMessage = relayMessage;
899
+ this.httpStatus = httpStatus;
900
+ this.cachedTreeFromChain = cachedTreeFromChain;
901
+ this.name = "RelayInternalError";
902
+ }
903
+ };
904
+ function classifyRelayError(responseText, httpStatus) {
905
+ let inner = "";
906
+ try {
907
+ const parsed = JSON.parse(responseText);
908
+ inner = parsed?.message ?? "";
909
+ } catch {
910
+ inner = responseText;
911
+ }
912
+ const haystack = inner.toLowerCase();
913
+ if (haystack.includes("0x1020") || haystack.includes("doublespend")) {
914
+ return new UtxoAlreadySpentError(
915
+ "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."
916
+ );
917
+ }
918
+ if (haystack.includes("0x10b0") || haystack.includes("rangequoteexpired")) {
919
+ return new SanctionsQuoteError(
920
+ "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.",
921
+ 4272,
922
+ "expired"
923
+ );
924
+ }
925
+ if (haystack.includes("0x10b2") || haystack.includes("rangequotewalletmismatch")) {
926
+ return new SanctionsQuoteError(
927
+ "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.",
928
+ 4274,
929
+ "wallet_mismatch"
930
+ );
931
+ }
932
+ if (haystack.includes("0x10b3") || haystack.includes("rangequotemissinged25519")) {
933
+ return new SanctionsQuoteError(
934
+ "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.",
935
+ 4275,
936
+ "missing_ix"
937
+ );
938
+ }
939
+ return new RelayInternalError(
940
+ `Relay returned ${httpStatus ?? "an error"}: ${inner || responseText}`,
941
+ inner || responseText,
942
+ httpStatus
943
+ );
944
+ }
880
945
  var ShieldPoolErrors = {
881
946
  // Root management errors
882
947
  4096: "Invalid Merkle root",
@@ -4173,6 +4238,63 @@ async function decryptComplianceMetadataWithMasterKey(encrypted, masterComplianc
4173
4238
  return decryptWithSharedSecret(payload, sharedSecret);
4174
4239
  }
4175
4240
 
4241
+ // src/utils/verify-utxos.ts
4242
+ async function verifyUtxos(utxos, connection, programId, commitment = "confirmed") {
4243
+ const checkable = [];
4244
+ const skipped = [];
4245
+ for (const utxo of utxos) {
4246
+ if (utxo.commitment === void 0 || utxo.mintAddress === void 0 || utxo.keypair === void 0 || utxo.keypair.privateKey === void 0) {
4247
+ skipped.push(utxo);
4248
+ continue;
4249
+ }
4250
+ try {
4251
+ const nullifierBig = await computeNullifier(utxo);
4252
+ const nullifierHex = nullifierBig.toString(16).padStart(64, "0");
4253
+ const nullifierBytes = Buffer.from(nullifierHex, "hex");
4254
+ const { pool } = getShieldPoolPDAs(programId, utxo.mintAddress);
4255
+ const [pda] = getNullifierPDA(pool, nullifierBytes, programId);
4256
+ checkable.push({ utxo, pda });
4257
+ } catch {
4258
+ skipped.push(utxo);
4259
+ }
4260
+ }
4261
+ if (checkable.length === 0) {
4262
+ return { spent: [], unspent: [], skipped };
4263
+ }
4264
+ const CHUNK = 100;
4265
+ const infos = [];
4266
+ for (let i = 0; i < checkable.length; i += CHUNK) {
4267
+ const page = checkable.slice(i, i + CHUNK).map((e) => e.pda);
4268
+ const pageInfos = await connection.getMultipleAccountsInfo(page, commitment);
4269
+ infos.push(...pageInfos);
4270
+ }
4271
+ const spent = [];
4272
+ const unspent = [];
4273
+ for (let i = 0; i < checkable.length; i++) {
4274
+ if (infos[i] !== null) {
4275
+ spent.push(checkable[i].utxo);
4276
+ } else {
4277
+ unspent.push(checkable[i].utxo);
4278
+ }
4279
+ }
4280
+ return { spent, unspent, skipped };
4281
+ }
4282
+ async function preflightNullifiers(utxos, connection, programId, commitment = "confirmed") {
4283
+ if (utxos.length === 0) return;
4284
+ const result = await verifyUtxos(utxos, connection, programId, commitment);
4285
+ if (result.spent.length > 0) {
4286
+ const allCommitments = result.spent.map(
4287
+ (u) => u.commitment !== void 0 ? u.commitment.toString(16).padStart(64, "0") : "<unknown>"
4288
+ );
4289
+ const preview = allCommitments.slice(0, 5);
4290
+ const extra = allCommitments.length > preview.length ? ` (+${allCommitments.length - preview.length} more)` : "";
4291
+ throw new UtxoAlreadySpentError(
4292
+ `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.`,
4293
+ allCommitments
4294
+ );
4295
+ }
4296
+ }
4297
+
4176
4298
  // src/services/risk-oracle.ts
4177
4299
  function buildRangeRiskScoreJob(OracleJob, walletAddress) {
4178
4300
  return OracleJob.fromObject({
@@ -4517,13 +4639,19 @@ async function buildMerkleTree(commitments, height = MERKLE_TREE_HEIGHT2) {
4517
4639
  }
4518
4640
 
4519
4641
  // src/utils/relay-client.ts
4520
- import bs58 from "bs58";
4642
+ import _bs58 from "bs58";
4643
+ var bs58 = _bs58.default || _bs58;
4521
4644
  var TRANSACT_TAG = 0;
4522
4645
  var TRANSACT_SWAP_TAG = 1;
4523
- var DEPOSIT_TAG = 1;
4524
4646
  var PROOF_LEN = 256;
4525
4647
  var PUBLIC_INPUTS_LEN = 264;
4526
4648
  var COMMITMENTS_OFFSET = 168;
4649
+ function isAllZero(b) {
4650
+ for (let i = 0; i < b.length; i++) {
4651
+ if (b[i] !== 0) return false;
4652
+ }
4653
+ return true;
4654
+ }
4527
4655
  var DEFAULT_TIMEOUT_MS = 3e4;
4528
4656
  var DEFAULT_MAX_RETRIES = 3;
4529
4657
  async function fetchWithRetry(url, options = {}) {
@@ -4661,24 +4789,20 @@ async function buildMerkleTreeFromRelay(relayUrl, options) {
4661
4789
  function extractCommitmentsFromInstruction(data) {
4662
4790
  if (data.length === 0) return [];
4663
4791
  const tag = data[0];
4664
- if (tag === TRANSACT_TAG || tag === TRANSACT_SWAP_TAG) {
4665
- const publicInputsStart = 1 + PROOF_LEN;
4666
- const publicInputsEnd = publicInputsStart + PUBLIC_INPUTS_LEN;
4667
- if (data.length < publicInputsEnd) return [];
4668
- const publicInputs = data.slice(publicInputsStart, publicInputsEnd);
4669
- const commitments = [];
4670
- const c0 = publicInputs.slice(COMMITMENTS_OFFSET, COMMITMENTS_OFFSET + 32);
4671
- const c1 = publicInputs.slice(COMMITMENTS_OFFSET + 32, COMMITMENTS_OFFSET + 64);
4672
- commitments.push(c0);
4673
- commitments.push(c1);
4674
- return commitments;
4675
- }
4676
- if (tag === DEPOSIT_TAG) {
4677
- if (data.length < 1 + 8 + 32) return [];
4678
- const commitment = data.slice(1 + 8, 1 + 8 + 32);
4679
- return [commitment];
4680
- }
4681
- return [];
4792
+ if (tag !== TRANSACT_TAG && tag !== TRANSACT_SWAP_TAG) return [];
4793
+ const publicInputsStart = 1 + PROOF_LEN;
4794
+ const publicInputsEnd = publicInputsStart + PUBLIC_INPUTS_LEN;
4795
+ if (data.length < publicInputsEnd) return [];
4796
+ const publicInputs = data.slice(publicInputsStart, publicInputsEnd);
4797
+ const c0 = publicInputs.slice(COMMITMENTS_OFFSET, COMMITMENTS_OFFSET + 32);
4798
+ const c1 = publicInputs.slice(
4799
+ COMMITMENTS_OFFSET + 32,
4800
+ COMMITMENTS_OFFSET + 64
4801
+ );
4802
+ const out = [];
4803
+ if (!isAllZero(c0)) out.push(c0);
4804
+ if (!isAllZero(c1)) out.push(c1);
4805
+ return out;
4682
4806
  }
4683
4807
  function isBrowser() {
4684
4808
  return typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
@@ -6298,6 +6422,7 @@ async function transact(params, options) {
6298
6422
  if (outputUtxos.length > 2) {
6299
6423
  throw new Error("Maximum 2 output UTXOs allowed");
6300
6424
  }
6425
+ await preflightNullifiers(inputUtxos, connection, programId);
6301
6426
  const mint = inputUtxos.find((u) => u.amount > 0)?.mintAddress ?? outputUtxos.find((u) => u.amount > 0)?.mintAddress ?? NATIVE_SOL_MINT;
6302
6427
  const paddedInputs = [...inputUtxos];
6303
6428
  while (paddedInputs.length < 2) {
@@ -6368,7 +6493,7 @@ async function transact(params, options) {
6368
6493
  }
6369
6494
  merkleTree = null;
6370
6495
  }
6371
- if (needsTreeForProof && relayUrl && !relayTreeDisabledForMint && !forceChainIndexing && (forceRefresh || !merkleTree)) {
6496
+ if (needsTreeForProof && relayUrl && !relayTreeDisabledForMint && !forceChainIndexing && (forceRefresh || !merkleTree) && !hasCachedTree) {
6372
6497
  try {
6373
6498
  const shouldRequestRelaySync = forceSync || requiredRelayIndex >= 0;
6374
6499
  const syncTimeoutMs = shouldRequestRelaySync ? Math.min(3e4, relayRequestTimeoutMs()) : 3500;
@@ -6393,7 +6518,7 @@ async function transact(params, options) {
6393
6518
  merkleTree = null;
6394
6519
  }
6395
6520
  }
6396
- if (merkleTree && treeState) {
6521
+ if (merkleTree && treeState && !hasCachedTree) {
6397
6522
  const relayRoot = merkleTree.root().toString(16).padStart(64, "0");
6398
6523
  const lengthDiff = treeState.nextIndex - merkleTree.length;
6399
6524
  if (merkleTree.length === 0) {
@@ -6524,7 +6649,7 @@ async function transact(params, options) {
6524
6649
  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");
6525
6650
  const hasSiblingHints = !!utxo.siblingCommitment || !!utxo.leftSiblingCommitment;
6526
6651
  const baseNearTipEligible = chainDistanceFromTip !== null && chainDistanceFromTip >= 0 && chainDistanceFromTip <= nearTipMaxDistance;
6527
- const allowNearTipOnChainFallback = relayTreeDisabledForMint && !disableNearTipFallbackEnv && baseNearTipEligible && (forceEnableNearTipFallbackEnv || useSiblingInfo && hasSiblingHints);
6652
+ let allowNearTipOnChainFallback = relayTreeDisabledForMint && !disableNearTipFallbackEnv && baseNearTipEligible && (forceEnableNearTipFallbackEnv || useSiblingInfo && hasSiblingHints);
6528
6653
  if (!relayTreeDisabledForMint && !useSiblingInfo) {
6529
6654
  onProgress?.(`Relay tree missing index ${utxo.index} (length: ${treeLength}) with sibling cache disabled; proof may require retry after relay sync.`);
6530
6655
  } else if (!relayTreeDisabledForMint && relayUrl && !forceChainIndexing && utxo.index >= treeLength && !allowNearTipOnChainFallback) {
@@ -6535,9 +6660,10 @@ async function transact(params, options) {
6535
6660
  onProgress?.(`Relay tree is lagging (missing index ${utxo.index}, length: ${treeLength}); using guarded on-chain proof fallback.`);
6536
6661
  }
6537
6662
  if (relayUrl && !relayTreeDisabledForMint && !forceChainIndexing && utxo.index >= treeLength && !allowNearTipOnChainFallback) {
6538
- throw new Error(
6539
- `Relay tree missing required commitment index ${utxo.index} (tree length: ${treeLength}); waiting for relay sync is required.`
6663
+ onProgress?.(
6664
+ `Relay tree missing index ${utxo.index} (tree length: ${treeLength}) \u2014 falling through to on-chain proof.`
6540
6665
  );
6666
+ allowNearTipOnChainFallback = true;
6541
6667
  }
6542
6668
  if (allowNearTipOnChainFallback && relayTreeDisabledForMint) {
6543
6669
  onProgress?.(
@@ -6771,11 +6897,19 @@ async function transact(params, options) {
6771
6897
  onProgress?.(`Merkle proof error (stale sibling data), will regenerate with fresh tree...`);
6772
6898
  lastError = new Error(`Circuit assertion failed: ${errorMsg}`);
6773
6899
  if (forceChainIndexing) {
6774
- onProgress?.("On-chain fallback proof failed; returning to relay-sync mode.");
6775
- forceChainIndexing = false;
6900
+ onProgress?.("On-chain frontier proof failed; escalating to full chain tree rebuild...");
6901
+ try {
6902
+ merkleTree = await buildMerkleTreeFromChain(connection, programId, pdas.merkleTree, onProgress);
6903
+ onProgress?.(`Chain tree rebuilt with ${merkleTree.length} leaves.`);
6904
+ } catch (chainBuildErr) {
6905
+ onProgress?.(`Chain tree rebuild failed (${chainBuildErr?.message?.slice(0, 80)}); returning to relay-sync mode.`);
6906
+ forceChainIndexing = false;
6907
+ merkleTree = null;
6908
+ }
6909
+ } else {
6910
+ merkleTree = null;
6776
6911
  }
6777
6912
  treeState = null;
6778
- merkleTree = null;
6779
6913
  useSiblingInfo = false;
6780
6914
  const waitMs = Math.min(retryDelayMs * Math.pow(2, Math.min(submissionAttempts, 4)), 5e3);
6781
6915
  if (waitMs > 0) {
@@ -7116,25 +7250,68 @@ async function transact(params, options) {
7116
7250
  }
7117
7251
  }
7118
7252
  if (lastError) {
7253
+ const classified = classifyRelayError(lastError.message);
7254
+ const isMerkleClass = !(classified instanceof UtxoAlreadySpentError) && !(classified instanceof SanctionsQuoteError) && !isRootNotFoundError2(lastError);
7255
+ if (!IS_BROWSER2 && isMerkleClass && relayUrl) {
7256
+ onProgress?.(
7257
+ "All relay-tree attempts failed. Rebuilding merkle tree from chain as last resort..."
7258
+ );
7259
+ try {
7260
+ const [merkleTreePda] = PublicKey6.findProgramAddressSync(
7261
+ [Buffer.from("merkle_tree"), mint.toBuffer()],
7262
+ programId
7263
+ );
7264
+ const chainTree = await buildMerkleTreeFromChain(
7265
+ connection,
7266
+ programId,
7267
+ merkleTreePda,
7268
+ onProgress
7269
+ );
7270
+ const chainRootHex = chainTree.root().toString(16).padStart(64, "0");
7271
+ onProgress?.(
7272
+ `Chain-rebuilt tree root: ${chainRootHex.substring(0, 16)}... \u2014 attached to error for caller retry`
7273
+ );
7274
+ throw new RelayInternalError(
7275
+ `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}`,
7276
+ lastError.message,
7277
+ void 0,
7278
+ chainTree
7279
+ );
7280
+ } catch (e) {
7281
+ if (e instanceof RelayInternalError) throw e;
7282
+ onProgress?.(
7283
+ `Chain-replay fallback failed: ${e instanceof Error ? e.message : String(e)}`
7284
+ );
7285
+ }
7286
+ }
7119
7287
  if (isRootNotFoundError2(lastError)) {
7120
- throw new Error(`Transaction failed after ${maxRootRetries + 1} retries: ${lastError.message}`);
7288
+ throw new Error(
7289
+ `Transaction failed after ${maxRootRetries + 1} retries: ${lastError.message}`
7290
+ );
7121
7291
  }
7122
- throw new Error(`Transaction failed: ${lastError.message}`);
7292
+ throw classified;
7123
7293
  }
7124
7294
  if (relayUrl && outputCommitments.length >= 2) {
7125
- const reconcileSeedIndices = commitmentIndices[0] >= 0 && commitmentIndices[1] >= 0 ? commitmentIndices : lastObservedNextIndexForOutputs !== null ? [lastObservedNextIndexForOutputs, lastObservedNextIndexForOutputs + 1] : commitmentIndices;
7126
- const reconciledIndices = await reconcileCommitmentIndicesWithRelay(
7127
- relayUrl,
7128
- mint,
7129
- reconcileSeedIndices,
7130
- [outputCommitments[0], outputCommitments[1]],
7131
- onProgress
7132
- );
7133
- if (reconciledIndices[0] !== commitmentIndices[0] || reconciledIndices[1] !== commitmentIndices[1]) {
7295
+ const hasOnChainIndices = commitmentIndices[0] >= 0 && commitmentIndices[1] >= 0;
7296
+ if (!hasOnChainIndices) {
7297
+ const reconcileSeedIndices = lastObservedNextIndexForOutputs !== null ? [lastObservedNextIndexForOutputs, lastObservedNextIndexForOutputs + 1] : commitmentIndices;
7298
+ const reconciledIndices = await reconcileCommitmentIndicesWithRelay(
7299
+ relayUrl,
7300
+ mint,
7301
+ reconcileSeedIndices,
7302
+ [outputCommitments[0], outputCommitments[1]],
7303
+ onProgress
7304
+ );
7305
+ if (reconciledIndices[0] !== commitmentIndices[0] || reconciledIndices[1] !== commitmentIndices[1]) {
7306
+ onProgress?.(
7307
+ `Adjusted commitment indices after relay reconciliation: [${commitmentIndices[0]}, ${commitmentIndices[1]}] -> [${reconciledIndices[0]}, ${reconciledIndices[1]}]`
7308
+ );
7309
+ commitmentIndices = reconciledIndices;
7310
+ }
7311
+ } else {
7134
7312
  onProgress?.(
7135
- `Adjusted commitment indices after relay reconciliation: [${commitmentIndices[0]}, ${commitmentIndices[1]}] -> [${reconciledIndices[0]}, ${reconciledIndices[1]}]`
7313
+ `Using on-chain commitment indices [${commitmentIndices[0]}, ${commitmentIndices[1]}] (skipping relay reconciliation).`
7136
7314
  );
7137
- commitmentIndices = reconciledIndices;
7138
7315
  }
7139
7316
  }
7140
7317
  const siblingCommitments = [
@@ -7416,6 +7593,7 @@ async function swapUtxo(params, options) {
7416
7593
  if (inputUtxos.length > 2) {
7417
7594
  throw new Error("Maximum 2 input UTXOs allowed");
7418
7595
  }
7596
+ await preflightNullifiers(inputUtxos, connection, programId);
7419
7597
  const outputMintAccount = await connection.getAccountInfo(outputMint, {
7420
7598
  commitment: "confirmed"
7421
7599
  });
@@ -7487,7 +7665,7 @@ async function swapUtxo(params, options) {
7487
7665
  }
7488
7666
  merkleTree = null;
7489
7667
  }
7490
- if (needsTreeForProof && relayUrl && !relayTreeDisabledForMint && !forceChainIndexing && (forceRefresh || !merkleTree)) {
7668
+ if (needsTreeForProof && relayUrl && !relayTreeDisabledForMint && !forceChainIndexing && (forceRefresh || !merkleTree) && !hasCachedTree) {
7491
7669
  try {
7492
7670
  const shouldRequestRelaySync = forceSync || requiredRelayIndex >= 0;
7493
7671
  const syncTimeoutMs = shouldRequestRelaySync ? Math.min(3e4, relayRequestTimeoutMs()) : 3500;
@@ -7512,7 +7690,7 @@ async function swapUtxo(params, options) {
7512
7690
  merkleTree = null;
7513
7691
  }
7514
7692
  }
7515
- if (merkleTree && merkleState) {
7693
+ if (merkleTree && merkleState && !hasCachedTree) {
7516
7694
  const relayRoot = merkleTree.root().toString(16).padStart(64, "0");
7517
7695
  const lengthDiff = merkleState.nextIndex - merkleTree.length;
7518
7696
  if (merkleTree.length === 0) {
@@ -7640,7 +7818,7 @@ async function swapUtxo(params, options) {
7640
7818
  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");
7641
7819
  const hasSiblingHints = !!utxo.siblingCommitment || !!utxo.leftSiblingCommitment;
7642
7820
  const baseNearTipEligible = chainDistanceFromTip !== null && chainDistanceFromTip >= 0 && chainDistanceFromTip <= nearTipMaxDistance;
7643
- const allowNearTipOnChainFallback = relayTreeDisabledForMint && !disableNearTipFallbackEnv && baseNearTipEligible && (forceEnableNearTipFallbackEnv || useSiblingInfo && hasSiblingHints);
7821
+ let allowNearTipOnChainFallback = relayTreeDisabledForMint && !disableNearTipFallbackEnv && baseNearTipEligible && (forceEnableNearTipFallbackEnv || useSiblingInfo && hasSiblingHints);
7644
7822
  if (!relayTreeDisabledForMint && !useSiblingInfo) {
7645
7823
  onProgress?.(`Relay tree missing index ${utxo.index} (length: ${treeLength}) with sibling cache disabled; proof may require retry after relay sync.`);
7646
7824
  } else if (!relayTreeDisabledForMint && relayUrl && !forceChainIndexing && utxo.index >= treeLength && !allowNearTipOnChainFallback) {
@@ -7651,9 +7829,10 @@ async function swapUtxo(params, options) {
7651
7829
  onProgress?.(`Relay tree is lagging (missing index ${utxo.index}, length: ${treeLength}); using guarded on-chain proof fallback.`);
7652
7830
  }
7653
7831
  if (relayUrl && !relayTreeDisabledForMint && !forceChainIndexing && utxo.index >= treeLength && !allowNearTipOnChainFallback) {
7654
- throw new Error(
7655
- `Relay tree missing required commitment index ${utxo.index} (tree length: ${treeLength}); waiting for relay sync is required.`
7832
+ onProgress?.(
7833
+ `Relay tree missing index ${utxo.index} (tree length: ${treeLength}) \u2014 falling through to on-chain proof.`
7656
7834
  );
7835
+ allowNearTipOnChainFallback = true;
7657
7836
  }
7658
7837
  if (allowNearTipOnChainFallback && relayTreeDisabledForMint) {
7659
7838
  onProgress?.(
@@ -7857,10 +8036,18 @@ async function swapUtxo(params, options) {
7857
8036
  onProgress?.(`Merkle proof error (stale sibling data), will regenerate with fresh tree...`);
7858
8037
  lastError = new Error(`Circuit assertion failed: ${errorMsg}`);
7859
8038
  if (forceChainIndexing) {
7860
- onProgress?.("On-chain fallback proof failed; returning to relay-sync mode.");
7861
- forceChainIndexing = false;
8039
+ onProgress?.("On-chain frontier proof failed; escalating to full chain tree rebuild...");
8040
+ try {
8041
+ merkleTree = await buildMerkleTreeFromChain(connection, programId, pdas.merkleTree, onProgress);
8042
+ onProgress?.(`Chain tree rebuilt with ${merkleTree.length} leaves.`);
8043
+ } catch (chainBuildErr) {
8044
+ onProgress?.(`Chain tree rebuild failed (${chainBuildErr?.message?.slice(0, 80)}); returning to relay-sync mode.`);
8045
+ forceChainIndexing = false;
8046
+ merkleTree = null;
8047
+ }
8048
+ } else {
8049
+ merkleTree = null;
7862
8050
  }
7863
- merkleTree = null;
7864
8051
  merkleState = null;
7865
8052
  useSiblingInfo = false;
7866
8053
  const waitMs = Math.min(retryDelayMs * Math.pow(2, Math.min(attempt, 4)), 5e3);
@@ -8055,19 +8242,26 @@ async function swapUtxo(params, options) {
8055
8242
  throw new Error(`Swap transaction failed: ${lastError.message}`);
8056
8243
  }
8057
8244
  if (relayUrl && outputCommitments.length >= 2) {
8058
- const reconcileSeedIndices = commitmentIndices[0] >= 0 && commitmentIndices[1] >= 0 ? commitmentIndices : lastObservedNextIndexForOutputs !== null ? [lastObservedNextIndexForOutputs, lastObservedNextIndexForOutputs + 1] : commitmentIndices;
8059
- const reconciledIndices = await reconcileCommitmentIndicesWithRelay(
8060
- relayUrl,
8061
- swapPoolMint,
8062
- reconcileSeedIndices,
8063
- [outputCommitments[0], outputCommitments[1]],
8064
- onProgress
8065
- );
8066
- if (reconciledIndices[0] !== commitmentIndices[0] || reconciledIndices[1] !== commitmentIndices[1]) {
8245
+ const hasOnChainIndices = commitmentIndices[0] >= 0 && commitmentIndices[1] >= 0;
8246
+ if (!hasOnChainIndices) {
8247
+ const reconcileSeedIndices = lastObservedNextIndexForOutputs !== null ? [lastObservedNextIndexForOutputs, lastObservedNextIndexForOutputs + 1] : commitmentIndices;
8248
+ const reconciledIndices = await reconcileCommitmentIndicesWithRelay(
8249
+ relayUrl,
8250
+ swapPoolMint,
8251
+ reconcileSeedIndices,
8252
+ [outputCommitments[0], outputCommitments[1]],
8253
+ onProgress
8254
+ );
8255
+ if (reconciledIndices[0] !== commitmentIndices[0] || reconciledIndices[1] !== commitmentIndices[1]) {
8256
+ onProgress?.(
8257
+ `Adjusted swap commitment indices after relay reconciliation: [${commitmentIndices[0]}, ${commitmentIndices[1]}] -> [${reconciledIndices[0]}, ${reconciledIndices[1]}]`
8258
+ );
8259
+ commitmentIndices = reconciledIndices;
8260
+ }
8261
+ } else {
8067
8262
  onProgress?.(
8068
- `Adjusted swap commitment indices after relay reconciliation: [${commitmentIndices[0]}, ${commitmentIndices[1]}] -> [${reconciledIndices[0]}, ${reconciledIndices[1]}]`
8263
+ `Using on-chain swap commitment indices [${commitmentIndices[0]}, ${commitmentIndices[1]}] (skipping relay reconciliation).`
8069
8264
  );
8070
- commitmentIndices = reconciledIndices;
8071
8265
  }
8072
8266
  }
8073
8267
  const siblingCommitments = [
@@ -8130,11 +8324,12 @@ async function swapWithChange(inputUtxos, swapAmount, outputMint, recipientAta,
8130
8324
  }
8131
8325
 
8132
8326
  // src/core/scanner.ts
8133
- import bs582 from "bs58";
8327
+ import _bs582 from "bs58";
8134
8328
  import {
8135
8329
  PublicKey as PublicKey7
8136
8330
  } from "@solana/web3.js";
8137
8331
  import { getAssociatedTokenAddressSync as getAssociatedTokenAddressSync2 } from "@solana/spl-token";
8332
+ var bs582 = _bs582.default || _bs582;
8138
8333
  var TRANSACT_TAG2 = 0;
8139
8334
  var TRANSACT_SWAP_TAG2 = 1;
8140
8335
  var PROOF_LEN2 = 256;
@@ -9223,7 +9418,7 @@ var SimpleWallet = class {
9223
9418
  };
9224
9419
 
9225
9420
  // src/index.ts
9226
- var VERSION = "1.0.0";
9421
+ var VERSION = "0.1.5";
9227
9422
  var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
9228
9423
  export {
9229
9424
  CLOAK_PROGRAM_ID,
@@ -9240,12 +9435,15 @@ export {
9240
9435
  MemoryStorageAdapter,
9241
9436
  MerkleTree,
9242
9437
  NATIVE_SOL_MINT,
9438
+ RelayInternalError,
9243
9439
  RelayService,
9244
9440
  RootNotFoundError,
9245
9441
  SCANNER_SUPPORTS_TRANSACT_SWAP,
9246
9442
  SIGN_IN_MESSAGE,
9443
+ SanctionsQuoteError,
9247
9444
  ShieldPoolErrors,
9248
9445
  SimpleWallet,
9446
+ UtxoAlreadySpentError,
9249
9447
  UtxoWallet,
9250
9448
  VARIABLE_FEE_DENOMINATOR,
9251
9449
  VARIABLE_FEE_NUMERATOR,
@@ -9264,6 +9462,7 @@ export {
9264
9462
  calculateRelayFee,
9265
9463
  chainNoteFromBase64,
9266
9464
  chainNoteToBase64,
9465
+ classifyRelayError,
9267
9466
  cleanupStalePendingOperations,
9268
9467
  clearPendingDeposits,
9269
9468
  clearPendingWithdrawals,
@@ -9376,6 +9575,7 @@ export {
9376
9575
  partialWithdraw,
9377
9576
  poseidonHash,
9378
9577
  preflightCheck,
9578
+ preflightNullifiers,
9379
9579
  prepareEncryptedOutput,
9380
9580
  prepareEncryptedOutputForRecipient,
9381
9581
  proofToBytes,
@@ -9423,6 +9623,7 @@ export {
9423
9623
  validateWithdrawableNote,
9424
9624
  verifyAllCircuits,
9425
9625
  verifyCircuitIntegrity,
9626
+ verifyUtxos,
9426
9627
  waitForRoot,
9427
9628
  withTiming
9428
9629
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cloak.dev/sdk",
3
3
  "description": "Shield, send, and swap on Solana privately — TypeScript SDK with UTXO-based zero-knowledge transactions",
4
- "version": "0.1.3",
4
+ "version": "0.1.5",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
7
7
  "module": "dist/index.js",
@@ -47,7 +47,7 @@
47
47
  "web3",
48
48
  "blockchain"
49
49
  ],
50
- "author": "Cloak Labs",
50
+ "author": "Cloak",
51
51
  "license": "Apache-2.0",
52
52
  "repository": {
53
53
  "type": "git",
@@ -61,15 +61,15 @@
61
61
  "@jup-ag/api": "^6.0.48",
62
62
  "@lightprotocol/hasher.rs": "^0.2.1",
63
63
  "@noble/hashes": "^1.8.0",
64
- "@solana/spl-token": "^0.4.0",
65
- "@solana/web3.js": "^1.0.0",
64
+ "@solana/spl-token": "0.4.14",
65
+ "@solana/web3.js": "^1.98.0",
66
66
  "bs58": "^6.0.0",
67
- "circomlibjs": "^0.1.7",
67
+ "circomlibjs": "0.1.7",
68
68
  "ffjavascript": "^0.3.0",
69
- "snarkjs": "^0.7.4",
69
+ "snarkjs": "0.7.6",
70
70
  "tweetnacl": "^1.0.3",
71
71
  "tweetnacl-util": "^0.15.1",
72
- "yaml": "^2.8.2"
72
+ "yaml": "^2.8.3"
73
73
  },
74
74
  "devDependencies": {
75
75
  "@coral-xyz/anchor": "^0.32.1",
@@ -86,8 +86,8 @@
86
86
  "peerDependencies": {
87
87
  "@switchboard-xyz/common": "^5.6.1",
88
88
  "@switchboard-xyz/on-demand": "^3.8.2",
89
- "@solana/spl-token": "^0.4.0",
90
- "@solana/web3.js": "^1.0.0"
89
+ "@solana/spl-token": ">=0.4.9",
90
+ "@solana/web3.js": ">=1.98.0"
91
91
  },
92
92
  "peerDependenciesMeta": {
93
93
  "@switchboard-xyz/common": {