@cloak.dev/sdk 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # @cloak.dev/sdk
1
+ # @cloak.ag/sdk
2
2
 
3
3
  TypeScript SDK for the Cloak Protocol - Private transactions on Solana using zero-knowledge proofs.
4
4
 
@@ -14,11 +14,11 @@ TypeScript SDK for the Cloak Protocol - Private transactions on Solana using zer
14
14
  ## Installation
15
15
 
16
16
  ```bash
17
- npm install @cloak.dev/sdk @solana/web3.js
17
+ npm install @cloak.ag/sdk @solana/web3.js
18
18
  # or
19
- yarn add @cloak.dev/sdk @solana/web3.js
19
+ yarn add @cloak.ag/sdk @solana/web3.js
20
20
  # or
21
- pnpm add @cloak.dev/sdk @solana/web3.js
21
+ pnpm add @cloak.ag/sdk @solana/web3.js
22
22
  ```
23
23
 
24
24
  **Note**: For swap functionality, you'll also need `@solana/spl-token`:
@@ -66,7 +66,7 @@ npm run test:examples
66
66
  ### Node.js (with Keypair)
67
67
 
68
68
  ```typescript
69
- import { CloakSDK } from "@cloak.dev/sdk";
69
+ import { CloakSDK } from "@cloak.ag/sdk";
70
70
  import { Connection, Keypair, PublicKey } from "@solana/web3.js";
71
71
 
72
72
  // Initialize connection and keypair
@@ -96,7 +96,7 @@ console.log("Withdrawn! TX:", withdrawResult.signature);
96
96
  ### React/Next.js (with Wallet Adapter)
97
97
 
98
98
  ```typescript
99
- import { CloakSDK } from "@cloak.dev/sdk";
99
+ import { CloakSDK } from "@cloak.ag/sdk";
100
100
  import { useWallet, useConnection } from "@solana/wallet-adapter-react";
101
101
  import { PublicKey } from "@solana/web3.js";
102
102
 
@@ -181,7 +181,7 @@ const result = await sdk.swap(connection, note, recipientPublicKey, {
181
181
  Use `getDistributableAmount()` to calculate the amount after fees:
182
182
 
183
183
  ```typescript
184
- import { getDistributableAmount } from "@cloak.dev/sdk";
184
+ import { getDistributableAmount } from "@cloak.ag/sdk";
185
185
 
186
186
  const deposited = 100_000_000; // 0.1 SOL
187
187
  const afterFees = getDistributableAmount(deposited); // ~94,700,000 lamports
@@ -245,7 +245,7 @@ sequenceDiagram
245
245
  ## Error Handling
246
246
 
247
247
  ```typescript
248
- import { CloakError } from "@cloak.dev/sdk";
248
+ import { CloakError } from "@cloak.ag/sdk";
249
249
 
250
250
  try {
251
251
  await sdk.withdraw(connection, note, recipient);
package/dist/index.cjs CHANGED
@@ -1863,6 +1863,22 @@ var ErrorPatterns = [
1863
1863
  recoverable: true
1864
1864
  }
1865
1865
  },
1866
+ {
1867
+ patterns: [
1868
+ "Swap refunded",
1869
+ "swap refunded",
1870
+ "funds refunded as SOL",
1871
+ "funds were refunded",
1872
+ "Swap timed out and funds were refunded"
1873
+ ],
1874
+ result: {
1875
+ title: "Swap Refunded",
1876
+ message: "No swap route was available. Your funds have been refunded as SOL to your wallet.",
1877
+ category: "service",
1878
+ suggestion: "Check your wallet balance \u2014 the SOL should already be there (minus the protocol fee). Try again with a different token or amount.",
1879
+ recoverable: true
1880
+ }
1881
+ },
1866
1882
  {
1867
1883
  patterns: [
1868
1884
  "Failed to get quote",
@@ -2615,8 +2631,8 @@ var IS_REACT_NATIVE = typeof navigator !== "undefined" && navigator.product ===
2615
2631
  var IS_BROWSER = IS_REACT_NATIVE || typeof window !== "undefined" || typeof globalThis !== "undefined" && typeof globalThis.document !== "undefined";
2616
2632
  var DEFAULT_CIRCUITS_URL = "https://cloak-circuits.s3.us-east-1.amazonaws.com/circuits/0.1.0";
2617
2633
  var SKIP_CIRCUIT_VERIFY_ENV = typeof process !== "undefined" && process.env && process.env.CLOAK_SKIP_CIRCUIT_INTEGRITY_CHECK === "1";
2618
- function isUrl(path2) {
2619
- return path2.startsWith("http://") || path2.startsWith("https://");
2634
+ function isUrl(path) {
2635
+ return path.startsWith("http://") || path.startsWith("https://");
2620
2636
  }
2621
2637
  function resolveCircuitsUrl(circuitsPath2) {
2622
2638
  if (circuitsPath2.trim() !== "" && circuitsPath2.trim() !== DEFAULT_CIRCUITS_URL) {
@@ -3083,7 +3099,7 @@ function createSetLoadedAccountsDataSizeLimitInstruction(bytes) {
3083
3099
  data
3084
3100
  });
3085
3101
  }
3086
- var CLOAK_PROGRAM_ID = new import_web35.PublicKey("c1oak6tetxYnNfvXKFkpn1d98FxtK7B68vBQLYQpWKp");
3102
+ var CLOAK_PROGRAM_ID = new import_web35.PublicKey("zh1eLd6rSphLejbFfJEneUwzHRfMKxgzrgkfwA6qRkW");
3087
3103
  var CLOAK_API_URL = "https://api.cloak.ag";
3088
3104
  var CloakSDK = class {
3089
3105
  /**
@@ -5560,56 +5576,39 @@ function chainNoteFromBase64(base64) {
5560
5576
  // src/core/transact.ts
5561
5577
  var import_circomlibjs4 = require("circomlibjs");
5562
5578
  var import_tweetnacl4 = __toESM(require("tweetnacl"), 1);
5563
- var path = __toESM(require("path"), 1);
5564
- var import_crypto13 = require("crypto");
5565
- var import_promises = require("fs/promises");
5566
- var import_os = require("os");
5579
+ var IS_BROWSER2 = typeof window !== "undefined" || typeof globalThis.document !== "undefined";
5567
5580
  var DEFAULT_TRANSACTION_CIRCUITS_URL = "https://cloak-circuits.s3.us-east-1.amazonaws.com/circuits/0.1.0";
5581
+ var DEFAULT_CLOAK_RELAY_URL = "https://api.cloak.ag";
5568
5582
  function resolveDefaultTransactionCircuitsPath() {
5569
5583
  return DEFAULT_TRANSACTION_CIRCUITS_URL;
5570
5584
  }
5571
5585
  var circuitsPath = resolveDefaultTransactionCircuitsPath();
5572
- function isHttpCircuitsPath(p) {
5573
- return p.startsWith("https://") || p.startsWith("http://");
5574
- }
5575
5586
  var _remoteTxCircuitsCache = null;
5576
5587
  async function resolveTransactionCircuitFiles() {
5577
5588
  const base = circuitsPath;
5578
- const wasmRel = path.join("transaction_js", "transaction.wasm");
5579
- const zkeyRel = "transaction_final.zkey";
5580
- if (!isHttpCircuitsPath(base)) {
5581
- const root = base;
5582
- return {
5583
- wasmPath: path.join(root, wasmRel),
5584
- zkeyPath: path.join(root, zkeyRel)
5585
- };
5586
- }
5587
5589
  const baseNorm = base.endsWith("/") ? base : `${base}/`;
5590
+ const wasmUrl = `${baseNorm}transaction_js/transaction.wasm`;
5591
+ const zkeyUrl = `${baseNorm}transaction_final.zkey`;
5592
+ if (IS_BROWSER2) {
5593
+ return { wasmPath: wasmUrl, zkeyPath: zkeyUrl };
5594
+ }
5588
5595
  if (_remoteTxCircuitsCache?.base === baseNorm) {
5589
- const { dir: dir2 } = _remoteTxCircuitsCache;
5590
- return {
5591
- wasmPath: path.join(dir2, "transaction.wasm"),
5592
- zkeyPath: path.join(dir2, "transaction_final.zkey")
5593
- };
5596
+ return _remoteTxCircuitsCache.artifacts;
5594
5597
  }
5595
- const wasmUrl = new URL(wasmRel.split(path.sep).join("/"), baseNorm).toString();
5596
- const zkeyUrl = new URL(zkeyRel, baseNorm).toString();
5597
- const subdir = `cloak-tx-circuits-${(0, import_crypto13.createHash)("sha256").update(baseNorm).digest("hex").slice(0, 16)}`;
5598
- const dir = path.join((0, import_os.tmpdir)(), subdir);
5599
- await (0, import_promises.mkdir)(dir, { recursive: true });
5600
- const wasmPath = path.join(dir, "transaction.wasm");
5601
- const zkeyPath = path.join(dir, "transaction_final.zkey");
5602
- const download = async (url, dest) => {
5598
+ const fetchAsBuffer = async (url) => {
5603
5599
  const res = await fetch(url);
5604
5600
  if (!res.ok) {
5605
5601
  throw new Error(`Failed to fetch circuit artifact ${url}: ${res.status} ${res.statusText}`);
5606
5602
  }
5607
- await (0, import_promises.writeFile)(dest, Buffer.from(await res.arrayBuffer()));
5603
+ return new Uint8Array(await res.arrayBuffer());
5608
5604
  };
5609
- await download(wasmUrl, wasmPath);
5610
- await download(zkeyUrl, zkeyPath);
5611
- _remoteTxCircuitsCache = { base: baseNorm, dir };
5612
- return { wasmPath, zkeyPath };
5605
+ const [wasmData, zkeyData] = await Promise.all([
5606
+ fetchAsBuffer(wasmUrl),
5607
+ fetchAsBuffer(zkeyUrl)
5608
+ ]);
5609
+ const artifacts = { wasmPath: wasmData, zkeyPath: zkeyData };
5610
+ _remoteTxCircuitsCache = { base: baseNorm, artifacts };
5611
+ return artifacts;
5613
5612
  }
5614
5613
  var _transactPaddingSaltCounter = 0;
5615
5614
  var _registeredViewingKeys = /* @__PURE__ */ new Set();
@@ -5703,6 +5702,12 @@ function deriveRelayUrlFromRiskQuoteUrl(riskQuoteUrl) {
5703
5702
  }
5704
5703
  return void 0;
5705
5704
  }
5705
+ function resolveRelayUrl(relayUrl) {
5706
+ const trimmed = relayUrl?.trim();
5707
+ if (trimmed === "") return void 0;
5708
+ if (trimmed) return trimmed.replace(/\/$/, "");
5709
+ return DEFAULT_CLOAK_RELAY_URL;
5710
+ }
5706
5711
  async function resolveAddressLookupTableAccounts(connection, relayUrl, altAddressesFromOptions, provided, onProgress) {
5707
5712
  if (provided && provided.length > 0) {
5708
5713
  return provided;
@@ -5800,9 +5805,8 @@ async function resolveChainNoteNk(options) {
5800
5805
  }
5801
5806
  return null;
5802
5807
  }
5803
- function shouldDefaultUseUniqueNullifiers(connection) {
5804
- const endpoint = (connection.rpcEndpoint || "").toLowerCase();
5805
- return endpoint.includes("127.0.0.1") || endpoint.includes("localhost") || endpoint.includes("0.0.0.0");
5808
+ function shouldDefaultUseUniqueNullifiers(_connection) {
5809
+ return true;
5806
5810
  }
5807
5811
  function toHex(bytes) {
5808
5812
  return Buffer.from(bytes).toString("hex");
@@ -5811,7 +5815,7 @@ async function ensureViewingKeyRegistered(options, onProgress, fallbackNk) {
5811
5815
  if (options.enforceViewingKeyRegistration === false) {
5812
5816
  return;
5813
5817
  }
5814
- const relayUrl = options.relayUrl?.replace(/\/$/, "");
5818
+ const relayUrl = resolveRelayUrl(options.relayUrl);
5815
5819
  if (!relayUrl) {
5816
5820
  throw new Error("Viewing key registration is mandatory: relayUrl is required.");
5817
5821
  }
@@ -5882,12 +5886,12 @@ async function ensureViewingKeyRegistered(options, onProgress, fallbackNk) {
5882
5886
  }
5883
5887
  async function generateTransactionProof(inputs, onProgress) {
5884
5888
  onProgress?.(10);
5885
- const { wasmPath, zkeyPath } = await resolveTransactionCircuitFiles();
5889
+ const { wasmPath: wasmInput, zkeyPath: zkeyInput } = await resolveTransactionCircuitFiles();
5886
5890
  onProgress?.(30);
5887
5891
  const { proof, publicSignals } = await snarkjs2.groth16.fullProve(
5888
5892
  inputs,
5889
- wasmPath,
5890
- zkeyPath
5893
+ wasmInput,
5894
+ zkeyInput
5891
5895
  );
5892
5896
  onProgress?.(100);
5893
5897
  return { proof, publicSignals };
@@ -6010,21 +6014,26 @@ function buildTransactInstruction(programId, payer, poolPDA, treasuryPDA, merkle
6010
6014
  { pubkey: import_web37.SystemProgram.programId, isSigner: false, isWritable: false }
6011
6015
  // 6. system_program
6012
6016
  ];
6013
- if (recipient) {
6014
- accounts.push({ pubkey: recipient, isSigner: false, isWritable: true });
6015
- if (enableRiskCheck) {
6016
- accounts.push({ pubkey: import_web37.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
6017
- }
6018
- } else if (enableRiskCheck) {
6019
- const instructionsSysvar = import_web37.SYSVAR_INSTRUCTIONS_PUBKEY;
6020
- accounts.push({ pubkey: instructionsSysvar, isSigner: false, isWritable: false });
6021
- }
6022
6017
  if (splAccounts) {
6023
6018
  accounts.push({ pubkey: splAccounts.vaultAuthority, isSigner: false, isWritable: false });
6024
6019
  accounts.push({ pubkey: splAccounts.poolVaultAta, isSigner: false, isWritable: true });
6025
6020
  accounts.push({ pubkey: splAccounts.tokenProgram, isSigner: false, isWritable: false });
6026
6021
  if (splAccounts.payerAta) {
6022
+ if (enableRiskCheck) {
6023
+ accounts.push({ pubkey: import_web37.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
6024
+ }
6027
6025
  accounts.push({ pubkey: splAccounts.payerAta, isSigner: false, isWritable: true });
6026
+ } else if (enableRiskCheck && !recipient) {
6027
+ accounts.push({ pubkey: import_web37.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
6028
+ }
6029
+ } else {
6030
+ if (recipient) {
6031
+ accounts.push({ pubkey: recipient, isSigner: false, isWritable: true });
6032
+ if (enableRiskCheck) {
6033
+ accounts.push({ pubkey: import_web37.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
6034
+ }
6035
+ } else if (enableRiskCheck) {
6036
+ accounts.push({ pubkey: import_web37.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
6028
6037
  }
6029
6038
  }
6030
6039
  return new import_web37.TransactionInstruction({
@@ -6140,13 +6149,7 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
6140
6149
  let riskQuoteIx = null;
6141
6150
  const isDeposit = recipient === void 0;
6142
6151
  const isSplPool = !mint.equals(NATIVE_SOL_MINT);
6143
- const riskCheckRequested = !!(riskOracleQueue || getRiskQuoteInstruction || riskQuoteUrl || rangeApiKey);
6144
- const riskCheckEnabled = !isSplPool && riskCheckRequested;
6145
- if (isSplPool && riskCheckRequested) {
6146
- onProgress?.(
6147
- "Skipping risk quote for SPL direct transaction path (not consumed by on-chain SPL transact handler)."
6148
- );
6149
- }
6152
+ const riskCheckEnabled = true;
6150
6153
  if (isDeposit && riskCheckEnabled && !getRiskQuoteInstruction && !riskQuoteUrl && rangeApiKey) {
6151
6154
  onProgress?.("Fetching risk quote from Switchboard (SDK-internal)...");
6152
6155
  const result = await fetchRiskQuoteIx(connection, depositor.publicKey, rangeApiKey);
@@ -6216,11 +6219,12 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
6216
6219
  proofBytes,
6217
6220
  publicInputsBytes,
6218
6221
  recipient,
6219
- isDeposit && riskCheckEnabled,
6222
+ (isDeposit || !recipient) && riskCheckEnabled,
6220
6223
  encryptedNoteBytes,
6221
6224
  splAccounts
6222
6225
  );
6223
- if (isDeposit && riskCheckEnabled && !riskQuoteIx) {
6226
+ const isSend = !recipient && publicAmount === BigInt(0);
6227
+ if ((isDeposit || isSend) && riskCheckEnabled && !riskQuoteIx) {
6224
6228
  if (getRiskQuoteInstruction) {
6225
6229
  onProgress?.("Fetching risk quote (custom)...");
6226
6230
  riskQuoteIx = await getRiskQuoteInstruction(depositor.publicKey);
@@ -6684,7 +6688,7 @@ async function transact(params, options) {
6684
6688
  onProofProgress,
6685
6689
  relayerFee = BigInt(0),
6686
6690
  relayer,
6687
- relayUrl,
6691
+ relayUrl: relayUrlOverride,
6688
6692
  depositorKeypair,
6689
6693
  signTransaction: signTransaction2,
6690
6694
  depositorPublicKey,
@@ -6705,6 +6709,7 @@ async function transact(params, options) {
6705
6709
  enforceViewingKeyRegistration = true,
6706
6710
  useChainRootForProof = true
6707
6711
  } = options;
6712
+ const relayUrl = resolveRelayUrl(relayUrlOverride);
6708
6713
  const useUniqueNullifiersEffective = useUniqueNullifiers ?? shouldDefaultUseUniqueNullifiers(connection);
6709
6714
  void enforceViewingKeyRegistration;
6710
6715
  const disableAutoRiskQuoteDerive = typeof process !== "undefined" && !!process.env && (process.env.CLOAK_DISABLE_RELAY_RISK_QUOTE_AUTO_DERIVE === "1" || process.env.CLOAK_DISABLE_RELAY_RISK_QUOTE_AUTO_DERIVE === "true");
@@ -6761,6 +6766,7 @@ async function transact(params, options) {
6761
6766
  const needsTreeForProof = paddedInputs.some(
6762
6767
  (utxo) => utxo.amount > BigInt(0) && utxo.index !== void 0
6763
6768
  );
6769
+ const hasCachedTree = !!cachedMerkleTree && cachedMerkleTree.length > 0;
6764
6770
  let merkleTree = cachedMerkleTree ?? null;
6765
6771
  let treeState = null;
6766
6772
  const chainIndexingEnabled = options.useChainForMerkle === true || typeof process !== "undefined" && !!process.env && (process.env.CLOAK_BUILD_TREE_FROM_CHAIN === "true" || process.env.CLOAK_BUILD_TREE_FROM_CHAIN === "1");
@@ -6784,7 +6790,7 @@ async function transact(params, options) {
6784
6790
  treeState = await readMerkleTreeState(connection, pdas.merkleTree, forceRefresh, relayUrl, useChainRootForProof, mint);
6785
6791
  }
6786
6792
  const relaySyncTargetIndex = treeState && treeState.nextIndex > 0 ? Math.max(requiredRelayIndex, treeState.nextIndex - 1) : requiredRelayIndex;
6787
- if (needsTreeForProof && relayTreeDisabledForMint && relayUrl && !forceChainIndexing && (forceRefresh || !merkleTree)) {
6793
+ if (needsTreeForProof && relayTreeDisabledForMint && relayUrl && !forceChainIndexing && (forceRefresh || !merkleTree) && !hasCachedTree) {
6788
6794
  if (!_relayMerkleFallbackNotifiedMints.has(relayMintKey)) {
6789
6795
  onProgress?.(`Using on-chain proof mode for ${relayMintKey} in this environment.`);
6790
6796
  _relayMerkleFallbackNotifiedMints.add(relayMintKey);
@@ -7596,14 +7602,40 @@ async function transact(params, options) {
7596
7602
  resultTree = merkleTree;
7597
7603
  }
7598
7604
  } else {
7599
- try {
7600
- const indexedCommitments = outputCommitments.map((c, i) => ({ c, idx: commitmentIndices[i], amount: paddedOutputs[i]?.amount ?? BigInt(0) })).filter((x) => x.idx >= 0 && x.amount > BigInt(0)).sort((a, b) => a.idx - b.idx);
7601
- const isContiguousFromZero = indexedCommitments.length > 0 && indexedCommitments.every((x, i) => x.idx === i);
7602
- if (isContiguousFromZero) {
7603
- const leaves = indexedCommitments.map((x) => x.c);
7604
- resultTree = await MerkleTree.create(32, leaves);
7605
+ if (relayUrl) {
7606
+ try {
7607
+ const maxIdx = Math.max(...commitmentIndices);
7608
+ if (maxIdx >= 0) {
7609
+ const postTree = await buildMerkleTreeFromRelay(relayUrl, {
7610
+ mint,
7611
+ sync: true,
7612
+ repair: true,
7613
+ timeoutMs: 8e3,
7614
+ maxRetries: 0,
7615
+ waitForIndex: maxIdx
7616
+ });
7617
+ if (postTree && postTree.length > 0) {
7618
+ const treeLenBefore = postTree.length;
7619
+ const toInsert = outputCommitments.filter((_, i) => commitmentIndices[i] >= treeLenBefore);
7620
+ if (toInsert.length > 0) {
7621
+ postTree.bulkInsert(toInsert);
7622
+ }
7623
+ resultTree = postTree;
7624
+ }
7625
+ }
7626
+ } catch {
7627
+ }
7628
+ }
7629
+ if (!resultTree) {
7630
+ try {
7631
+ const indexedCommitments = outputCommitments.map((c, i) => ({ c, idx: commitmentIndices[i], amount: paddedOutputs[i]?.amount ?? BigInt(0) })).filter((x) => x.idx >= 0 && x.amount > BigInt(0)).sort((a, b) => a.idx - b.idx);
7632
+ const isContiguousFromZero = indexedCommitments.length > 0 && indexedCommitments.every((x, i) => x.idx === i);
7633
+ if (isContiguousFromZero) {
7634
+ const leaves = indexedCommitments.map((x) => x.c);
7635
+ resultTree = await MerkleTree.create(32, leaves);
7636
+ }
7637
+ } catch {
7605
7638
  }
7606
- } catch {
7607
7639
  }
7608
7640
  }
7609
7641
  return {
@@ -7655,9 +7687,37 @@ async function partialWithdraw(inputUtxos, recipient, withdrawAmount, options) {
7655
7687
  if (inputSum < withdrawAmount) {
7656
7688
  throw new Error(`Insufficient funds: have ${inputSum}, need ${withdrawAmount}`);
7657
7689
  }
7658
- const change = inputSum - withdrawAmount;
7659
7690
  const myKeypair = inputUtxos[0].keypair;
7660
7691
  const mint = inputUtxos[0].mintAddress;
7692
+ let utxos = [...inputUtxos];
7693
+ let cachedTree = options.cachedMerkleTree;
7694
+ while (utxos.length > 2) {
7695
+ options.onProgress?.(`Consolidating notes (${utxos.length} remaining)...`);
7696
+ utxos.sort((a, b) => a.amount > b.amount ? 1 : a.amount < b.amount ? -1 : 0);
7697
+ const [small1, small2, ...rest] = utxos;
7698
+ const mergedAmount = small1.amount + small2.amount;
7699
+ const mergedOutput = await createUtxo(mergedAmount, myKeypair, mint);
7700
+ const mergeResult = await transact(
7701
+ {
7702
+ inputUtxos: [small1, small2],
7703
+ outputUtxos: [mergedOutput],
7704
+ externalAmount: BigInt(0)
7705
+ // Shield-to-shield, no external movement
7706
+ },
7707
+ {
7708
+ ...options,
7709
+ cachedMerkleTree: cachedTree
7710
+ }
7711
+ );
7712
+ cachedTree = mergeResult.merkleTree;
7713
+ const consolidatedUtxo = mergeResult.outputUtxos?.[0] ?? mergedOutput;
7714
+ if (mergeResult.commitmentIndices?.[0] >= 0) {
7715
+ consolidatedUtxo.index = mergeResult.commitmentIndices[0];
7716
+ }
7717
+ utxos = [consolidatedUtxo, ...rest];
7718
+ }
7719
+ const finalInputSum = sumUtxoAmounts(utxos);
7720
+ const change = finalInputSum - withdrawAmount;
7661
7721
  const outputUtxos = [];
7662
7722
  if (change > BigInt(0)) {
7663
7723
  const changeUtxo = await createUtxo(change, myKeypair, mint);
@@ -7665,13 +7725,15 @@ async function partialWithdraw(inputUtxos, recipient, withdrawAmount, options) {
7665
7725
  }
7666
7726
  return transact(
7667
7727
  {
7668
- inputUtxos,
7728
+ inputUtxos: utxos,
7669
7729
  outputUtxos,
7670
7730
  recipient,
7671
7731
  externalAmount: -withdrawAmount
7672
- // Negative = withdraw
7673
7732
  },
7674
- options
7733
+ {
7734
+ ...options,
7735
+ cachedMerkleTree: cachedTree
7736
+ }
7675
7737
  );
7676
7738
  }
7677
7739
  async function fullWithdraw(inputUtxos, recipient, options) {
@@ -7699,6 +7761,10 @@ async function waitForSwapCompletion(relayUrl, requestId, onProgress, maxAttempt
7699
7761
  const detail = typeof json.data.error === "string" && json.data.error.length > 0 ? json.data.error : "likely refunded after timeout";
7700
7762
  throw new Error(`Swap execution cancelled: ${detail}`);
7701
7763
  }
7764
+ if (status === "refunded") {
7765
+ const detail = typeof json.data.error === "string" && json.data.error.length > 0 ? json.data.error : "Swap route unavailable \u2014 funds refunded as SOL to your wallet";
7766
+ throw new Error(`Swap refunded: ${detail}`);
7767
+ }
7702
7768
  if (swapPhase && swapPhase !== lastSwapPhase) {
7703
7769
  lastSwapPhase = swapPhase;
7704
7770
  if (swapPhase === "waiting_timeout") {
@@ -7711,7 +7777,7 @@ async function waitForSwapCompletion(relayUrl, requestId, onProgress, maxAttempt
7711
7777
  }
7712
7778
  } catch (e) {
7713
7779
  const msg = e.message ?? "";
7714
- if (msg.includes("Swap execution failed") || msg.includes("Swap execution cancelled") || msg.startsWith("Swap failed") || msg.toLowerCase().includes("cancelled")) {
7780
+ if (msg.includes("Swap execution failed") || msg.includes("Swap execution cancelled") || msg.includes("Swap refunded") || msg.startsWith("Swap failed") || msg.toLowerCase().includes("cancelled") || msg.toLowerCase().includes("refunded")) {
7715
7781
  throw e;
7716
7782
  }
7717
7783
  }
@@ -7730,10 +7796,14 @@ async function waitForSwapCompletion(relayUrl, requestId, onProgress, maxAttempt
7730
7796
  const detail = typeof json.data.error === "string" && json.data.error.length > 0 ? json.data.error : "likely refunded after timeout";
7731
7797
  throw new Error(`Swap execution cancelled: ${detail}`);
7732
7798
  }
7799
+ if (status === "refunded") {
7800
+ const detail = typeof json.data.error === "string" && json.data.error.length > 0 ? json.data.error : "Swap route unavailable \u2014 funds refunded as SOL to your wallet";
7801
+ throw new Error(`Swap refunded: ${detail}`);
7802
+ }
7733
7803
  }
7734
7804
  } catch (e) {
7735
7805
  const msg = e.message ?? "";
7736
- if (msg.includes("Swap execution failed") || msg.includes("Swap execution cancelled") || msg.startsWith("Swap failed") || msg.toLowerCase().includes("cancelled")) {
7806
+ if (msg.includes("Swap execution failed") || msg.includes("Swap execution cancelled") || msg.includes("Swap refunded") || msg.startsWith("Swap failed") || msg.toLowerCase().includes("cancelled") || msg.toLowerCase().includes("refunded")) {
7737
7807
  throw e;
7738
7808
  }
7739
7809
  }
@@ -7758,7 +7828,7 @@ async function swapUtxo(params, options) {
7758
7828
  useChainForMerkle,
7759
7829
  relayerFee = BigInt(0),
7760
7830
  relayer,
7761
- relayUrl,
7831
+ relayUrl: relayUrlOverride,
7762
7832
  riskQuoteUrl,
7763
7833
  maxRootRetries = 40,
7764
7834
  // Increased to handle prolonged relay sync recovery under high concurrency
@@ -7767,6 +7837,7 @@ async function swapUtxo(params, options) {
7767
7837
  useUniqueNullifiers,
7768
7838
  useChainRootForProof = true
7769
7839
  } = options;
7840
+ const relayUrl = resolveRelayUrl(relayUrlOverride);
7770
7841
  const useUniqueNullifiersEffective = useUniqueNullifiers ?? shouldDefaultUseUniqueNullifiers(connection);
7771
7842
  if (inputUtxos.length === 0) {
7772
7843
  throw new Error("At least one input UTXO required");
@@ -7821,6 +7892,7 @@ async function swapUtxo(params, options) {
7821
7892
  const useRelayMerkleByDefault = shouldUseRelayMerkleByDefault(connection);
7822
7893
  const relayMintKey = swapPoolMint.toBase58();
7823
7894
  let relayTreeDisabledForMint = _relayMerkleDisabledMints.has(relayMintKey) || !useRelayMerkleByDefault;
7895
+ const hasCachedTree = !!cachedMerkleTree && cachedMerkleTree.length > 0;
7824
7896
  const requiredRelayIndex = paddedInputs.reduce((maxIndex, utxo) => {
7825
7897
  if (utxo.amount === BigInt(0) || utxo.index === void 0 || utxo.index < 0) {
7826
7898
  return maxIndex;
@@ -7837,7 +7909,7 @@ async function swapUtxo(params, options) {
7837
7909
  merkleState = await readMerkleTreeState(connection, pdas.merkleTree, forceRefresh, relayUrl, useChainRootForProof, swapPoolMint);
7838
7910
  }
7839
7911
  const relaySyncTargetIndex = merkleState && merkleState.nextIndex > 0 ? Math.max(requiredRelayIndex, merkleState.nextIndex - 1) : requiredRelayIndex;
7840
- if (needsTreeForProof && relayTreeDisabledForMint && relayUrl && !forceChainIndexing && (forceRefresh || !merkleTree)) {
7912
+ if (needsTreeForProof && relayTreeDisabledForMint && relayUrl && !forceChainIndexing && (forceRefresh || !merkleTree) && !hasCachedTree) {
7841
7913
  if (!_relayMerkleFallbackNotifiedMints.has(relayMintKey)) {
7842
7914
  onProgress?.(`Using on-chain proof mode for ${relayMintKey} in this environment.`);
7843
7915
  _relayMerkleFallbackNotifiedMints.add(relayMintKey);
package/dist/index.d.cts CHANGED
@@ -2843,7 +2843,10 @@ interface TransactOptions {
2843
2843
  relayerFee?: bigint;
2844
2844
  /** Relayer address for fee payment */
2845
2845
  relayer?: PublicKey;
2846
- /** Relay service URL for submitting transactions */
2846
+ /**
2847
+ * Relay URL override.
2848
+ * Defaults to `https://api.cloak.ag` when omitted.
2849
+ */
2847
2850
  relayUrl?: string;
2848
2851
  /** Keypair of the depositor (signs the deposit transaction) - for programmatic use */
2849
2852
  depositorKeypair?: Keypair;
package/dist/index.d.ts CHANGED
@@ -2843,7 +2843,10 @@ interface TransactOptions {
2843
2843
  relayerFee?: bigint;
2844
2844
  /** Relayer address for fee payment */
2845
2845
  relayer?: PublicKey;
2846
- /** Relay service URL for submitting transactions */
2846
+ /**
2847
+ * Relay URL override.
2848
+ * Defaults to `https://api.cloak.ag` when omitted.
2849
+ */
2847
2850
  relayUrl?: string;
2848
2851
  /** Keypair of the depositor (signs the deposit transaction) - for programmatic use */
2849
2852
  depositorKeypair?: Keypair;
package/dist/index.js CHANGED
@@ -1401,6 +1401,22 @@ var ErrorPatterns = [
1401
1401
  recoverable: true
1402
1402
  }
1403
1403
  },
1404
+ {
1405
+ patterns: [
1406
+ "Swap refunded",
1407
+ "swap refunded",
1408
+ "funds refunded as SOL",
1409
+ "funds were refunded",
1410
+ "Swap timed out and funds were refunded"
1411
+ ],
1412
+ result: {
1413
+ title: "Swap Refunded",
1414
+ message: "No swap route was available. Your funds have been refunded as SOL to your wallet.",
1415
+ category: "service",
1416
+ suggestion: "Check your wallet balance \u2014 the SOL should already be there (minus the protocol fee). Try again with a different token or amount.",
1417
+ recoverable: true
1418
+ }
1419
+ },
1404
1420
  {
1405
1421
  patterns: [
1406
1422
  "Failed to get quote",
@@ -2156,8 +2172,8 @@ var IS_REACT_NATIVE = typeof navigator !== "undefined" && navigator.product ===
2156
2172
  var IS_BROWSER = IS_REACT_NATIVE || typeof window !== "undefined" || typeof globalThis !== "undefined" && typeof globalThis.document !== "undefined";
2157
2173
  var DEFAULT_CIRCUITS_URL = "https://cloak-circuits.s3.us-east-1.amazonaws.com/circuits/0.1.0";
2158
2174
  var SKIP_CIRCUIT_VERIFY_ENV = typeof process !== "undefined" && process.env && process.env.CLOAK_SKIP_CIRCUIT_INTEGRITY_CHECK === "1";
2159
- function isUrl(path2) {
2160
- return path2.startsWith("http://") || path2.startsWith("https://");
2175
+ function isUrl(path) {
2176
+ return path.startsWith("http://") || path.startsWith("https://");
2161
2177
  }
2162
2178
  function resolveCircuitsUrl(circuitsPath2) {
2163
2179
  if (circuitsPath2.trim() !== "" && circuitsPath2.trim() !== DEFAULT_CIRCUITS_URL) {
@@ -2621,7 +2637,7 @@ function createSetLoadedAccountsDataSizeLimitInstruction(bytes) {
2621
2637
  data
2622
2638
  });
2623
2639
  }
2624
- var CLOAK_PROGRAM_ID = new PublicKey4("c1oak6tetxYnNfvXKFkpn1d98FxtK7B68vBQLYQpWKp");
2640
+ var CLOAK_PROGRAM_ID = new PublicKey4("zh1eLd6rSphLejbFfJEneUwzHRfMKxgzrgkfwA6qRkW");
2625
2641
  var CLOAK_API_URL = "https://api.cloak.ag";
2626
2642
  var CloakSDK = class {
2627
2643
  /**
@@ -5113,56 +5129,39 @@ function chainNoteFromBase64(base64) {
5113
5129
  // src/core/transact.ts
5114
5130
  import { buildPoseidon as buildPoseidon3 } from "circomlibjs";
5115
5131
  import nacl4 from "tweetnacl";
5116
- import * as path from "path";
5117
- import { createHash } from "crypto";
5118
- import { mkdir, writeFile } from "fs/promises";
5119
- import { tmpdir } from "os";
5132
+ var IS_BROWSER2 = typeof window !== "undefined" || typeof globalThis.document !== "undefined";
5120
5133
  var DEFAULT_TRANSACTION_CIRCUITS_URL = "https://cloak-circuits.s3.us-east-1.amazonaws.com/circuits/0.1.0";
5134
+ var DEFAULT_CLOAK_RELAY_URL = "https://api.cloak.ag";
5121
5135
  function resolveDefaultTransactionCircuitsPath() {
5122
5136
  return DEFAULT_TRANSACTION_CIRCUITS_URL;
5123
5137
  }
5124
5138
  var circuitsPath = resolveDefaultTransactionCircuitsPath();
5125
- function isHttpCircuitsPath(p) {
5126
- return p.startsWith("https://") || p.startsWith("http://");
5127
- }
5128
5139
  var _remoteTxCircuitsCache = null;
5129
5140
  async function resolveTransactionCircuitFiles() {
5130
5141
  const base = circuitsPath;
5131
- const wasmRel = path.join("transaction_js", "transaction.wasm");
5132
- const zkeyRel = "transaction_final.zkey";
5133
- if (!isHttpCircuitsPath(base)) {
5134
- const root = base;
5135
- return {
5136
- wasmPath: path.join(root, wasmRel),
5137
- zkeyPath: path.join(root, zkeyRel)
5138
- };
5139
- }
5140
5142
  const baseNorm = base.endsWith("/") ? base : `${base}/`;
5143
+ const wasmUrl = `${baseNorm}transaction_js/transaction.wasm`;
5144
+ const zkeyUrl = `${baseNorm}transaction_final.zkey`;
5145
+ if (IS_BROWSER2) {
5146
+ return { wasmPath: wasmUrl, zkeyPath: zkeyUrl };
5147
+ }
5141
5148
  if (_remoteTxCircuitsCache?.base === baseNorm) {
5142
- const { dir: dir2 } = _remoteTxCircuitsCache;
5143
- return {
5144
- wasmPath: path.join(dir2, "transaction.wasm"),
5145
- zkeyPath: path.join(dir2, "transaction_final.zkey")
5146
- };
5149
+ return _remoteTxCircuitsCache.artifacts;
5147
5150
  }
5148
- const wasmUrl = new URL(wasmRel.split(path.sep).join("/"), baseNorm).toString();
5149
- const zkeyUrl = new URL(zkeyRel, baseNorm).toString();
5150
- const subdir = `cloak-tx-circuits-${createHash("sha256").update(baseNorm).digest("hex").slice(0, 16)}`;
5151
- const dir = path.join(tmpdir(), subdir);
5152
- await mkdir(dir, { recursive: true });
5153
- const wasmPath = path.join(dir, "transaction.wasm");
5154
- const zkeyPath = path.join(dir, "transaction_final.zkey");
5155
- const download = async (url, dest) => {
5151
+ const fetchAsBuffer = async (url) => {
5156
5152
  const res = await fetch(url);
5157
5153
  if (!res.ok) {
5158
5154
  throw new Error(`Failed to fetch circuit artifact ${url}: ${res.status} ${res.statusText}`);
5159
5155
  }
5160
- await writeFile(dest, Buffer.from(await res.arrayBuffer()));
5156
+ return new Uint8Array(await res.arrayBuffer());
5161
5157
  };
5162
- await download(wasmUrl, wasmPath);
5163
- await download(zkeyUrl, zkeyPath);
5164
- _remoteTxCircuitsCache = { base: baseNorm, dir };
5165
- return { wasmPath, zkeyPath };
5158
+ const [wasmData, zkeyData] = await Promise.all([
5159
+ fetchAsBuffer(wasmUrl),
5160
+ fetchAsBuffer(zkeyUrl)
5161
+ ]);
5162
+ const artifacts = { wasmPath: wasmData, zkeyPath: zkeyData };
5163
+ _remoteTxCircuitsCache = { base: baseNorm, artifacts };
5164
+ return artifacts;
5166
5165
  }
5167
5166
  var _transactPaddingSaltCounter = 0;
5168
5167
  var _registeredViewingKeys = /* @__PURE__ */ new Set();
@@ -5256,6 +5255,12 @@ function deriveRelayUrlFromRiskQuoteUrl(riskQuoteUrl) {
5256
5255
  }
5257
5256
  return void 0;
5258
5257
  }
5258
+ function resolveRelayUrl(relayUrl) {
5259
+ const trimmed = relayUrl?.trim();
5260
+ if (trimmed === "") return void 0;
5261
+ if (trimmed) return trimmed.replace(/\/$/, "");
5262
+ return DEFAULT_CLOAK_RELAY_URL;
5263
+ }
5259
5264
  async function resolveAddressLookupTableAccounts(connection, relayUrl, altAddressesFromOptions, provided, onProgress) {
5260
5265
  if (provided && provided.length > 0) {
5261
5266
  return provided;
@@ -5353,9 +5358,8 @@ async function resolveChainNoteNk(options) {
5353
5358
  }
5354
5359
  return null;
5355
5360
  }
5356
- function shouldDefaultUseUniqueNullifiers(connection) {
5357
- const endpoint = (connection.rpcEndpoint || "").toLowerCase();
5358
- return endpoint.includes("127.0.0.1") || endpoint.includes("localhost") || endpoint.includes("0.0.0.0");
5361
+ function shouldDefaultUseUniqueNullifiers(_connection) {
5362
+ return true;
5359
5363
  }
5360
5364
  function toHex(bytes) {
5361
5365
  return Buffer.from(bytes).toString("hex");
@@ -5364,7 +5368,7 @@ async function ensureViewingKeyRegistered(options, onProgress, fallbackNk) {
5364
5368
  if (options.enforceViewingKeyRegistration === false) {
5365
5369
  return;
5366
5370
  }
5367
- const relayUrl = options.relayUrl?.replace(/\/$/, "");
5371
+ const relayUrl = resolveRelayUrl(options.relayUrl);
5368
5372
  if (!relayUrl) {
5369
5373
  throw new Error("Viewing key registration is mandatory: relayUrl is required.");
5370
5374
  }
@@ -5435,12 +5439,12 @@ async function ensureViewingKeyRegistered(options, onProgress, fallbackNk) {
5435
5439
  }
5436
5440
  async function generateTransactionProof(inputs, onProgress) {
5437
5441
  onProgress?.(10);
5438
- const { wasmPath, zkeyPath } = await resolveTransactionCircuitFiles();
5442
+ const { wasmPath: wasmInput, zkeyPath: zkeyInput } = await resolveTransactionCircuitFiles();
5439
5443
  onProgress?.(30);
5440
5444
  const { proof, publicSignals } = await snarkjs2.groth16.fullProve(
5441
5445
  inputs,
5442
- wasmPath,
5443
- zkeyPath
5446
+ wasmInput,
5447
+ zkeyInput
5444
5448
  );
5445
5449
  onProgress?.(100);
5446
5450
  return { proof, publicSignals };
@@ -5563,21 +5567,26 @@ function buildTransactInstruction(programId, payer, poolPDA, treasuryPDA, merkle
5563
5567
  { pubkey: SystemProgram2.programId, isSigner: false, isWritable: false }
5564
5568
  // 6. system_program
5565
5569
  ];
5566
- if (recipient) {
5567
- accounts.push({ pubkey: recipient, isSigner: false, isWritable: true });
5568
- if (enableRiskCheck) {
5569
- accounts.push({ pubkey: SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
5570
- }
5571
- } else if (enableRiskCheck) {
5572
- const instructionsSysvar = SYSVAR_INSTRUCTIONS_PUBKEY;
5573
- accounts.push({ pubkey: instructionsSysvar, isSigner: false, isWritable: false });
5574
- }
5575
5570
  if (splAccounts) {
5576
5571
  accounts.push({ pubkey: splAccounts.vaultAuthority, isSigner: false, isWritable: false });
5577
5572
  accounts.push({ pubkey: splAccounts.poolVaultAta, isSigner: false, isWritable: true });
5578
5573
  accounts.push({ pubkey: splAccounts.tokenProgram, isSigner: false, isWritable: false });
5579
5574
  if (splAccounts.payerAta) {
5575
+ if (enableRiskCheck) {
5576
+ accounts.push({ pubkey: SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
5577
+ }
5580
5578
  accounts.push({ pubkey: splAccounts.payerAta, isSigner: false, isWritable: true });
5579
+ } else if (enableRiskCheck && !recipient) {
5580
+ accounts.push({ pubkey: SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
5581
+ }
5582
+ } else {
5583
+ if (recipient) {
5584
+ accounts.push({ pubkey: recipient, isSigner: false, isWritable: true });
5585
+ if (enableRiskCheck) {
5586
+ accounts.push({ pubkey: SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
5587
+ }
5588
+ } else if (enableRiskCheck) {
5589
+ accounts.push({ pubkey: SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false });
5581
5590
  }
5582
5591
  }
5583
5592
  return new TransactionInstruction3({
@@ -5693,13 +5702,7 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
5693
5702
  let riskQuoteIx = null;
5694
5703
  const isDeposit = recipient === void 0;
5695
5704
  const isSplPool = !mint.equals(NATIVE_SOL_MINT);
5696
- const riskCheckRequested = !!(riskOracleQueue || getRiskQuoteInstruction || riskQuoteUrl || rangeApiKey);
5697
- const riskCheckEnabled = !isSplPool && riskCheckRequested;
5698
- if (isSplPool && riskCheckRequested) {
5699
- onProgress?.(
5700
- "Skipping risk quote for SPL direct transaction path (not consumed by on-chain SPL transact handler)."
5701
- );
5702
- }
5705
+ const riskCheckEnabled = true;
5703
5706
  if (isDeposit && riskCheckEnabled && !getRiskQuoteInstruction && !riskQuoteUrl && rangeApiKey) {
5704
5707
  onProgress?.("Fetching risk quote from Switchboard (SDK-internal)...");
5705
5708
  const result = await fetchRiskQuoteIx(connection, depositor.publicKey, rangeApiKey);
@@ -5769,11 +5772,12 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
5769
5772
  proofBytes,
5770
5773
  publicInputsBytes,
5771
5774
  recipient,
5772
- isDeposit && riskCheckEnabled,
5775
+ (isDeposit || !recipient) && riskCheckEnabled,
5773
5776
  encryptedNoteBytes,
5774
5777
  splAccounts
5775
5778
  );
5776
- if (isDeposit && riskCheckEnabled && !riskQuoteIx) {
5779
+ const isSend = !recipient && publicAmount === BigInt(0);
5780
+ if ((isDeposit || isSend) && riskCheckEnabled && !riskQuoteIx) {
5777
5781
  if (getRiskQuoteInstruction) {
5778
5782
  onProgress?.("Fetching risk quote (custom)...");
5779
5783
  riskQuoteIx = await getRiskQuoteInstruction(depositor.publicKey);
@@ -6237,7 +6241,7 @@ async function transact(params, options) {
6237
6241
  onProofProgress,
6238
6242
  relayerFee = BigInt(0),
6239
6243
  relayer,
6240
- relayUrl,
6244
+ relayUrl: relayUrlOverride,
6241
6245
  depositorKeypair,
6242
6246
  signTransaction: signTransaction2,
6243
6247
  depositorPublicKey,
@@ -6258,6 +6262,7 @@ async function transact(params, options) {
6258
6262
  enforceViewingKeyRegistration = true,
6259
6263
  useChainRootForProof = true
6260
6264
  } = options;
6265
+ const relayUrl = resolveRelayUrl(relayUrlOverride);
6261
6266
  const useUniqueNullifiersEffective = useUniqueNullifiers ?? shouldDefaultUseUniqueNullifiers(connection);
6262
6267
  void enforceViewingKeyRegistration;
6263
6268
  const disableAutoRiskQuoteDerive = typeof process !== "undefined" && !!process.env && (process.env.CLOAK_DISABLE_RELAY_RISK_QUOTE_AUTO_DERIVE === "1" || process.env.CLOAK_DISABLE_RELAY_RISK_QUOTE_AUTO_DERIVE === "true");
@@ -6314,6 +6319,7 @@ async function transact(params, options) {
6314
6319
  const needsTreeForProof = paddedInputs.some(
6315
6320
  (utxo) => utxo.amount > BigInt(0) && utxo.index !== void 0
6316
6321
  );
6322
+ const hasCachedTree = !!cachedMerkleTree && cachedMerkleTree.length > 0;
6317
6323
  let merkleTree = cachedMerkleTree ?? null;
6318
6324
  let treeState = null;
6319
6325
  const chainIndexingEnabled = options.useChainForMerkle === true || typeof process !== "undefined" && !!process.env && (process.env.CLOAK_BUILD_TREE_FROM_CHAIN === "true" || process.env.CLOAK_BUILD_TREE_FROM_CHAIN === "1");
@@ -6337,7 +6343,7 @@ async function transact(params, options) {
6337
6343
  treeState = await readMerkleTreeState(connection, pdas.merkleTree, forceRefresh, relayUrl, useChainRootForProof, mint);
6338
6344
  }
6339
6345
  const relaySyncTargetIndex = treeState && treeState.nextIndex > 0 ? Math.max(requiredRelayIndex, treeState.nextIndex - 1) : requiredRelayIndex;
6340
- if (needsTreeForProof && relayTreeDisabledForMint && relayUrl && !forceChainIndexing && (forceRefresh || !merkleTree)) {
6346
+ if (needsTreeForProof && relayTreeDisabledForMint && relayUrl && !forceChainIndexing && (forceRefresh || !merkleTree) && !hasCachedTree) {
6341
6347
  if (!_relayMerkleFallbackNotifiedMints.has(relayMintKey)) {
6342
6348
  onProgress?.(`Using on-chain proof mode for ${relayMintKey} in this environment.`);
6343
6349
  _relayMerkleFallbackNotifiedMints.add(relayMintKey);
@@ -7149,14 +7155,40 @@ async function transact(params, options) {
7149
7155
  resultTree = merkleTree;
7150
7156
  }
7151
7157
  } else {
7152
- try {
7153
- const indexedCommitments = outputCommitments.map((c, i) => ({ c, idx: commitmentIndices[i], amount: paddedOutputs[i]?.amount ?? BigInt(0) })).filter((x) => x.idx >= 0 && x.amount > BigInt(0)).sort((a, b) => a.idx - b.idx);
7154
- const isContiguousFromZero = indexedCommitments.length > 0 && indexedCommitments.every((x, i) => x.idx === i);
7155
- if (isContiguousFromZero) {
7156
- const leaves = indexedCommitments.map((x) => x.c);
7157
- resultTree = await MerkleTree.create(32, leaves);
7158
+ if (relayUrl) {
7159
+ try {
7160
+ const maxIdx = Math.max(...commitmentIndices);
7161
+ if (maxIdx >= 0) {
7162
+ const postTree = await buildMerkleTreeFromRelay(relayUrl, {
7163
+ mint,
7164
+ sync: true,
7165
+ repair: true,
7166
+ timeoutMs: 8e3,
7167
+ maxRetries: 0,
7168
+ waitForIndex: maxIdx
7169
+ });
7170
+ if (postTree && postTree.length > 0) {
7171
+ const treeLenBefore = postTree.length;
7172
+ const toInsert = outputCommitments.filter((_, i) => commitmentIndices[i] >= treeLenBefore);
7173
+ if (toInsert.length > 0) {
7174
+ postTree.bulkInsert(toInsert);
7175
+ }
7176
+ resultTree = postTree;
7177
+ }
7178
+ }
7179
+ } catch {
7180
+ }
7181
+ }
7182
+ if (!resultTree) {
7183
+ try {
7184
+ const indexedCommitments = outputCommitments.map((c, i) => ({ c, idx: commitmentIndices[i], amount: paddedOutputs[i]?.amount ?? BigInt(0) })).filter((x) => x.idx >= 0 && x.amount > BigInt(0)).sort((a, b) => a.idx - b.idx);
7185
+ const isContiguousFromZero = indexedCommitments.length > 0 && indexedCommitments.every((x, i) => x.idx === i);
7186
+ if (isContiguousFromZero) {
7187
+ const leaves = indexedCommitments.map((x) => x.c);
7188
+ resultTree = await MerkleTree.create(32, leaves);
7189
+ }
7190
+ } catch {
7158
7191
  }
7159
- } catch {
7160
7192
  }
7161
7193
  }
7162
7194
  return {
@@ -7208,9 +7240,37 @@ async function partialWithdraw(inputUtxos, recipient, withdrawAmount, options) {
7208
7240
  if (inputSum < withdrawAmount) {
7209
7241
  throw new Error(`Insufficient funds: have ${inputSum}, need ${withdrawAmount}`);
7210
7242
  }
7211
- const change = inputSum - withdrawAmount;
7212
7243
  const myKeypair = inputUtxos[0].keypair;
7213
7244
  const mint = inputUtxos[0].mintAddress;
7245
+ let utxos = [...inputUtxos];
7246
+ let cachedTree = options.cachedMerkleTree;
7247
+ while (utxos.length > 2) {
7248
+ options.onProgress?.(`Consolidating notes (${utxos.length} remaining)...`);
7249
+ utxos.sort((a, b) => a.amount > b.amount ? 1 : a.amount < b.amount ? -1 : 0);
7250
+ const [small1, small2, ...rest] = utxos;
7251
+ const mergedAmount = small1.amount + small2.amount;
7252
+ const mergedOutput = await createUtxo(mergedAmount, myKeypair, mint);
7253
+ const mergeResult = await transact(
7254
+ {
7255
+ inputUtxos: [small1, small2],
7256
+ outputUtxos: [mergedOutput],
7257
+ externalAmount: BigInt(0)
7258
+ // Shield-to-shield, no external movement
7259
+ },
7260
+ {
7261
+ ...options,
7262
+ cachedMerkleTree: cachedTree
7263
+ }
7264
+ );
7265
+ cachedTree = mergeResult.merkleTree;
7266
+ const consolidatedUtxo = mergeResult.outputUtxos?.[0] ?? mergedOutput;
7267
+ if (mergeResult.commitmentIndices?.[0] >= 0) {
7268
+ consolidatedUtxo.index = mergeResult.commitmentIndices[0];
7269
+ }
7270
+ utxos = [consolidatedUtxo, ...rest];
7271
+ }
7272
+ const finalInputSum = sumUtxoAmounts(utxos);
7273
+ const change = finalInputSum - withdrawAmount;
7214
7274
  const outputUtxos = [];
7215
7275
  if (change > BigInt(0)) {
7216
7276
  const changeUtxo = await createUtxo(change, myKeypair, mint);
@@ -7218,13 +7278,15 @@ async function partialWithdraw(inputUtxos, recipient, withdrawAmount, options) {
7218
7278
  }
7219
7279
  return transact(
7220
7280
  {
7221
- inputUtxos,
7281
+ inputUtxos: utxos,
7222
7282
  outputUtxos,
7223
7283
  recipient,
7224
7284
  externalAmount: -withdrawAmount
7225
- // Negative = withdraw
7226
7285
  },
7227
- options
7286
+ {
7287
+ ...options,
7288
+ cachedMerkleTree: cachedTree
7289
+ }
7228
7290
  );
7229
7291
  }
7230
7292
  async function fullWithdraw(inputUtxos, recipient, options) {
@@ -7252,6 +7314,10 @@ async function waitForSwapCompletion(relayUrl, requestId, onProgress, maxAttempt
7252
7314
  const detail = typeof json.data.error === "string" && json.data.error.length > 0 ? json.data.error : "likely refunded after timeout";
7253
7315
  throw new Error(`Swap execution cancelled: ${detail}`);
7254
7316
  }
7317
+ if (status === "refunded") {
7318
+ const detail = typeof json.data.error === "string" && json.data.error.length > 0 ? json.data.error : "Swap route unavailable \u2014 funds refunded as SOL to your wallet";
7319
+ throw new Error(`Swap refunded: ${detail}`);
7320
+ }
7255
7321
  if (swapPhase && swapPhase !== lastSwapPhase) {
7256
7322
  lastSwapPhase = swapPhase;
7257
7323
  if (swapPhase === "waiting_timeout") {
@@ -7264,7 +7330,7 @@ async function waitForSwapCompletion(relayUrl, requestId, onProgress, maxAttempt
7264
7330
  }
7265
7331
  } catch (e) {
7266
7332
  const msg = e.message ?? "";
7267
- if (msg.includes("Swap execution failed") || msg.includes("Swap execution cancelled") || msg.startsWith("Swap failed") || msg.toLowerCase().includes("cancelled")) {
7333
+ if (msg.includes("Swap execution failed") || msg.includes("Swap execution cancelled") || msg.includes("Swap refunded") || msg.startsWith("Swap failed") || msg.toLowerCase().includes("cancelled") || msg.toLowerCase().includes("refunded")) {
7268
7334
  throw e;
7269
7335
  }
7270
7336
  }
@@ -7283,10 +7349,14 @@ async function waitForSwapCompletion(relayUrl, requestId, onProgress, maxAttempt
7283
7349
  const detail = typeof json.data.error === "string" && json.data.error.length > 0 ? json.data.error : "likely refunded after timeout";
7284
7350
  throw new Error(`Swap execution cancelled: ${detail}`);
7285
7351
  }
7352
+ if (status === "refunded") {
7353
+ const detail = typeof json.data.error === "string" && json.data.error.length > 0 ? json.data.error : "Swap route unavailable \u2014 funds refunded as SOL to your wallet";
7354
+ throw new Error(`Swap refunded: ${detail}`);
7355
+ }
7286
7356
  }
7287
7357
  } catch (e) {
7288
7358
  const msg = e.message ?? "";
7289
- if (msg.includes("Swap execution failed") || msg.includes("Swap execution cancelled") || msg.startsWith("Swap failed") || msg.toLowerCase().includes("cancelled")) {
7359
+ if (msg.includes("Swap execution failed") || msg.includes("Swap execution cancelled") || msg.includes("Swap refunded") || msg.startsWith("Swap failed") || msg.toLowerCase().includes("cancelled") || msg.toLowerCase().includes("refunded")) {
7290
7360
  throw e;
7291
7361
  }
7292
7362
  }
@@ -7311,7 +7381,7 @@ async function swapUtxo(params, options) {
7311
7381
  useChainForMerkle,
7312
7382
  relayerFee = BigInt(0),
7313
7383
  relayer,
7314
- relayUrl,
7384
+ relayUrl: relayUrlOverride,
7315
7385
  riskQuoteUrl,
7316
7386
  maxRootRetries = 40,
7317
7387
  // Increased to handle prolonged relay sync recovery under high concurrency
@@ -7320,6 +7390,7 @@ async function swapUtxo(params, options) {
7320
7390
  useUniqueNullifiers,
7321
7391
  useChainRootForProof = true
7322
7392
  } = options;
7393
+ const relayUrl = resolveRelayUrl(relayUrlOverride);
7323
7394
  const useUniqueNullifiersEffective = useUniqueNullifiers ?? shouldDefaultUseUniqueNullifiers(connection);
7324
7395
  if (inputUtxos.length === 0) {
7325
7396
  throw new Error("At least one input UTXO required");
@@ -7374,6 +7445,7 @@ async function swapUtxo(params, options) {
7374
7445
  const useRelayMerkleByDefault = shouldUseRelayMerkleByDefault(connection);
7375
7446
  const relayMintKey = swapPoolMint.toBase58();
7376
7447
  let relayTreeDisabledForMint = _relayMerkleDisabledMints.has(relayMintKey) || !useRelayMerkleByDefault;
7448
+ const hasCachedTree = !!cachedMerkleTree && cachedMerkleTree.length > 0;
7377
7449
  const requiredRelayIndex = paddedInputs.reduce((maxIndex, utxo) => {
7378
7450
  if (utxo.amount === BigInt(0) || utxo.index === void 0 || utxo.index < 0) {
7379
7451
  return maxIndex;
@@ -7390,7 +7462,7 @@ async function swapUtxo(params, options) {
7390
7462
  merkleState = await readMerkleTreeState(connection, pdas.merkleTree, forceRefresh, relayUrl, useChainRootForProof, swapPoolMint);
7391
7463
  }
7392
7464
  const relaySyncTargetIndex = merkleState && merkleState.nextIndex > 0 ? Math.max(requiredRelayIndex, merkleState.nextIndex - 1) : requiredRelayIndex;
7393
- if (needsTreeForProof && relayTreeDisabledForMint && relayUrl && !forceChainIndexing && (forceRefresh || !merkleTree)) {
7465
+ if (needsTreeForProof && relayTreeDisabledForMint && relayUrl && !forceChainIndexing && (forceRefresh || !merkleTree) && !hasCachedTree) {
7394
7466
  if (!_relayMerkleFallbackNotifiedMints.has(relayMintKey)) {
7395
7467
  onProgress?.(`Using on-chain proof mode for ${relayMintKey} in this environment.`);
7396
7468
  _relayMerkleFallbackNotifiedMints.add(relayMintKey);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cloak.dev/sdk",
3
3
  "description": "TypeScript SDK for Cloak",
4
- "version": "0.1.0",
4
+ "version": "0.1.1",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
7
7
  "module": "dist/index.js",
@@ -21,6 +21,7 @@
21
21
  "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch",
22
22
  "test:coverage": "NODE_OPTIONS=--experimental-vm-modules jest --coverage",
23
23
  "test:verbose": "NODE_OPTIONS=--experimental-vm-modules jest --verbose",
24
+ "prepare": "npm run build",
24
25
  "prepublishOnly": "npm run build",
25
26
  "example:fast-send": "tsx examples/fast-send.ts",
26
27
  "example:fast-usdc-send": "tsx examples/fast-usdc-send.ts",