@cloak.dev/sdk 0.2.2 → 0.2.3-staging.005e3de

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
@@ -577,9 +577,9 @@ var LocalStorageAdapter = class {
577
577
  };
578
578
 
579
579
  // src/scanning/compliance-keys.ts
580
- import nacl2 from "tweetnacl";
580
+ import nacl3 from "tweetnacl";
581
581
  import { blake3 as blake32 } from "@noble/hashes/blake3";
582
- import { sha256 } from "@noble/hashes/sha2";
582
+ import { sha256 as sha2562 } from "@noble/hashes/sha2";
583
583
 
584
584
  // src/relay/viewing-key.ts
585
585
  var VIEWING_KEY_CHALLENGE_PREFIX = "CLOAK_VIEWING_KEY_REGISTER";
@@ -621,7 +621,7 @@ function parseStrictViewingKeyChallenge(response, userPubkey, viewingKeyHex) {
621
621
 
622
622
  // src/config/relay.ts
623
623
  var CLOAK_PRODUCTION_RELAY_URL = "https://api.cloak.ag";
624
- var RELAY_ORIGIN_ALLOWLIST = [CLOAK_PRODUCTION_RELAY_URL];
624
+ var RELAY_ORIGIN_ALLOWLIST = ["https://staging-api.cloak.ag"];
625
625
  var ABSOLUTE_URL = /^[a-z][a-z0-9+.-]*:\/\//i;
626
626
  var LOOPBACK_IPV4 = /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
627
627
  var THIS_NETWORK_IPV4 = /^0\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
@@ -766,6 +766,275 @@ function connectionRpcEndpoint(connection) {
766
766
  return connection?._rpcEndpoint ?? connection?.rpcEndpoint;
767
767
  }
768
768
 
769
+ // src/relay/payload.ts
770
+ import { sha256 } from "@noble/hashes/sha2";
771
+ import { bytesToHex as bytesToHex2 } from "@noble/hashes/utils";
772
+ import nacl2 from "tweetnacl";
773
+ import { Keypair, PublicKey as PublicKey3, SystemProgram, Transaction } from "@solana/web3.js";
774
+ var REQUEST_AUTH_DOMAIN = "CLOAK_RELAY_REQUEST_AUTH_V1";
775
+ var REQUEST_AUTH_MAX_AGE_SECONDS = 300;
776
+ var REQUEST_AUTH_MAX_FUTURE_SKEW_SECONDS = 30;
777
+ var RELAY_AUTH_APPROVAL_MARGIN_SECONDS = 15;
778
+ function sha256Hex(text) {
779
+ return bytesToHex2(sha256(new TextEncoder().encode(text)));
780
+ }
781
+ function randomNonceUuid() {
782
+ const webCrypto = globalThis?.crypto;
783
+ if (typeof webCrypto?.randomUUID === "function") return webCrypto.randomUUID();
784
+ const proc = globalThis?.process;
785
+ const nodeCrypto = typeof proc?.getBuiltinModule === "function" ? proc.getBuiltinModule("node:crypto") : void 0;
786
+ if (typeof nodeCrypto?.randomUUID === "function") return nodeCrypto.randomUUID();
787
+ const bytes = randomBytes(16);
788
+ bytes[6] = bytes[6] & 15 | 64;
789
+ bytes[8] = bytes[8] & 63 | 128;
790
+ const hex = bytesToHex2(bytes);
791
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
792
+ }
793
+ var TRANSACT_AUTH_FIELDS = [
794
+ "encrypted_notes",
795
+ "max_fee",
796
+ "mint",
797
+ "proof_bytes",
798
+ "public_inputs",
799
+ "recipient",
800
+ "recipient_delivery_notes",
801
+ "risk_quote",
802
+ "sender"
803
+ ];
804
+ var TRANSACT_SWAP_AUTH_FIELDS = [
805
+ "close_timed_out",
806
+ "dexes",
807
+ "encrypted_notes",
808
+ "exclude_dexes",
809
+ "max_fee",
810
+ "min_output_amount",
811
+ "output_mint",
812
+ "proof_bytes",
813
+ "public_inputs",
814
+ "recipient",
815
+ "recipient_ata",
816
+ "refund_blinding",
817
+ "refund_pubkey",
818
+ "retry_request_id",
819
+ "risk_quote",
820
+ "route_retry_attempts",
821
+ "sender",
822
+ "slippage_bps",
823
+ "swap_max_retries"
824
+ ];
825
+ var UTF8 = new TextEncoder();
826
+ function compareKeysBytewise(a, b) {
827
+ if (a === b) return 0;
828
+ const ab = UTF8.encode(a);
829
+ const bb = UTF8.encode(b);
830
+ const shared = Math.min(ab.length, bb.length);
831
+ for (let i = 0; i < shared; i++) {
832
+ if (ab[i] !== bb[i]) return ab[i] - bb[i];
833
+ }
834
+ return ab.length - bb.length;
835
+ }
836
+ function canonicalJson(value) {
837
+ if (value === null || value === void 0) return "null";
838
+ if (typeof value === "boolean") return value ? "true" : "false";
839
+ if (typeof value === "number") {
840
+ if (!Number.isFinite(value) || !Number.isInteger(value) || !Number.isSafeInteger(value)) {
841
+ throw new Error(
842
+ `canonicalJson: refusing to sign the number ${String(value)}. The relay renders numbers with serde_json, which spells non-integer, non-finite and very large values differently from JavaScript, so the two digests would differ and the request would be rejected with an unexplained 401. Send it as a decimal string instead, which is how every amount in this schema travels.`
843
+ );
844
+ }
845
+ return String(value);
846
+ }
847
+ if (typeof value === "bigint") return value.toString();
848
+ if (typeof value === "string") return JSON.stringify(value);
849
+ if (Array.isArray(value)) return "[" + value.map(canonicalJson).join(",") + "]";
850
+ if (typeof value === "object") {
851
+ const obj = value;
852
+ const keys = Object.keys(obj).sort(compareKeysBytewise);
853
+ return "{" + keys.map((k) => {
854
+ const v = obj[k];
855
+ if (typeof v === "function" || typeof v === "symbol") {
856
+ throw new Error(
857
+ `canonicalJson: the key "${k}" holds a ${typeof v}, which has no JSON representation. JSON.stringify would drop it from the wire body while it stayed in the signed view, so the relay's digest could never match this one.`
858
+ );
859
+ }
860
+ return `${JSON.stringify(k)}:${canonicalJson(v)}`;
861
+ }).join(",") + "}";
862
+ }
863
+ throw new Error(`canonicalJson: unsupported value of type ${typeof value}`);
864
+ }
865
+ var FIELDS_WITHOUT_NULL_ENCODING = {
866
+ slippage_bps: "500 (`default_slippage_bps` in api/transact_swap.rs)"
867
+ };
868
+ function buildAuthRequest(body, sender, fields) {
869
+ const out = {};
870
+ for (const k of fields) {
871
+ if (k === "sender") {
872
+ out[k] = sender;
873
+ continue;
874
+ }
875
+ const value = body[k];
876
+ if (value === void 0 || value === null) {
877
+ const relayDefault = FIELDS_WITHOUT_NULL_ENCODING[k];
878
+ if (relayDefault) {
879
+ throw new Error(
880
+ `Relay request auth: \`${k}\` must be present in the body. It is the one field in this schema that is not optional on the relay side: when it is missing the relay signs its default, ${relayDefault}, while this request would sign null. The two digests differ and the relay answers 401 "Relay request signature does not match the exact request", which points at nothing. Set \`${k}\` explicitly, to the same value the body will carry.`
881
+ );
882
+ }
883
+ out[k] = null;
884
+ continue;
885
+ }
886
+ out[k] = value;
887
+ }
888
+ return out;
889
+ }
890
+ function buildRequestAuthMessage(endpoint, programId, issuedAt, nonce, request) {
891
+ const digest = sha256Hex(canonicalJson(request));
892
+ return new TextEncoder().encode(
893
+ `${REQUEST_AUTH_DOMAIN}
894
+ ${endpoint}
895
+ ${programId.toBase58()}
896
+ ${nonce}
897
+ ${issuedAt}
898
+ ${digest}`
899
+ );
900
+ }
901
+ function buildRelayAuthPreimage(endpoint, programId, body, sender, nowSeconds, fields = TRANSACT_AUTH_FIELDS) {
902
+ const senderB58 = sender.toBase58();
903
+ const issuedAt = String(nowSeconds ?? Math.floor(Date.now() / 1e3));
904
+ const nonce = randomNonceUuid();
905
+ const request = buildAuthRequest({ ...body, sender: senderB58 }, senderB58, fields);
906
+ const message = buildRequestAuthMessage(endpoint, programId, issuedAt, nonce, request);
907
+ return { sender: senderB58, auth_issued_at: issuedAt, auth_nonce: nonce, message };
908
+ }
909
+ function signRelayRequest(endpoint, programId, body, signer, nowSeconds, fields = TRANSACT_AUTH_FIELDS) {
910
+ const preimage = buildRelayAuthPreimage(
911
+ endpoint,
912
+ programId,
913
+ body,
914
+ signer.publicKey,
915
+ nowSeconds,
916
+ fields
917
+ );
918
+ const signature = nacl2.sign.detached(preimage.message, signer.secretKey);
919
+ return {
920
+ sender: preimage.sender,
921
+ auth_issued_at: preimage.auth_issued_at,
922
+ auth_nonce: preimage.auth_nonce,
923
+ auth_signature: Buffer.from(signature).toString("base64")
924
+ };
925
+ }
926
+ function buildAuthTransactionMessage(sender, digest) {
927
+ if (digest.length !== 32) throw new Error(`auth transaction digest must be 32 bytes (got ${digest.length})`);
928
+ const tx = new Transaction();
929
+ tx.add(SystemProgram.transfer({ fromPubkey: sender, toPubkey: sender, lamports: 0 }));
930
+ tx.feePayer = sender;
931
+ tx.recentBlockhash = new PublicKey3(digest).toBase58();
932
+ return tx;
933
+ }
934
+ function serializeAuthTransactionMessage(sender, digest) {
935
+ return Uint8Array.from(buildAuthTransactionMessage(sender, digest).compileMessage().serialize());
936
+ }
937
+ async function signRelayAuthPayload(signer, payload) {
938
+ if (signer instanceof Keypair) {
939
+ return { signature: nacl2.sign.detached(payload, signer.secretKey), mode: "message" };
940
+ }
941
+ if (signer.signMessage) {
942
+ const signature = await signer.signMessage(payload);
943
+ assertDetachedSignature(signature, "signMessage");
944
+ return { signature, mode: "message" };
945
+ }
946
+ if (signer.signAuthTransaction) {
947
+ const digest = sha256(payload);
948
+ const signed = await signer.signAuthTransaction(buildAuthTransactionMessage(signer.walletPublicKey, digest));
949
+ const own = signed.signatures.find((s) => s.publicKey.equals(signer.walletPublicKey))?.signature;
950
+ if (!own) {
951
+ throw new Error(
952
+ "The wallet returned no signature for the Cloak auth transaction. Nothing was submitted."
953
+ );
954
+ }
955
+ const signature = Uint8Array.from(own);
956
+ assertDetachedSignature(signature, "signTransaction");
957
+ return { signature, mode: "transaction" };
958
+ }
959
+ throw new Error("Relay auth signer has neither signMessage nor signAuthTransaction.");
960
+ }
961
+ function assertDetachedSignature(signature, method) {
962
+ if (!(signature instanceof Uint8Array) || signature.length !== 64) {
963
+ throw new Error(
964
+ `The wallet's ${method} did not return a 64-byte ed25519 detached signature (got ${signature instanceof Uint8Array ? `${signature.length} bytes` : typeof signature}). Cloak signs with a raw detached signature over the bytes it hands the wallet; adapters that wrap or re-encode the result cannot be used here.`
965
+ );
966
+ }
967
+ }
968
+ function assertApprovalWithinFreshnessWindow(elapsedMs, maxAgeSeconds = REQUEST_AUTH_MAX_AGE_SECONDS) {
969
+ const elapsedSeconds = elapsedMs / 1e3;
970
+ const budget = maxAgeSeconds - RELAY_AUTH_APPROVAL_MARGIN_SECONDS;
971
+ if (elapsedSeconds <= budget) return;
972
+ throw new Error(
973
+ `The wallet approval took ${Math.round(elapsedSeconds)} seconds, and Cloak requests stay valid for ${maxAgeSeconds} seconds from the moment they are signed (${RELAY_AUTH_APPROVAL_MARGIN_SECONDS} of those are held back for the request to reach the network). The timestamp is inside the signature, so it cannot be refreshed without your approval again. NOTHING WAS SUBMITTED and no funds moved. Start the same operation again and approve the prompt when it appears.`
974
+ );
975
+ }
976
+ function explainRelayAuthRejection(responseText) {
977
+ const text = String(responseText);
978
+ const has = (needle) => text.includes(needle);
979
+ if (has("issued too far in the future")) {
980
+ return `This computer's clock is more than ${REQUEST_AUTH_MAX_FUTURE_SKEW_SECONDS} seconds ahead of the network's, so the request looks like it was signed in the future and is refused before anything else happens. Turn on automatic date and time (or resynchronise the clock) and try again. Nothing was submitted.`;
981
+ }
982
+ if (has("signature expired")) {
983
+ return `The request was signed more than ${REQUEST_AUTH_MAX_AGE_SECONDS} seconds before it arrived, usually because a wallet approval was left waiting, or because this computer's clock is running behind. The signed timestamp cannot be refreshed on its own. Start the operation again. Nothing was submitted.`;
984
+ }
985
+ if (has("does not match the exact request")) {
986
+ return `The request body changed after it was signed, so the relay's digest and this one disagree. Sign the exact body that is POSTed, and re-POST a retry byte for byte rather than rebuilding it. If this is a hand-built swap call, check that every field in TRANSACT_SWAP_AUTH_FIELDS is present, including \`slippage_bps\`.`;
987
+ }
988
+ if (has("has no registered viewing key")) {
989
+ return `The signature was accepted, so this is not a signing problem: the wallet that signed has no viewing key registered with Cloak yet. Register one first, or let the SDK do it by leaving \`enforceViewingKeyRegistration\` on and supplying the viewing key.`;
990
+ }
991
+ if (has("Authenticated sender is required") || has("auth_issued_at is required") || has("auth_nonce is required") || has("auth_signature is required")) {
992
+ return `The request reached the relay without its authentication fields. Pass \`depositorKeypair\`, or \`signMessage\` together with \`walletPublicKey\`, so the request can be signed.`;
993
+ }
994
+ if (has("auth_nonce must be a canonical UUID")) {
995
+ return `\`auth_nonce\` must be a canonical lowercase UUID. Reuse the one from the preimage.`;
996
+ }
997
+ if (has("Relay request signature must be exactly 64 bytes") || has("Invalid relay request signature encoding")) {
998
+ return `\`auth_signature\` must be the raw 64-byte ed25519 detached signature, base64 encoded. Wallet adapters that wrap or re-encode what \`signMessage\` returns cannot be used here.`;
999
+ }
1000
+ return null;
1001
+ }
1002
+ async function buildRelayAuthFields(endpoint, programId, body, signers, fields = TRANSACT_AUTH_FIELDS) {
1003
+ if (signers.depositorKeypair) {
1004
+ return signRelayRequest(endpoint, programId, body, signers.depositorKeypair, void 0, fields);
1005
+ }
1006
+ const wallet = signers.relayAuthSigner;
1007
+ if (!wallet) return null;
1008
+ const preimage = buildRelayAuthPreimage(
1009
+ endpoint,
1010
+ programId,
1011
+ body,
1012
+ wallet.walletPublicKey,
1013
+ void 0,
1014
+ fields
1015
+ );
1016
+ const approvalStartedMs = Date.now();
1017
+ const { signature, mode } = await signRelayAuthPayload(wallet, preimage.message);
1018
+ assertApprovalWithinFreshnessWindow(Date.now() - approvalStartedMs);
1019
+ return {
1020
+ sender: preimage.sender,
1021
+ auth_issued_at: preimage.auth_issued_at,
1022
+ auth_nonce: preimage.auth_nonce,
1023
+ auth_signature: Buffer.from(signature).toString("base64"),
1024
+ ...mode === "transaction" ? { auth_mode: mode } : {}
1025
+ };
1026
+ }
1027
+ var REQUEST_AUTH_BATCH_DOMAIN = "CLOAK_RELAY_BATCH_AUTH_V1";
1028
+ var REQUEST_AUTH_BATCH_MAX_AGE_SECONDS = 600;
1029
+ var RELAY_BATCH_AUTH_MAX_ITEMS = 64;
1030
+ function relayRequestDigestHex(preimage) {
1031
+ return bytesToHex2(sha256(preimage.message));
1032
+ }
1033
+ function buildRelayBatchAuthMessage(programId, issuedAt, digests) {
1034
+ const lines = [REQUEST_AUTH_BATCH_DOMAIN, programId.toBase58(), issuedAt, String(digests.length)];
1035
+ return new TextEncoder().encode([...lines, ...digests].join("\n"));
1036
+ }
1037
+
769
1038
  // src/scanning/compliance-keys.ts
770
1039
  var X25519_KEY_LENGTH = 32;
771
1040
  var DIVERSIFIER_LENGTH = 11;
@@ -803,7 +1072,7 @@ function deriveViewingKeyFromNk(nk) {
803
1072
  preimage.set(nk, CHAIN_NOTE_VK_DOMAIN.length);
804
1073
  const derived = blake32(preimage);
805
1074
  const privateKey = clampX25519Secret(derived);
806
- const publicKey = nacl2.scalarMult.base(privateKey);
1075
+ const publicKey = nacl3.scalarMult.base(privateKey);
807
1076
  return { privateKey, publicKey };
808
1077
  }
809
1078
  function deriveDiversifier(nk, commitmentHex, outputIndex) {
@@ -838,7 +1107,7 @@ function deriveDiversifiedViewingKey(nk, diversifier) {
838
1107
  preimage.set(diversifier, SK_D_DOMAIN.length + nk.length);
839
1108
  const derived = blake32(preimage);
840
1109
  const privateKey = clampX25519Secret(derived);
841
- const publicKey = nacl2.scalarMult.base(privateKey);
1110
+ const publicKey = nacl3.scalarMult.base(privateKey);
842
1111
  return { privateKey, publicKey };
843
1112
  }
844
1113
  function deriveViewingKeyFromSpendKey(skSpend) {
@@ -901,19 +1170,19 @@ async function deriveUserCompliancePublicKey(masterCompliancePublicKey, userPubl
901
1170
  masterCompliancePublicKey,
902
1171
  userPublicKey
903
1172
  );
904
- return nacl2.scalarMult(factor, masterCompliancePublicKey);
1173
+ return nacl3.scalarMult(factor, masterCompliancePublicKey);
905
1174
  }
906
- function bytesToHex2(bytes) {
1175
+ function bytesToHex3(bytes) {
907
1176
  return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
908
1177
  }
909
1178
  function computeViewingKeyIdentifier(userPubkey, nkHex) {
910
1179
  const preimage = new TextEncoder().encode(`${userPubkey.toBase58()}${nkHex}`);
911
- return bytesToHex2(sha256(preimage));
1180
+ return bytesToHex3(sha2562(preimage));
912
1181
  }
913
1182
  var SIGN_IN_MESSAGE = "Cloak: Sign in\n\nSign this message to securely access your Cloak account.\nThis does NOT authorize any transaction or spend any funds.";
914
1183
  async function registerViewingKey(relayUrl, userPubkey, nk, signMessage) {
915
1184
  assert32Bytes(nk, "nk");
916
- const nkHex = bytesToHex2(nk);
1185
+ const nkHex = bytesToHex3(nk);
917
1186
  const keyId = computeViewingKeyIdentifier(userPubkey, nkHex);
918
1187
  const challengeResponse = await relayFetch(`${relayUrl}/viewing-key/challenge`, {
919
1188
  method: "POST",
@@ -934,8 +1203,11 @@ async function registerViewingKey(relayUrl, userPubkey, nk, signMessage) {
934
1203
  nkHex
935
1204
  );
936
1205
  const messageBytes = new TextEncoder().encode(challenge.message);
937
- const signatureBytes = await signMessage(messageBytes);
938
- const signature = Buffer.from(signatureBytes).toString("base64");
1206
+ const signed = await signRelayAuthPayload(
1207
+ typeof signMessage === "function" ? { walletPublicKey: userPubkey, signMessage } : signMessage,
1208
+ messageBytes
1209
+ );
1210
+ const signature = Buffer.from(signed.signature).toString("base64");
939
1211
  const response = await relayFetch(`${relayUrl}/viewing-key/register`, {
940
1212
  method: "POST",
941
1213
  headers: { "Content-Type": "application/json" },
@@ -944,7 +1216,8 @@ async function registerViewingKey(relayUrl, userPubkey, nk, signMessage) {
944
1216
  viewing_key: nkHex,
945
1217
  nonce: challenge.nonce,
946
1218
  identifier: keyId,
947
- signature
1219
+ signature,
1220
+ ...signed.mode === "transaction" ? { auth_mode: signed.mode } : {}
948
1221
  })
949
1222
  });
950
1223
  if (!response.ok) {
@@ -953,7 +1226,7 @@ async function registerViewingKey(relayUrl, userPubkey, nk, signMessage) {
953
1226
  }
954
1227
 
955
1228
  // src/scanning/metadata-encryption.ts
956
- import nacl3 from "tweetnacl";
1229
+ import nacl4 from "tweetnacl";
957
1230
  var AES_GCM_NONCE_LENGTH = 12;
958
1231
  var AES_GCM_TAG_LENGTH = 16;
959
1232
  var X25519_KEY_LENGTH2 = 32;
@@ -1050,8 +1323,8 @@ async function aesGcmDecrypt(ciphertext, keyBytes, nonce) {
1050
1323
  }
1051
1324
  async function encryptForRecipient(plaintext, recipientPublicKey) {
1052
1325
  assert32Bytes2(recipientPublicKey, "recipientPublicKey");
1053
- const ephemeral = nacl3.box.keyPair();
1054
- const sharedSecret = nacl3.scalarMult(ephemeral.secretKey, recipientPublicKey);
1326
+ const ephemeral = nacl4.box.keyPair();
1327
+ const sharedSecret = nacl4.scalarMult(ephemeral.secretKey, recipientPublicKey);
1055
1328
  const nonce = randomBytes(AES_GCM_NONCE_LENGTH);
1056
1329
  const ciphertext = await aesGcmEncrypt(plaintext, sharedSecret, nonce);
1057
1330
  return {
@@ -1110,7 +1383,7 @@ async function encryptTransactionMetadataBundle(metadata, viewingKeyPrivate, use
1110
1383
  const plaintext = new TextEncoder().encode(JSON.stringify(metadata));
1111
1384
  const basePoint = new Uint8Array(32);
1112
1385
  basePoint[0] = 9;
1113
- const viewingKeyPublic = nacl3.scalarMult(viewingKeyPrivate, basePoint);
1386
+ const viewingKeyPublic = nacl4.scalarMult(viewingKeyPrivate, basePoint);
1114
1387
  const [userPayload, compliancePayload] = await Promise.all([
1115
1388
  encryptForRecipient(plaintext, viewingKeyPublic),
1116
1389
  encryptForRecipient(plaintext, viewingKeyPublic)
@@ -1129,7 +1402,7 @@ async function decryptTransactionMetadata(encrypted, viewKeySecret) {
1129
1402
  const payload = decodePayload(encrypted);
1130
1403
  const ephemeralPk = hexToBytes(payload.ephemeral_pk);
1131
1404
  assert32Bytes2(ephemeralPk, "ephemeral public key");
1132
- const sharedSecret = nacl3.scalarMult(viewKeySecret, ephemeralPk);
1405
+ const sharedSecret = nacl4.scalarMult(viewKeySecret, ephemeralPk);
1133
1406
  return decryptWithSharedSecret(payload, sharedSecret);
1134
1407
  }
1135
1408
  async function decryptComplianceMetadataWithMasterKey(encrypted, masterCompliancePrivateKey, masterCompliancePublicKey, userPublicKey) {
@@ -1142,8 +1415,8 @@ async function decryptComplianceMetadataWithMasterKey(encrypted, masterComplianc
1142
1415
  masterCompliancePublicKey,
1143
1416
  userPublicKey
1144
1417
  );
1145
- const scaledEphemeral = nacl3.scalarMult(userFactor, ephemeralPk);
1146
- const sharedSecret = nacl3.scalarMult(masterCompliancePrivateKey, scaledEphemeral);
1418
+ const scaledEphemeral = nacl4.scalarMult(userFactor, ephemeralPk);
1419
+ const sharedSecret = nacl4.scalarMult(masterCompliancePrivateKey, scaledEphemeral);
1147
1420
  return decryptWithSharedSecret(payload, sharedSecret);
1148
1421
  }
1149
1422
 
@@ -2073,22 +2346,22 @@ function formatErrorForLogging(error) {
2073
2346
  }
2074
2347
 
2075
2348
  // src/program/pda.ts
2076
- import { PublicKey as PublicKey3 } from "@solana/web3.js";
2349
+ import { PublicKey as PublicKey4 } from "@solana/web3.js";
2077
2350
  function getShieldPoolPDAs(programId, mint = NATIVE_SOL_MINT) {
2078
2351
  const pid = programId || CLOAK_PROGRAM_ID;
2079
- const [pool] = PublicKey3.findProgramAddressSync(
2352
+ const [pool] = PublicKey4.findProgramAddressSync(
2080
2353
  [Buffer.from("pool"), mint.toBuffer()],
2081
2354
  pid
2082
2355
  );
2083
- const [merkleTree] = PublicKey3.findProgramAddressSync(
2356
+ const [merkleTree] = PublicKey4.findProgramAddressSync(
2084
2357
  [Buffer.from("merkle_tree"), mint.toBuffer()],
2085
2358
  pid
2086
2359
  );
2087
- const [treasury] = PublicKey3.findProgramAddressSync(
2360
+ const [treasury] = PublicKey4.findProgramAddressSync(
2088
2361
  [Buffer.from("treasury"), mint.toBuffer()],
2089
2362
  pid
2090
2363
  );
2091
- const [vaultAuthority] = PublicKey3.findProgramAddressSync(
2364
+ const [vaultAuthority] = PublicKey4.findProgramAddressSync(
2092
2365
  [Buffer.from("vault_authority"), mint.toBuffer()],
2093
2366
  pid
2094
2367
  );
@@ -2104,7 +2377,7 @@ function getNullifierPDA(poolPubkey, nullifier, programId) {
2104
2377
  if (nullifier.length !== 32) {
2105
2378
  throw new Error(`Nullifier must be 32 bytes, got ${nullifier.length}`);
2106
2379
  }
2107
- return PublicKey3.findProgramAddressSync(
2380
+ return PublicKey4.findProgramAddressSync(
2108
2381
  [Buffer.from("nullifier"), poolPubkey.toBuffer(), Buffer.from(nullifier)],
2109
2382
  pid
2110
2383
  );
@@ -2114,25 +2387,25 @@ function getSwapStatePDA(poolPubkey, nullifier, programId) {
2114
2387
  if (nullifier.length !== 32) {
2115
2388
  throw new Error(`Nullifier must be 32 bytes, got ${nullifier.length}`);
2116
2389
  }
2117
- return PublicKey3.findProgramAddressSync(
2390
+ return PublicKey4.findProgramAddressSync(
2118
2391
  [Buffer.from("swap_state"), poolPubkey.toBuffer(), Buffer.from(nullifier)],
2119
2392
  pid
2120
2393
  );
2121
2394
  }
2122
2395
  function getPoolAuthorityConfigPDA(mint = NATIVE_SOL_MINT, programId) {
2123
2396
  const pid = programId || CLOAK_PROGRAM_ID;
2124
- return PublicKey3.findProgramAddressSync(
2397
+ return PublicKey4.findProgramAddressSync(
2125
2398
  [Buffer.from("pool_authority"), mint.toBuffer()],
2126
2399
  pid
2127
2400
  );
2128
2401
  }
2129
2402
  function getDeliveryRegistryPDA(programId) {
2130
2403
  const pid = programId || CLOAK_PROGRAM_ID;
2131
- return PublicKey3.findProgramAddressSync([Buffer.from("cloak_delivery_registry")], pid)[0];
2404
+ return PublicKey4.findProgramAddressSync([Buffer.from("cloak_delivery_registry")], pid)[0];
2132
2405
  }
2133
2406
  function getChainNoteRegistryPDA(programId) {
2134
2407
  const pid = programId || CLOAK_PROGRAM_ID;
2135
- return PublicKey3.findProgramAddressSync([Buffer.from("cloak_chain_note_registry")], pid)[0];
2408
+ return PublicKey4.findProgramAddressSync([Buffer.from("cloak_chain_note_registry")], pid)[0];
2136
2409
  }
2137
2410
 
2138
2411
  // src/flows/verify-utxos.ts
@@ -2150,8 +2423,8 @@ async function verifyUtxos(utxos, connection, programId, commitment = "confirmed
2150
2423
  const nullifierHex = nullifierBig.toString(16).padStart(64, "0");
2151
2424
  const nullifierBytes = Buffer.from(nullifierHex, "hex");
2152
2425
  const { pool } = getShieldPoolPDAs(programId, utxo.mintAddress);
2153
- const [pda] = getNullifierPDA(pool, nullifierBytes, programId);
2154
- checkable.push({ utxo, pda });
2426
+ const [pda2] = getNullifierPDA(pool, nullifierBytes, programId);
2427
+ checkable.push({ utxo, pda: pda2 });
2155
2428
  } catch {
2156
2429
  skipped.push(utxo);
2157
2430
  }
@@ -2221,8 +2494,8 @@ function deriveInputNullifierPdas(programId, mint, inputNullifiers) {
2221
2494
  const pdas = [];
2222
2495
  for (const nullifier of inputNullifiers) {
2223
2496
  if (nullifier === BigInt(0)) continue;
2224
- const [pda] = getNullifierPDA(pool, bigintToBytes324(nullifier), programId);
2225
- pdas.push(pda);
2497
+ const [pda2] = getNullifierPDA(pool, bigintToBytes324(nullifier), programId);
2498
+ pdas.push(pda2);
2226
2499
  }
2227
2500
  return pdas;
2228
2501
  }
@@ -2463,7 +2736,8 @@ function truncate(str, len = 20) {
2463
2736
  var sdkLogger = createLogger("cloak::sdk");
2464
2737
 
2465
2738
  // src/relay/relay-service.ts
2466
- import { sha256 as sha2562 } from "@noble/hashes/sha2";
2739
+ import { sha256 as sha2563 } from "@noble/hashes/sha2";
2740
+ import { PublicKey as PublicKey5 } from "@solana/web3.js";
2467
2741
  var RelayService = class {
2468
2742
  /**
2469
2743
  * Create a new Relay Service client
@@ -2779,8 +3053,11 @@ var RelayService = class {
2779
3053
  userPubkey,
2780
3054
  viewingKey
2781
3055
  );
2782
- const signatureBytes = await signMessage(new TextEncoder().encode(challenge.message));
2783
- const signature = this.bytesToBase64(signatureBytes);
3056
+ const signed = await signRelayAuthPayload(
3057
+ typeof signMessage === "function" ? { walletPublicKey: new PublicKey5(userPubkey), signMessage } : signMessage,
3058
+ new TextEncoder().encode(challenge.message)
3059
+ );
3060
+ const signature = this.bytesToBase64(signed.signature);
2784
3061
  const response = await relayFetch(`${this.baseUrl}/viewing-key/register`, {
2785
3062
  method: "POST",
2786
3063
  headers: { "Content-Type": "application/json" },
@@ -2789,7 +3066,8 @@ var RelayService = class {
2789
3066
  viewing_key: viewingKey,
2790
3067
  nonce: challenge.nonce,
2791
3068
  identifier,
2792
- signature
3069
+ signature,
3070
+ ...signed.mode === "transaction" ? { auth_mode: signed.mode } : {}
2793
3071
  })
2794
3072
  });
2795
3073
  if (!response.ok) {
@@ -2827,7 +3105,7 @@ var RelayService = class {
2827
3105
  }
2828
3106
  computeViewingKeyIdentifier(userPubkey, viewingKeyHex) {
2829
3107
  const preimage = new TextEncoder().encode(`${userPubkey}${viewingKeyHex}`);
2830
- const hash = sha2562(preimage);
3108
+ const hash = sha2563(preimage);
2831
3109
  return Array.from(hash).map((b) => b.toString(16).padStart(2, "0")).join("");
2832
3110
  }
2833
3111
  };
@@ -2945,10 +3223,10 @@ function encodeNoteSimple(note) {
2945
3223
 
2946
3224
  // src/wallet/adapter.ts
2947
3225
  import {
2948
- Keypair
3226
+ Keypair as Keypair2
2949
3227
  } from "@solana/web3.js";
2950
3228
  function validateWalletConnected(wallet) {
2951
- if (wallet instanceof Keypair) {
3229
+ if (wallet instanceof Keypair2) {
2952
3230
  return;
2953
3231
  }
2954
3232
  if (!wallet.publicKey) {
@@ -2960,7 +3238,7 @@ function validateWalletConnected(wallet) {
2960
3238
  }
2961
3239
  }
2962
3240
  function getPublicKey(wallet) {
2963
- if (wallet instanceof Keypair) {
3241
+ if (wallet instanceof Keypair2) {
2964
3242
  return wallet.publicKey;
2965
3243
  }
2966
3244
  if (!wallet.publicKey) {
@@ -2974,7 +3252,7 @@ function getPublicKey(wallet) {
2974
3252
  }
2975
3253
  async function sendTransaction(transaction, wallet, connection, options) {
2976
3254
  assertAllowedRpcConnection(connection);
2977
- if (wallet instanceof Keypair) {
3255
+ if (wallet instanceof Keypair2) {
2978
3256
  return await connection.sendTransaction(transaction, [wallet], options);
2979
3257
  }
2980
3258
  if (wallet.sendTransaction) {
@@ -2991,7 +3269,7 @@ async function sendTransaction(transaction, wallet, connection, options) {
2991
3269
  }
2992
3270
  }
2993
3271
  async function signTransaction(transaction, wallet) {
2994
- if (wallet instanceof Keypair) {
3272
+ if (wallet instanceof Keypair2) {
2995
3273
  transaction.sign(wallet);
2996
3274
  return transaction;
2997
3275
  }
@@ -3020,8 +3298,8 @@ function keypairToAdapter(keypair) {
3020
3298
 
3021
3299
  // src/program/instructions.ts
3022
3300
  import {
3023
- PublicKey as PublicKey5,
3024
- SystemProgram,
3301
+ PublicKey as PublicKey7,
3302
+ SystemProgram as SystemProgram2,
3025
3303
  TransactionInstruction
3026
3304
  } from "@solana/web3.js";
3027
3305
  function createDepositInstruction(params) {
@@ -3053,7 +3331,7 @@ function createDepositInstruction(params) {
3053
3331
  // Account 1: Pool (writable) - receives SOL
3054
3332
  { pubkey: params.pool, isSigner: false, isWritable: true },
3055
3333
  // Account 2: System Program (readonly) - for transfers
3056
- { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
3334
+ { pubkey: SystemProgram2.programId, isSigner: false, isWritable: false },
3057
3335
  // Account 3: Merkle Tree (writable) - stores on-chain Merkle tree
3058
3336
  { pubkey: params.merkleTree, isSigner: false, isWritable: true }
3059
3337
  ],
@@ -3061,16 +3339,16 @@ function createDepositInstruction(params) {
3061
3339
  });
3062
3340
  }
3063
3341
  function validateDepositParams(params) {
3064
- if (!(params.programId instanceof PublicKey5)) {
3342
+ if (!(params.programId instanceof PublicKey7)) {
3065
3343
  throw new Error("programId must be a PublicKey");
3066
3344
  }
3067
- if (!(params.payer instanceof PublicKey5)) {
3345
+ if (!(params.payer instanceof PublicKey7)) {
3068
3346
  throw new Error("payer must be a PublicKey");
3069
3347
  }
3070
- if (!(params.pool instanceof PublicKey5)) {
3348
+ if (!(params.pool instanceof PublicKey7)) {
3071
3349
  throw new Error("pool must be a PublicKey");
3072
3350
  }
3073
- if (!(params.merkleTree instanceof PublicKey5)) {
3351
+ if (!(params.merkleTree instanceof PublicKey7)) {
3074
3352
  throw new Error("merkleTree must be a PublicKey");
3075
3353
  }
3076
3354
  if (typeof params.amount !== "number" || params.amount <= 0) {
@@ -3410,7 +3688,7 @@ async function buildMerkleTree(commitments, height = MERKLE_TREE_HEIGHT2) {
3410
3688
  }
3411
3689
 
3412
3690
  // src/relay/client.ts
3413
- import { PublicKey as PublicKey6 } from "@solana/web3.js";
3691
+ import { PublicKey as PublicKey8 } from "@solana/web3.js";
3414
3692
  import _bs58 from "bs58";
3415
3693
 
3416
3694
  // src/notes/refund-leaf.ts
@@ -3527,7 +3805,7 @@ var CLOSE_SWAP_STATE_TAG = 13;
3527
3805
  var CLOSE_SWAP_STATE_PHASE1_PAYLOAD_LEN = 40;
3528
3806
  var CLOSE_SWAP_STATE_PHASE1_WIRE_LEN = 1 + CLOSE_SWAP_STATE_PHASE1_PAYLOAD_LEN;
3529
3807
  var CLOSE_SWAP_STATE_COMMITMENT_END = 1 + 32;
3530
- var NATIVE_SOL_MINT2 = new PublicKey6("So11111111111111111111111111111111111111112");
3808
+ var NATIVE_SOL_MINT2 = new PublicKey8("So11111111111111111111111111111111111111112");
3531
3809
  var PROOF_LEN = 256;
3532
3810
  var PUBLIC_INPUTS_LEN = 264;
3533
3811
  var COMMITMENTS_OFFSET = 168;
@@ -3543,7 +3821,7 @@ async function fetchWithRetry(url, options = {}) {
3543
3821
  const {
3544
3822
  timeoutMs = DEFAULT_TIMEOUT_MS,
3545
3823
  maxRetries = DEFAULT_MAX_RETRIES,
3546
- retryDelayMs = 1e3
3824
+ retryDelayMs: retryDelayMs2 = 1e3
3547
3825
  } = options;
3548
3826
  assertAllowedRelayOrigin(url);
3549
3827
  let lastError = null;
@@ -3567,7 +3845,7 @@ async function fetchWithRetry(url, options = {}) {
3567
3845
  `Relay request failed after ${attempt + 1} attempts: ${lastError.message}`
3568
3846
  );
3569
3847
  }
3570
- const delay = retryDelayMs * Math.pow(2, attempt) + Math.random() * 500;
3848
+ const delay = retryDelayMs2 * Math.pow(2, attempt) + Math.random() * 500;
3571
3849
  await new Promise((resolve) => setTimeout(resolve, delay));
3572
3850
  }
3573
3851
  }
@@ -3817,11 +4095,11 @@ async function preflightCheck(relayUrl, rootHex) {
3817
4095
 
3818
4096
  // src/flows/transact.ts
3819
4097
  import {
3820
- PublicKey as PublicKey8,
3821
- Transaction as Transaction2,
4098
+ PublicKey as PublicKey10,
4099
+ Transaction as Transaction3,
3822
4100
  TransactionInstruction as TransactionInstruction2,
3823
4101
  sendAndConfirmTransaction,
3824
- SystemProgram as SystemProgram2,
4102
+ SystemProgram as SystemProgram3,
3825
4103
  ComputeBudgetProgram,
3826
4104
  SYSVAR_INSTRUCTIONS_PUBKEY,
3827
4105
  SYSVAR_SLOT_HASHES_PUBKEY,
@@ -4060,7 +4338,7 @@ function chainNoteFromBase64(base64) {
4060
4338
  }
4061
4339
 
4062
4340
  // src/notes/delivery-note.ts
4063
- import nacl4 from "tweetnacl";
4341
+ import nacl5 from "tweetnacl";
4064
4342
  var RECIPIENT_DELIVERY_EPHEMERAL_PK_LEN = 32;
4065
4343
  var RECIPIENT_DELIVERY_NONCE_LEN = 24;
4066
4344
  var RECIPIENT_DELIVERY_PLAINTEXT_LEN = 40;
@@ -4111,10 +4389,10 @@ function encodeRecipientDeliveryNote(note, recipientViewingPublicKey) {
4111
4389
  const plaintext = new Uint8Array(RECIPIENT_DELIVERY_PLAINTEXT_LEN);
4112
4390
  writeU64LE2(plaintext, 0, note.amount);
4113
4391
  writeU256BE2(plaintext, 8, note.blinding);
4114
- const ephemeral = nacl4.box.keyPair();
4115
- const shared = nacl4.box.before(recipientViewingPublicKey, ephemeral.secretKey);
4116
- const nonce = nacl4.randomBytes(nacl4.secretbox.nonceLength);
4117
- const ciphertext = nacl4.secretbox(plaintext, nonce, shared);
4392
+ const ephemeral = nacl5.box.keyPair();
4393
+ const shared = nacl5.box.before(recipientViewingPublicKey, ephemeral.secretKey);
4394
+ const nonce = nacl5.randomBytes(nacl5.secretbox.nonceLength);
4395
+ const ciphertext = nacl5.secretbox(plaintext, nonce, shared);
4118
4396
  if (ciphertext.length !== RECIPIENT_DELIVERY_CIPHERTEXT_LEN) {
4119
4397
  throw new Error(
4120
4398
  `delivery ciphertext must be ${RECIPIENT_DELIVERY_CIPHERTEXT_LEN} bytes, got ${ciphertext.length}`
@@ -4140,8 +4418,8 @@ function openRecipientDeliveryNote(envelope, viewingSecretKey) {
4140
4418
  const ciphertext = envelope.slice(
4141
4419
  RECIPIENT_DELIVERY_EPHEMERAL_PK_LEN + RECIPIENT_DELIVERY_NONCE_LEN
4142
4420
  );
4143
- const shared = nacl4.box.before(ephemeralPk, viewingSecretKey);
4144
- const plaintext = nacl4.secretbox.open(ciphertext, nonce, shared);
4421
+ const shared = nacl5.box.before(ephemeralPk, viewingSecretKey);
4422
+ const plaintext = nacl5.secretbox.open(ciphertext, nonce, shared);
4145
4423
  if (!plaintext || plaintext.length !== RECIPIENT_DELIVERY_PLAINTEXT_LEN) return null;
4146
4424
  return { amount: readU64LE2(plaintext, 0), blinding: readU256BE2(plaintext, 8) };
4147
4425
  } catch {
@@ -4199,7 +4477,7 @@ function parseDeliveryCarrierMemo(data) {
4199
4477
 
4200
4478
  // src/notes/swap-refund.ts
4201
4479
  import { blake3 as blake33 } from "@noble/hashes/blake3";
4202
- import { PublicKey as PublicKey7 } from "@solana/web3.js";
4480
+ import { PublicKey as PublicKey9 } from "@solana/web3.js";
4203
4481
  import _bs582 from "bs58";
4204
4482
  var bs582 = _bs582.default || _bs582;
4205
4483
  var REFUND_SEED_DOMAIN = new TextEncoder().encode("cloak_swap_refund_v1");
@@ -4344,7 +4622,7 @@ async function discoverSwapRefunds(connection, programId, viewingKeyNk, options
4344
4622
  const { limit = 0, untilSignature, batchSize = 50, onStatus } = options;
4345
4623
  const poolMint = options.poolMint ?? NATIVE_SOL_MINT;
4346
4624
  const programIdBase58 = programId.toBase58();
4347
- const [pool] = PublicKey7.findProgramAddressSync(
4625
+ const [pool] = PublicKey9.findProgramAddressSync(
4348
4626
  [Buffer.from("pool"), poolMint.toBuffer()],
4349
4627
  programId
4350
4628
  );
@@ -4395,7 +4673,7 @@ async function discoverSwapRefunds(connection, programId, viewingKeyNk, options
4395
4673
  if (data[0] !== TRANSACT_SWAP_TAG2 || data.length < MIN_TRANSACT_SWAP_LEN) continue;
4396
4674
  const nullifier = data.slice(NULLIFIER_0_OFFSET, NULLIFIER_0_OFFSET + 32);
4397
4675
  if (nullifier.length !== 32) continue;
4398
- const [swapState] = PublicKey7.findProgramAddressSync(
4676
+ const [swapState] = PublicKey9.findProgramAddressSync(
4399
4677
  [Buffer.from("swap_state"), pool.toBuffer(), Buffer.from(nullifier)],
4400
4678
  programId
4401
4679
  );
@@ -4622,226 +4900,6 @@ async function matchChangeNote(params) {
4622
4900
  import { buildPoseidon as buildPoseidon2 } from "circomlibjs";
4623
4901
  import nacl6 from "tweetnacl";
4624
4902
 
4625
- // src/relay/payload.ts
4626
- import { sha256 as sha2563 } from "@noble/hashes/sha2";
4627
- import { bytesToHex as bytesToHex3 } from "@noble/hashes/utils";
4628
- import nacl5 from "tweetnacl";
4629
- var REQUEST_AUTH_DOMAIN = "CLOAK_RELAY_REQUEST_AUTH_V1";
4630
- var REQUEST_AUTH_MAX_AGE_SECONDS = 300;
4631
- var REQUEST_AUTH_MAX_FUTURE_SKEW_SECONDS = 30;
4632
- var RELAY_AUTH_APPROVAL_MARGIN_SECONDS = 15;
4633
- function sha256Hex(text) {
4634
- return bytesToHex3(sha2563(new TextEncoder().encode(text)));
4635
- }
4636
- function randomNonceUuid() {
4637
- const webCrypto = globalThis?.crypto;
4638
- if (typeof webCrypto?.randomUUID === "function") return webCrypto.randomUUID();
4639
- const proc = globalThis?.process;
4640
- const nodeCrypto = typeof proc?.getBuiltinModule === "function" ? proc.getBuiltinModule("node:crypto") : void 0;
4641
- if (typeof nodeCrypto?.randomUUID === "function") return nodeCrypto.randomUUID();
4642
- const bytes = randomBytes(16);
4643
- bytes[6] = bytes[6] & 15 | 64;
4644
- bytes[8] = bytes[8] & 63 | 128;
4645
- const hex = bytesToHex3(bytes);
4646
- return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
4647
- }
4648
- var TRANSACT_AUTH_FIELDS = [
4649
- "encrypted_notes",
4650
- "max_fee",
4651
- "mint",
4652
- "proof_bytes",
4653
- "public_inputs",
4654
- "recipient",
4655
- "recipient_delivery_notes",
4656
- "risk_quote",
4657
- "sender"
4658
- ];
4659
- var TRANSACT_SWAP_AUTH_FIELDS = [
4660
- "close_timed_out",
4661
- "dexes",
4662
- "encrypted_notes",
4663
- "exclude_dexes",
4664
- "max_fee",
4665
- "min_output_amount",
4666
- "output_mint",
4667
- "proof_bytes",
4668
- "public_inputs",
4669
- "recipient",
4670
- "recipient_ata",
4671
- "refund_blinding",
4672
- "refund_pubkey",
4673
- "retry_request_id",
4674
- "risk_quote",
4675
- "route_retry_attempts",
4676
- "sender",
4677
- "slippage_bps",
4678
- "swap_max_retries"
4679
- ];
4680
- var UTF8 = new TextEncoder();
4681
- function compareKeysBytewise(a, b) {
4682
- if (a === b) return 0;
4683
- const ab = UTF8.encode(a);
4684
- const bb = UTF8.encode(b);
4685
- const shared = Math.min(ab.length, bb.length);
4686
- for (let i = 0; i < shared; i++) {
4687
- if (ab[i] !== bb[i]) return ab[i] - bb[i];
4688
- }
4689
- return ab.length - bb.length;
4690
- }
4691
- function canonicalJson(value) {
4692
- if (value === null || value === void 0) return "null";
4693
- if (typeof value === "boolean") return value ? "true" : "false";
4694
- if (typeof value === "number") {
4695
- if (!Number.isFinite(value) || !Number.isInteger(value) || !Number.isSafeInteger(value)) {
4696
- throw new Error(
4697
- `canonicalJson: refusing to sign the number ${String(value)}. The relay renders numbers with serde_json, which spells non-integer, non-finite and very large values differently from JavaScript, so the two digests would differ and the request would be rejected with an unexplained 401. Send it as a decimal string instead, which is how every amount in this schema travels.`
4698
- );
4699
- }
4700
- return String(value);
4701
- }
4702
- if (typeof value === "bigint") return value.toString();
4703
- if (typeof value === "string") return JSON.stringify(value);
4704
- if (Array.isArray(value)) return "[" + value.map(canonicalJson).join(",") + "]";
4705
- if (typeof value === "object") {
4706
- const obj = value;
4707
- const keys = Object.keys(obj).sort(compareKeysBytewise);
4708
- return "{" + keys.map((k) => {
4709
- const v = obj[k];
4710
- if (typeof v === "function" || typeof v === "symbol") {
4711
- throw new Error(
4712
- `canonicalJson: the key "${k}" holds a ${typeof v}, which has no JSON representation. JSON.stringify would drop it from the wire body while it stayed in the signed view, so the relay's digest could never match this one.`
4713
- );
4714
- }
4715
- return `${JSON.stringify(k)}:${canonicalJson(v)}`;
4716
- }).join(",") + "}";
4717
- }
4718
- throw new Error(`canonicalJson: unsupported value of type ${typeof value}`);
4719
- }
4720
- var FIELDS_WITHOUT_NULL_ENCODING = {
4721
- slippage_bps: "500 (`default_slippage_bps` in api/transact_swap.rs)"
4722
- };
4723
- function buildAuthRequest(body, sender, fields) {
4724
- const out = {};
4725
- for (const k of fields) {
4726
- if (k === "sender") {
4727
- out[k] = sender;
4728
- continue;
4729
- }
4730
- const value = body[k];
4731
- if (value === void 0 || value === null) {
4732
- const relayDefault = FIELDS_WITHOUT_NULL_ENCODING[k];
4733
- if (relayDefault) {
4734
- throw new Error(
4735
- `Relay request auth: \`${k}\` must be present in the body. It is the one field in this schema that is not optional on the relay side: when it is missing the relay signs its default, ${relayDefault}, while this request would sign null. The two digests differ and the relay answers 401 "Relay request signature does not match the exact request", which points at nothing. Set \`${k}\` explicitly, to the same value the body will carry.`
4736
- );
4737
- }
4738
- out[k] = null;
4739
- continue;
4740
- }
4741
- out[k] = value;
4742
- }
4743
- return out;
4744
- }
4745
- function buildRequestAuthMessage(endpoint, programId, issuedAt, nonce, request) {
4746
- const digest = sha256Hex(canonicalJson(request));
4747
- return new TextEncoder().encode(
4748
- `${REQUEST_AUTH_DOMAIN}
4749
- ${endpoint}
4750
- ${programId.toBase58()}
4751
- ${nonce}
4752
- ${issuedAt}
4753
- ${digest}`
4754
- );
4755
- }
4756
- function buildRelayAuthPreimage(endpoint, programId, body, sender, nowSeconds, fields = TRANSACT_AUTH_FIELDS) {
4757
- const senderB58 = sender.toBase58();
4758
- const issuedAt = String(nowSeconds ?? Math.floor(Date.now() / 1e3));
4759
- const nonce = randomNonceUuid();
4760
- const request = buildAuthRequest({ ...body, sender: senderB58 }, senderB58, fields);
4761
- const message = buildRequestAuthMessage(endpoint, programId, issuedAt, nonce, request);
4762
- return { sender: senderB58, auth_issued_at: issuedAt, auth_nonce: nonce, message };
4763
- }
4764
- function signRelayRequest(endpoint, programId, body, signer, nowSeconds, fields = TRANSACT_AUTH_FIELDS) {
4765
- const preimage = buildRelayAuthPreimage(
4766
- endpoint,
4767
- programId,
4768
- body,
4769
- signer.publicKey,
4770
- nowSeconds,
4771
- fields
4772
- );
4773
- const signature = nacl5.sign.detached(preimage.message, signer.secretKey);
4774
- return {
4775
- sender: preimage.sender,
4776
- auth_issued_at: preimage.auth_issued_at,
4777
- auth_nonce: preimage.auth_nonce,
4778
- auth_signature: Buffer.from(signature).toString("base64")
4779
- };
4780
- }
4781
- function assertApprovalWithinFreshnessWindow(elapsedMs) {
4782
- const elapsedSeconds = elapsedMs / 1e3;
4783
- const budget = REQUEST_AUTH_MAX_AGE_SECONDS - RELAY_AUTH_APPROVAL_MARGIN_SECONDS;
4784
- if (elapsedSeconds <= budget) return;
4785
- throw new Error(
4786
- `The wallet approval took ${Math.round(elapsedSeconds)} seconds, and Cloak requests stay valid for ${REQUEST_AUTH_MAX_AGE_SECONDS} seconds from the moment they are signed (${RELAY_AUTH_APPROVAL_MARGIN_SECONDS} of those are held back for the request to reach the network). The timestamp is inside the signature, so it cannot be refreshed without your approval again. NOTHING WAS SUBMITTED and no funds moved. Start the same operation again and approve the prompt when it appears.`
4787
- );
4788
- }
4789
- function explainRelayAuthRejection(responseText) {
4790
- const text = String(responseText);
4791
- const has = (needle) => text.includes(needle);
4792
- if (has("issued too far in the future")) {
4793
- return `This computer's clock is more than ${REQUEST_AUTH_MAX_FUTURE_SKEW_SECONDS} seconds ahead of the network's, so the request looks like it was signed in the future and is refused before anything else happens. Turn on automatic date and time (or resynchronise the clock) and try again. Nothing was submitted.`;
4794
- }
4795
- if (has("signature expired")) {
4796
- return `The request was signed more than ${REQUEST_AUTH_MAX_AGE_SECONDS} seconds before it arrived, usually because a wallet approval was left waiting, or because this computer's clock is running behind. The signed timestamp cannot be refreshed on its own. Start the operation again. Nothing was submitted.`;
4797
- }
4798
- if (has("does not match the exact request")) {
4799
- return `The request body changed after it was signed, so the relay's digest and this one disagree. Sign the exact body that is POSTed, and re-POST a retry byte for byte rather than rebuilding it. If this is a hand-built swap call, check that every field in TRANSACT_SWAP_AUTH_FIELDS is present, including \`slippage_bps\`.`;
4800
- }
4801
- if (has("has no registered viewing key")) {
4802
- return `The signature was accepted, so this is not a signing problem: the wallet that signed has no viewing key registered with Cloak yet. Register one first, or let the SDK do it by leaving \`enforceViewingKeyRegistration\` on and supplying the viewing key.`;
4803
- }
4804
- if (has("Authenticated sender is required") || has("auth_issued_at is required") || has("auth_nonce is required") || has("auth_signature is required")) {
4805
- return `The request reached the relay without its authentication fields. Pass \`depositorKeypair\`, or \`signMessage\` together with \`walletPublicKey\`, so the request can be signed.`;
4806
- }
4807
- if (has("auth_nonce must be a canonical UUID")) {
4808
- return `\`auth_nonce\` must be a canonical lowercase UUID. Reuse the one from the preimage.`;
4809
- }
4810
- if (has("Relay request signature must be exactly 64 bytes") || has("Invalid relay request signature encoding")) {
4811
- return `\`auth_signature\` must be the raw 64-byte ed25519 detached signature, base64 encoded. Wallet adapters that wrap or re-encode what \`signMessage\` returns cannot be used here.`;
4812
- }
4813
- return null;
4814
- }
4815
- async function buildRelayAuthFields(endpoint, programId, body, signers, fields = TRANSACT_AUTH_FIELDS) {
4816
- if (signers.depositorKeypair) {
4817
- return signRelayRequest(endpoint, programId, body, signers.depositorKeypair, void 0, fields);
4818
- }
4819
- const wallet = signers.relayAuthSigner;
4820
- if (!wallet) return null;
4821
- const preimage = buildRelayAuthPreimage(
4822
- endpoint,
4823
- programId,
4824
- body,
4825
- wallet.walletPublicKey,
4826
- void 0,
4827
- fields
4828
- );
4829
- const approvalStartedMs = Date.now();
4830
- const signature = await wallet.signMessage(preimage.message);
4831
- assertApprovalWithinFreshnessWindow(Date.now() - approvalStartedMs);
4832
- if (!(signature instanceof Uint8Array) || signature.length !== 64) {
4833
- throw new Error(
4834
- `The wallet's signMessage did not return a 64-byte ed25519 detached signature (got ${signature instanceof Uint8Array ? `${signature.length} bytes` : typeof signature}). Cloak signs with a raw detached signature over the bytes it hands the wallet; adapters that wrap or re-encode the result cannot be used here. Pass depositorKeypair instead, or use an adapter whose signMessage returns the raw signature.`
4835
- );
4836
- }
4837
- return {
4838
- sender: preimage.sender,
4839
- auth_issued_at: preimage.auth_issued_at,
4840
- auth_nonce: preimage.auth_nonce,
4841
- auth_signature: Buffer.from(signature).toString("base64")
4842
- };
4843
- }
4844
-
4845
4903
  // src/proving/artifacts.ts
4846
4904
  import { sha256 as sha2564 } from "@noble/hashes/sha2";
4847
4905
 
@@ -5117,6 +5175,7 @@ async function resolveTransactionCircuitFiles() {
5117
5175
  }
5118
5176
  var _transactPaddingSaltCounter = 0;
5119
5177
  var _registeredViewingKeys = /* @__PURE__ */ new Set();
5178
+ var _viewingKeyRegistrationsInFlight = /* @__PURE__ */ new Map();
5120
5179
  var _resolvedAltAccountsCache = /* @__PURE__ */ new Map();
5121
5180
  var _relayMerkleDisabledMints = /* @__PURE__ */ new Set();
5122
5181
  var _relayMerkleFallbackNotifiedMints = /* @__PURE__ */ new Set();
@@ -5241,7 +5300,7 @@ async function fetchSupplementalAltFromRelay(relayUrl, params) {
5241
5300
  throw new Error("Supplemental ALT response is missing a 'table' address");
5242
5301
  }
5243
5302
  try {
5244
- return new PublicKey8(table);
5303
+ return new PublicKey10(table);
5245
5304
  } catch {
5246
5305
  throw new Error(`Supplemental ALT response 'table' is not a valid public key: ${table}`);
5247
5306
  }
@@ -5309,7 +5368,7 @@ async function resolveAddressLookupTableAccounts(connection, relayUrl, altAddres
5309
5368
  const fetched = await Promise.all(
5310
5369
  altAddresses.map(async (addr) => {
5311
5370
  try {
5312
- const pubkey = new PublicKey8(addr);
5371
+ const pubkey = new PublicKey10(addr);
5313
5372
  const result = await connection.getAddressLookupTable(pubkey);
5314
5373
  return result.value ?? null;
5315
5374
  } catch {
@@ -5399,7 +5458,7 @@ function calculateConfiguredProtocolFee(amount, config) {
5399
5458
  return total;
5400
5459
  }
5401
5460
  async function readLiveProtocolFee(connection, programId, mint, amount, requireSwapOpen = false) {
5402
- const [poolConfigPda] = PublicKey8.findProgramAddressSync(
5461
+ const [poolConfigPda] = PublicKey10.findProgramAddressSync(
5403
5462
  [Buffer.from("pool_config"), mint.toBuffer()],
5404
5463
  programId
5405
5464
  );
@@ -5449,6 +5508,23 @@ async function computeSwapExtDataHash(outputMint, recipientAta, minOutputAmount,
5449
5508
  ]);
5450
5509
  return poseidonHasher.F.toObject(hash);
5451
5510
  }
5511
+ function assertInputMints(inputUtxos, expectedMint) {
5512
+ const funded = inputUtxos.filter((u) => u.amount > BigInt(0));
5513
+ if (funded.length === 0) return;
5514
+ const first = funded[0].mintAddress;
5515
+ for (const utxo of funded) {
5516
+ if (!utxo.mintAddress.equals(first)) {
5517
+ throw new Error(
5518
+ `Input notes span more than one mint (${first.toBase58()} and ${utxo.mintAddress.toBase58()}). Every pool holds a single mint, so this cannot be a valid spend of either. Select notes of one mint.`
5519
+ );
5520
+ }
5521
+ }
5522
+ if (expectedMint && !first.equals(expectedMint)) {
5523
+ throw new Error(
5524
+ `Input notes are ${first.toBase58()} but this transaction expects ${expectedMint.toBase58()}. The notes decide which pool is spent, so proceeding would move the wrong asset \u2014 refusing. This usually means note selection ignored the mint.`
5525
+ );
5526
+ }
5527
+ }
5452
5528
  function parseNkInput(input) {
5453
5529
  if (typeof input !== "string") {
5454
5530
  if (input.length !== 32) {
@@ -5504,6 +5580,26 @@ async function ensureViewingKeyRegistered(options, onProgress, fallbackNk) {
5504
5580
  if (_registeredViewingKeys.has(cacheKey)) {
5505
5581
  return;
5506
5582
  }
5583
+ const inFlight = _viewingKeyRegistrationsInFlight.get(cacheKey);
5584
+ if (inFlight) {
5585
+ return inFlight;
5586
+ }
5587
+ const registration = registerViewingKeyOnce(
5588
+ relayUrl,
5589
+ userPubkey,
5590
+ viewingKeyHex,
5591
+ cacheKey,
5592
+ options,
5593
+ onProgress
5594
+ );
5595
+ _viewingKeyRegistrationsInFlight.set(cacheKey, registration);
5596
+ try {
5597
+ await registration;
5598
+ } finally {
5599
+ _viewingKeyRegistrationsInFlight.delete(cacheKey);
5600
+ }
5601
+ }
5602
+ async function registerViewingKeyOnce(relayUrl, userPubkey, viewingKeyHex, cacheKey, options, onProgress) {
5507
5603
  const challengeResponse = await relayFetch(`${relayUrl}/viewing-key/challenge`, {
5508
5604
  method: "POST",
5509
5605
  headers: { "Content-Type": "application/json" },
@@ -5524,17 +5620,27 @@ async function ensureViewingKeyRegistered(options, onProgress, fallbackNk) {
5524
5620
  viewingKeyHex
5525
5621
  );
5526
5622
  let signatureBase64;
5623
+ let authMode = "message";
5624
+ const messageBytes = new TextEncoder().encode(challenge.message);
5527
5625
  if (options.signMessage) {
5528
- const messageBytes = new TextEncoder().encode(challenge.message);
5529
5626
  const sig = await options.signMessage(messageBytes);
5530
5627
  signatureBase64 = Buffer.from(sig).toString("base64");
5531
5628
  } else if (options.depositorKeypair) {
5532
- const messageBytes = new TextEncoder().encode(challenge.message);
5533
5629
  const sig = nacl6.sign.detached(messageBytes, options.depositorKeypair.secretKey);
5534
5630
  signatureBase64 = Buffer.from(sig).toString("base64");
5631
+ } else if (options.signAuthTransaction) {
5632
+ onProgress?.(
5633
+ "Approve the viewing-key registration in your wallet. It shows as a 0 SOL transfer to yourself and is never sent to the network."
5634
+ );
5635
+ const signed = await signRelayAuthPayload(
5636
+ { walletPublicKey: userPubkey, signAuthTransaction: options.signAuthTransaction },
5637
+ messageBytes
5638
+ );
5639
+ signatureBase64 = Buffer.from(signed.signature).toString("base64");
5640
+ authMode = signed.mode;
5535
5641
  } else {
5536
5642
  throw new Error(
5537
- "Viewing key registration is mandatory: signMessage (wallet) or depositorKeypair is required."
5643
+ "Viewing key registration is mandatory: signMessage or signAuthTransaction (wallet) or depositorKeypair is required."
5538
5644
  );
5539
5645
  }
5540
5646
  onProgress?.("Registering viewing key...");
@@ -5545,7 +5651,8 @@ async function ensureViewingKeyRegistered(options, onProgress, fallbackNk) {
5545
5651
  user_pubkey: userPubkey.toBase58(),
5546
5652
  viewing_key: viewingKeyHex,
5547
5653
  nonce: challenge.nonce,
5548
- signature: signatureBase64
5654
+ signature: signatureBase64,
5655
+ ...authMode === "transaction" ? { auth_mode: authMode } : {}
5549
5656
  })
5550
5657
  });
5551
5658
  if (!response.ok) {
@@ -5639,7 +5746,7 @@ function buildPublicInputsBytesFromSignals(publicSignals) {
5639
5746
  var TRANSACT_DISCRIMINATOR = 0;
5640
5747
  var RANGE_QUOTE_TAG_DEPOSIT = 1;
5641
5748
  function deriveRiskNoncePDA(programId, nonce) {
5642
- return PublicKey8.findProgramAddressSync(
5749
+ return PublicKey10.findProgramAddressSync(
5643
5750
  [Buffer.from("risk_nonce"), Buffer.from(nonce)],
5644
5751
  programId
5645
5752
  );
@@ -5658,7 +5765,7 @@ function extractDepositNonceFromEd25519Ix(ix) {
5658
5765
  }
5659
5766
  }
5660
5767
  function deriveNullifierPDA(programId, poolPDA, nullifier) {
5661
- return PublicKey8.findProgramAddressSync(
5768
+ return PublicKey10.findProgramAddressSync(
5662
5769
  [Buffer.from("nullifier"), poolPDA.toBuffer(), Buffer.from(nullifier)],
5663
5770
  programId
5664
5771
  );
@@ -5691,7 +5798,7 @@ function buildTransactInstruction(programId, payer, poolPDA, treasuryPDA, merkle
5691
5798
  }
5692
5799
  if (!isDepositData) {
5693
5800
  data[offset++] = 3;
5694
- data.set((relayer ?? PublicKey8.default).toBytes(), offset);
5801
+ data.set((relayer ?? PublicKey10.default).toBytes(), offset);
5695
5802
  offset += 32;
5696
5803
  writeBigUInt64LE(data, relayerFee ?? BigInt(0), offset);
5697
5804
  offset += 8;
@@ -5721,7 +5828,7 @@ function buildTransactInstruction(programId, payer, poolPDA, treasuryPDA, merkle
5721
5828
  // 4. nullifier PDA 0 (writable)
5722
5829
  { pubkey: nullifierPDA1, isSigner: false, isWritable: true },
5723
5830
  // 5. nullifier PDA 1 (writable)
5724
- { pubkey: SystemProgram2.programId, isSigner: false, isWritable: false }
5831
+ { pubkey: SystemProgram3.programId, isSigner: false, isWritable: false }
5725
5832
  // 6. system_program
5726
5833
  ];
5727
5834
  if (splAccounts) {
@@ -5766,7 +5873,7 @@ function buildTransactInstruction(programId, payer, poolPDA, treasuryPDA, merkle
5766
5873
  var PACKET_LIMIT_BYTES = 1232;
5767
5874
  function getCommonALTAddresses() {
5768
5875
  return [
5769
- SystemProgram2.programId,
5876
+ SystemProgram3.programId,
5770
5877
  SYSVAR_SLOT_HASHES_PUBKEY,
5771
5878
  SYSVAR_INSTRUCTIONS_PUBKEY,
5772
5879
  ComputeBudgetProgram.programId
@@ -5844,7 +5951,7 @@ async function createEphemeralALT(connection, depositor, onProgress, additionalA
5844
5951
  allowSupplementalAlt
5845
5952
  );
5846
5953
  } else {
5847
- const tx = new Transaction2().add(createIx).add(extendIx);
5954
+ const tx = new Transaction3().add(createIx).add(extendIx);
5848
5955
  const { blockhash } = await connection.getLatestBlockhash();
5849
5956
  tx.recentBlockhash = blockhash;
5850
5957
  tx.feePayer = depositor.publicKey;
@@ -6505,7 +6612,7 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
6505
6612
  let legacyTransportAttempt = 0;
6506
6613
  while (true) {
6507
6614
  try {
6508
- const tx = new Transaction2();
6615
+ const tx = new Transaction3();
6509
6616
  for (const ix of instructions) {
6510
6617
  tx.add(ix);
6511
6618
  }
@@ -6729,7 +6836,7 @@ async function fetchRiskQuote(riskQuoteUrl, wallet, options) {
6729
6836
  const messageArr = hexToBytes2(messageHex);
6730
6837
  let signerPubkey;
6731
6838
  try {
6732
- signerPubkey = new PublicKey8(signerB58);
6839
+ signerPubkey = new PublicKey10(signerB58);
6733
6840
  } catch {
6734
6841
  throw new Error("Invalid risk quote response: signer_pubkey is not a public key");
6735
6842
  }
@@ -6830,18 +6937,22 @@ function planRelayAuth(options) {
6830
6937
  const walletPublicKey = resolveUserWallet(options);
6831
6938
  if (options.depositorKeypair) return { kind: "keypair" };
6832
6939
  const signMessage = options.signMessage;
6940
+ const signAuthTransaction = options.signAuthTransaction;
6833
6941
  if (walletPublicKey && signMessage) return { kind: "wallet", signer: { walletPublicKey, signMessage } };
6834
- if (!walletPublicKey && !signMessage) return { kind: "none" };
6942
+ if (walletPublicKey && signAuthTransaction) {
6943
+ return { kind: "wallet", signer: { walletPublicKey, signAuthTransaction } };
6944
+ }
6945
+ if (!walletPublicKey && !signMessage && !signAuthTransaction) return { kind: "none" };
6835
6946
  return {
6836
6947
  kind: "incomplete",
6837
- detail: signMessage ? "`signMessage` was provided but no wallet public key, so the request has no sender to authenticate as. Add `walletPublicKey` (or `depositorPublicKey`)." : "a wallet public key was provided but no `signMessage`, so nothing can sign. Add `signMessage` from the same wallet adapter."
6948
+ detail: signMessage || signAuthTransaction ? "a signer was provided but no wallet public key, so the request has no sender to authenticate as. Add `walletPublicKey` (or `depositorPublicKey`)." : "a wallet public key was provided but no `signMessage` and no `signAuthTransaction`, so nothing can sign. Add `signMessage` from the wallet adapter, or `signAuthTransaction` (its `signTransaction`) for a wallet that cannot sign messages."
6838
6949
  };
6839
6950
  }
6840
6951
  function assertRelayAuthAvailable(plan, flow) {
6841
6952
  if (plan.kind === "keypair" || plan.kind === "wallet") return;
6842
6953
  const cause = plan.kind === "none" ? "no signer was provided." : `the signer provided cannot produce one: ${plan.detail}`;
6843
6954
  throw new Error(
6844
- `Cloak ${flow} requires an authenticated sender, and ${cause} Pass either \`depositorKeypair\` (a local Keypair) or \`signMessage\` together with \`walletPublicKey\` (a browser wallet adapter). The sender must be the end user's own wallet. Checked before proof generation, so nothing has been computed or submitted yet.`
6955
+ `Cloak ${flow} requires an authenticated sender, and ${cause} Pass either \`depositorKeypair\` (a local Keypair), or \`signMessage\` (a browser wallet adapter) or \`signAuthTransaction\` (a hardware wallet that cannot sign messages) together with \`walletPublicKey\`. The sender must be the end user's own wallet. Checked up front, so nothing has been computed or submitted yet.`
6845
6956
  );
6846
6957
  }
6847
6958
  async function submitTransactToRelay(args) {
@@ -6859,12 +6970,12 @@ async function submitTransactToRelay(args) {
6859
6970
  const doFetch = args.fetchImpl ?? fetch;
6860
6971
  const maxNetworkRetries = args.maxNetworkRetries ?? relayNetworkRetries();
6861
6972
  const requestTimeoutMs = args.requestTimeoutMs ?? relayRequestTimeoutMs();
6862
- if (!depositorKeypair && args.relayAuthSigner) {
6973
+ if (!depositorKeypair && args.relayAuthSigner && !args.relayAuthBatch) {
6863
6974
  onProgress?.(
6864
6975
  "Approve the request in your wallet. This signs the request for the relay, not a transaction, and it must be approved within a few minutes to stay valid."
6865
6976
  );
6866
6977
  }
6867
- const authFields = await buildRelayAuthFields("/transact", programId, requestBody, {
6978
+ const authFields = args.relayAuthBatch ? await args.relayAuthBatch.authorize("/transact", programId, requestBody, TRANSACT_AUTH_FIELDS) : await buildRelayAuthFields("/transact", programId, requestBody, {
6868
6979
  depositorKeypair,
6869
6980
  relayAuthSigner: args.relayAuthSigner
6870
6981
  });
@@ -7060,7 +7171,7 @@ async function transact(params, options) {
7060
7171
  // names is refused.
7061
7172
  maxRootRetries = 40,
7062
7173
  // Increased to handle prolonged relay sync recovery under high concurrency
7063
- retryDelayMs = 500,
7174
+ retryDelayMs: retryDelayMs2 = 500,
7064
7175
  // Start with short delay, will exponentially back off
7065
7176
  riskOracleQueue,
7066
7177
  riskQuoteUrl: riskQuoteUrlOption,
@@ -7115,6 +7226,7 @@ async function transact(params, options) {
7115
7226
  }
7116
7227
  }
7117
7228
  assertAllowedRpcConnection(connection);
7229
+ assertInputMints(inputUtxos, options.expectedMint);
7118
7230
  await ensureViewingKeyRegistered(options, onProgress, fallbackNk);
7119
7231
  const resolvedChainNoteNk = explicitChainNoteNk ?? fallbackNk ?? null;
7120
7232
  if (inputUtxos.length > 2) {
@@ -7481,7 +7593,7 @@ async function transact(params, options) {
7481
7593
  while (submissionAttempts <= maxRootRetries) {
7482
7594
  const isRetry = submissionAttempts > 0;
7483
7595
  if (isRetry) {
7484
- const exponentialDelay = Math.min(retryDelayMs * Math.pow(1.5, submissionAttempts - 1), 5e3);
7596
+ const exponentialDelay = Math.min(retryDelayMs2 * Math.pow(1.5, submissionAttempts - 1), 5e3);
7485
7597
  const jitter = Math.random() * 500;
7486
7598
  const totalDelay = Math.floor(exponentialDelay + jitter);
7487
7599
  await sleep2(totalDelay);
@@ -7620,11 +7732,14 @@ async function transact(params, options) {
7620
7732
  }
7621
7733
  let proof;
7622
7734
  let publicSignals;
7735
+ const releaseProofSlot = options.relayAuthBatch ? await options.relayAuthBatch.proofSlot() : void 0;
7623
7736
  try {
7624
7737
  const result = await generateTransactionProof(circuitInputs, onProofProgress);
7625
7738
  proof = result.proof;
7626
7739
  publicSignals = result.publicSignals;
7740
+ releaseProofSlot?.();
7627
7741
  } catch (proofError) {
7742
+ releaseProofSlot?.();
7628
7743
  const errorMsg = proofError?.message || String(proofError);
7629
7744
  if ((errorMsg.includes("ForceEqual") || errorMsg.includes("Assert Failed")) && submissionAttempts < maxRootRetries) {
7630
7745
  onProgress?.(`Merkle proof error (stale sibling data), will regenerate with fresh tree...`);
@@ -7644,7 +7759,7 @@ async function transact(params, options) {
7644
7759
  }
7645
7760
  treeState = null;
7646
7761
  useSiblingInfo = false;
7647
- const waitMs = Math.min(retryDelayMs * Math.pow(2, Math.min(submissionAttempts, 4)), 5e3);
7762
+ const waitMs = Math.min(retryDelayMs2 * Math.pow(2, Math.min(submissionAttempts, 4)), 5e3);
7648
7763
  if (waitMs > 0) {
7649
7764
  onProgress?.(`Waiting ${waitMs}ms for relay sync before retry...`);
7650
7765
  await new Promise((r) => setTimeout(r, waitMs));
@@ -7937,6 +8052,7 @@ async function transact(params, options) {
7937
8052
  // point where they reach the relay submission instead of stopping at viewing-key
7938
8053
  // registration. The keypair still wins when both are present.
7939
8054
  relayAuthSigner: relayAuthPlan.kind === "wallet" ? relayAuthPlan.signer : void 0,
8055
+ relayAuthBatch: options.relayAuthBatch,
7940
8056
  settlement: {
7941
8057
  connection,
7942
8058
  programId,
@@ -7982,7 +8098,7 @@ async function transact(params, options) {
7982
8098
  "All relay-tree attempts failed. Rebuilding merkle tree from chain as last resort..."
7983
8099
  );
7984
8100
  try {
7985
- const [merkleTreePda] = PublicKey8.findProgramAddressSync(
8101
+ const [merkleTreePda] = PublicKey10.findProgramAddressSync(
7986
8102
  [Buffer.from("merkle_tree"), mint.toBuffer()],
7987
8103
  programId
7988
8104
  );
@@ -8338,7 +8454,7 @@ async function swapUtxo(params, options) {
8338
8454
  riskQuoteUrl: riskQuoteUrlOption,
8339
8455
  maxRootRetries = 40,
8340
8456
  // Increased to handle prolonged relay sync recovery under high concurrency
8341
- retryDelayMs = 500,
8457
+ retryDelayMs: retryDelayMs2 = 500,
8342
8458
  // Start with short delay, will exponentially back off
8343
8459
  useUniqueNullifiers,
8344
8460
  useChainRootForProof = true
@@ -8357,6 +8473,12 @@ async function swapUtxo(params, options) {
8357
8473
  if (inputUtxos.length > 2) {
8358
8474
  throw new Error("Maximum 2 input UTXOs allowed");
8359
8475
  }
8476
+ assertInputMints(inputUtxos, NATIVE_SOL_MINT);
8477
+ if (options.expectedMint && !options.expectedMint.equals(NATIVE_SOL_MINT)) {
8478
+ throw new Error(
8479
+ `swapUtxo expects ${NATIVE_SOL_MINT.toBase58()} input notes (a swap always spends from the wSOL pool), but expectedMint was ${options.expectedMint.toBase58()}. Drop expectedMint, or spend the other mint with transact/transfer instead.`
8480
+ );
8481
+ }
8360
8482
  await preflightNullifiers(inputUtxos, connection, programId);
8361
8483
  const outputMintAccount = await connection.getAccountInfo(outputMint, {
8362
8484
  commitment: "confirmed"
@@ -8743,7 +8865,7 @@ async function swapUtxo(params, options) {
8743
8865
  let walletApprovals = 0;
8744
8866
  for (let attempt = 0; attempt <= maxRootRetries; attempt++) {
8745
8867
  if (attempt > 0) {
8746
- const exponentialDelay = Math.min(retryDelayMs * Math.pow(1.5, attempt - 1), 5e3);
8868
+ const exponentialDelay = Math.min(retryDelayMs2 * Math.pow(1.5, attempt - 1), 5e3);
8747
8869
  const jitter = Math.random() * 500;
8748
8870
  const totalDelay = Math.floor(exponentialDelay + jitter);
8749
8871
  await sleep2(totalDelay);
@@ -8881,7 +9003,7 @@ async function swapUtxo(params, options) {
8881
9003
  }
8882
9004
  merkleState = null;
8883
9005
  useSiblingInfo = false;
8884
- const waitMs = Math.min(retryDelayMs * Math.pow(2, Math.min(attempt, 4)), 5e3);
9006
+ const waitMs = Math.min(retryDelayMs2 * Math.pow(2, Math.min(attempt, 4)), 5e3);
8885
9007
  if (waitMs > 0) {
8886
9008
  onProgress?.(`Waiting ${waitMs}ms for relay sync before retry...`);
8887
9009
  await new Promise((r) => setTimeout(r, waitMs));
@@ -8964,7 +9086,7 @@ async function swapUtxo(params, options) {
8964
9086
  }
8965
9087
  if (options.onSwapStatePredicted && inputNullifiers.length > 0) {
8966
9088
  const nBytes = bigintToBytes322(inputNullifiers[0]);
8967
- const [ssp] = PublicKey8.findProgramAddressSync(
9089
+ const [ssp] = PublicKey10.findProgramAddressSync(
8968
9090
  [Buffer.from("swap_state"), pdas.pool.toBuffer(), Buffer.from(nBytes)],
8969
9091
  programId
8970
9092
  );
@@ -9383,10 +9505,276 @@ function cleanupStalePendingOperations(maxAgeMs = 24 * 60 * 60 * 1e3) {
9383
9505
  return { removedDeposits, removedWithdrawals };
9384
9506
  }
9385
9507
 
9508
+ // src/relay/batch-auth.ts
9509
+ import { Keypair as Keypair4 } from "@solana/web3.js";
9510
+ var RelayBatchAuthCoordinator = class {
9511
+ constructor(options) {
9512
+ this.proofSlotsInUse = 0;
9513
+ this.proofQueue = [];
9514
+ this.items = [];
9515
+ this.signing = false;
9516
+ this.slotsInUse = 0;
9517
+ this.ready = [];
9518
+ this.waves = 0;
9519
+ if (!Number.isInteger(options.items) || options.items < 1) {
9520
+ throw new Error(`A batch needs at least one item (got ${options.items}).`);
9521
+ }
9522
+ if (options.items > RELAY_BATCH_AUTH_MAX_ITEMS) {
9523
+ throw new Error(
9524
+ `A batch may cover at most ${RELAY_BATCH_AUTH_MAX_ITEMS} requests (got ${options.items}); the relay refuses longer digest lists. Chunk the payout. The practical bound is lower still: each spend appends two roots to the on-chain ring of 100, so a batch proved against one root evicts it by itself before 50 items.`
9525
+ );
9526
+ }
9527
+ const concurrency = options.submitConcurrency ?? 3;
9528
+ if (!Number.isInteger(concurrency) || concurrency < 1) {
9529
+ throw new Error(`submitConcurrency must be a positive integer (got ${concurrency}).`);
9530
+ }
9531
+ this.signer = options.signer;
9532
+ this.sender = options.signer instanceof Keypair4 ? options.signer.publicKey : options.signer.walletPublicKey;
9533
+ this.expectedItems = options.items;
9534
+ this.submitConcurrency = concurrency;
9535
+ const proofConcurrency = options.proofConcurrency ?? 2;
9536
+ if (!Number.isInteger(proofConcurrency) || proofConcurrency < 1) {
9537
+ throw new Error(`proofConcurrency must be a positive integer (got ${proofConcurrency}).`);
9538
+ }
9539
+ this.proofConcurrency = proofConcurrency;
9540
+ this.onProgress = options.onProgress;
9541
+ }
9542
+ /** The wallet that signs and is every item's authenticated `sender`. */
9543
+ get walletPublicKey() {
9544
+ return this.sender;
9545
+ }
9546
+ /** How many times the signer has been asked so far: one per wave. */
9547
+ get approvals() {
9548
+ return this.waves;
9549
+ }
9550
+ /** Where every item is right now. For progress UIs and for diagnosing a batch that never signs. */
9551
+ snapshot() {
9552
+ const count = (state) => this.items.filter((i) => i.state === state).length;
9553
+ return {
9554
+ building: count("building"),
9555
+ waiting: count("waiting"),
9556
+ submitting: count("submitting"),
9557
+ done: count("done"),
9558
+ proving: this.proofSlotsInUse,
9559
+ proofQueue: this.proofQueue.length,
9560
+ registered: this.items.length,
9561
+ expected: this.expectedItems
9562
+ };
9563
+ }
9564
+ /** Register one item. Throws past `items`: the wave condition would never be reachable. */
9565
+ item() {
9566
+ if (this.items.length >= this.expectedItems) {
9567
+ throw new Error(
9568
+ `This batch was created for ${this.expectedItems} item(s) and all of them are registered.`
9569
+ );
9570
+ }
9571
+ const item = { state: "building", waiting: null, holdsSlot: false };
9572
+ this.items.push(item);
9573
+ return {
9574
+ proofSlot: () => this.acquireProofSlot(),
9575
+ authorize: (endpoint, programId, body, fields) => this.authorize(item, endpoint, programId, body, fields),
9576
+ finish: () => this.finish(item)
9577
+ };
9578
+ }
9579
+ acquireProofSlot() {
9580
+ return new Promise((resolve) => {
9581
+ const grant = () => {
9582
+ this.proofSlotsInUse += 1;
9583
+ let released = false;
9584
+ resolve(() => {
9585
+ if (released) return;
9586
+ released = true;
9587
+ this.proofSlotsInUse -= 1;
9588
+ const next = this.proofQueue.shift();
9589
+ if (next) next();
9590
+ });
9591
+ };
9592
+ if (this.proofSlotsInUse < this.proofConcurrency) grant();
9593
+ else this.proofQueue.push(grant);
9594
+ });
9595
+ }
9596
+ authorize(item, endpoint, programId, body, fields) {
9597
+ if (item.state === "done") {
9598
+ return Promise.reject(new Error("This batch item already finished; it cannot authorize."));
9599
+ }
9600
+ if (item.state === "waiting") {
9601
+ return Promise.reject(new Error("This batch item is already waiting for a signature."));
9602
+ }
9603
+ this.releaseSlot(item);
9604
+ return new Promise((resolve, reject) => {
9605
+ item.state = "waiting";
9606
+ item.waiting = { endpoint, programId, body, fields, resolve, reject };
9607
+ this.maybeSign();
9608
+ });
9609
+ }
9610
+ finish(item) {
9611
+ if (item.state === "done") return;
9612
+ if (item.state === "waiting" && item.waiting) {
9613
+ item.waiting.reject(new Error("Batch item finished before it was signed."));
9614
+ item.waiting = null;
9615
+ }
9616
+ this.releaseSlot(item);
9617
+ item.state = "done";
9618
+ this.maybeSign();
9619
+ this.pump();
9620
+ }
9621
+ releaseSlot(item) {
9622
+ if (!item.holdsSlot) return;
9623
+ item.holdsSlot = false;
9624
+ this.slotsInUse -= 1;
9625
+ }
9626
+ /** Sign when every registered item is either waiting here or finished, and nothing is building. */
9627
+ maybeSign() {
9628
+ if (this.signing) return;
9629
+ if (this.items.length < this.expectedItems) return;
9630
+ if (this.items.some((item) => item.state === "building" || item.state === "submitting")) return;
9631
+ const wave = this.items.filter((item) => item.state === "waiting");
9632
+ if (wave.length === 0) return;
9633
+ this.signing = true;
9634
+ void this.signWave(wave).finally(() => {
9635
+ this.signing = false;
9636
+ this.maybeSign();
9637
+ });
9638
+ }
9639
+ async signWave(wave) {
9640
+ const waiting = wave.map((item) => item.waiting);
9641
+ try {
9642
+ const programId = waiting[0].programId;
9643
+ for (const w of waiting) {
9644
+ if (!w.programId.equals(programId)) {
9645
+ throw new Error("Every item of a batch must target the same program id.");
9646
+ }
9647
+ }
9648
+ const nowSeconds = Math.floor(Date.now() / 1e3);
9649
+ const preimages = waiting.map(
9650
+ (w) => buildRelayAuthPreimage(w.endpoint, programId, w.body, this.sender, nowSeconds, w.fields)
9651
+ );
9652
+ const digests = preimages.map(relayRequestDigestHex);
9653
+ const issuedAt = preimages[0].auth_issued_at;
9654
+ const message = buildRelayBatchAuthMessage(programId, issuedAt, digests);
9655
+ this.waves += 1;
9656
+ if (!(this.signer instanceof Keypair4)) {
9657
+ const asTransaction = !this.signer.signMessage;
9658
+ this.onProgress?.(
9659
+ `Approve ${waiting.length} request(s) in your wallet` + (this.waves > 1 ? ` (approval ${this.waves}, for the rows that had to re-prove)` : "") + (asTransaction ? `. Your wallet shows this as a 0 SOL transfer to yourself: it is how a wallet that cannot sign messages authorizes the requests for the relay, and it is never sent to the network. Approve it on the device within a few minutes.` : `. This signs the requests for the relay, not a transaction, and must be approved within a few minutes to stay valid.`)
9660
+ );
9661
+ }
9662
+ const approvalStartedMs = Date.now();
9663
+ const { signature, mode } = await signRelayAuthPayload(this.signer, message);
9664
+ if (!(this.signer instanceof Keypair4)) {
9665
+ assertApprovalWithinFreshnessWindow(
9666
+ Date.now() - approvalStartedMs,
9667
+ REQUEST_AUTH_BATCH_MAX_AGE_SECONDS
9668
+ );
9669
+ }
9670
+ const signatureB64 = Buffer.from(signature).toString("base64");
9671
+ wave.forEach((item, i) => {
9672
+ const preimage = preimages[i];
9673
+ this.ready.push({
9674
+ item,
9675
+ fields: {
9676
+ sender: preimage.sender,
9677
+ auth_issued_at: preimage.auth_issued_at,
9678
+ auth_nonce: preimage.auth_nonce,
9679
+ auth_signature: signatureB64,
9680
+ ...mode === "transaction" ? { auth_mode: mode } : {},
9681
+ // A fresh array per item: `Object.assign` puts this on the wire body, and the body is
9682
+ // serialized again on every network retry.
9683
+ auth_batch: { digests: [...digests] }
9684
+ }
9685
+ });
9686
+ });
9687
+ this.pump();
9688
+ } catch (error) {
9689
+ for (const item of wave) {
9690
+ const w = item.waiting;
9691
+ item.waiting = null;
9692
+ item.state = "done";
9693
+ w?.reject(error);
9694
+ }
9695
+ }
9696
+ }
9697
+ /** Release signed items to submit, `submitConcurrency` at a time, in signing order. */
9698
+ pump() {
9699
+ while (this.ready.length > 0 && this.slotsInUse < this.submitConcurrency) {
9700
+ const next = this.ready.shift();
9701
+ const w = next.item.waiting;
9702
+ next.item.waiting = null;
9703
+ if (next.item.state !== "waiting" || !w) continue;
9704
+ next.item.state = "submitting";
9705
+ next.item.holdsSlot = true;
9706
+ this.slotsInUse += 1;
9707
+ w.resolve(next.fields);
9708
+ }
9709
+ }
9710
+ };
9711
+ function createRelayBatchAuthCoordinator(options) {
9712
+ return new RelayBatchAuthCoordinator(options);
9713
+ }
9714
+
9715
+ // src/flows/transact-batch.ts
9716
+ async function transactBatch(items, options) {
9717
+ if (items.length === 0) {
9718
+ return { results: [], approvals: 0 };
9719
+ }
9720
+ if (items.length > RELAY_BATCH_AUTH_MAX_ITEMS) {
9721
+ throw new Error(
9722
+ `transactBatch accepts at most ${RELAY_BATCH_AUTH_MAX_ITEMS} items per call (got ${items.length}). Chunk the payout; see RELAY_BATCH_AUTH_MAX_ITEMS for why the practical bound is lower.`
9723
+ );
9724
+ }
9725
+ if (!options.relayUrl) {
9726
+ throw new Error("transactBatch requires relayUrl: batch approval is a relay authentication scheme.");
9727
+ }
9728
+ const { proofConcurrency, submitConcurrency, ...shared } = options;
9729
+ const signer = shared.depositorKeypair ? shared.depositorKeypair : (() => {
9730
+ const wallet = shared.walletPublicKey ?? shared.depositorPublicKey;
9731
+ if (!wallet) return null;
9732
+ if (shared.signMessage) return { walletPublicKey: wallet, signMessage: shared.signMessage };
9733
+ if (shared.signAuthTransaction) {
9734
+ return { walletPublicKey: wallet, signAuthTransaction: shared.signAuthTransaction };
9735
+ }
9736
+ return null;
9737
+ })();
9738
+ if (!signer) {
9739
+ throw new Error(
9740
+ "transactBatch requires an authenticated sender: pass `depositorKeypair` (a local Keypair), or `walletPublicKey` with `signMessage` (a browser wallet adapter) or `signAuthTransaction` (a hardware wallet). Checked up front, so nothing has been computed or submitted yet."
9741
+ );
9742
+ }
9743
+ const coordinator = createRelayBatchAuthCoordinator({
9744
+ signer,
9745
+ items: items.length,
9746
+ proofConcurrency,
9747
+ submitConcurrency,
9748
+ onProgress: shared.onProgress
9749
+ });
9750
+ const handles = items.map(() => coordinator.item());
9751
+ const results = new Array(items.length);
9752
+ const runOne = async (index) => {
9753
+ const item = items[index];
9754
+ const handle = handles[index];
9755
+ const label = `[batch ${index + 1}/${items.length}]`;
9756
+ try {
9757
+ const value = await transact(item.params, {
9758
+ ...shared,
9759
+ ...item.options,
9760
+ relayAuthBatch: handle,
9761
+ onProgress: shared.onProgress ? (status) => shared.onProgress?.(`${label} ${status}`) : void 0
9762
+ });
9763
+ results[index] = { status: "fulfilled", value };
9764
+ } catch (reason) {
9765
+ results[index] = { status: "rejected", reason };
9766
+ } finally {
9767
+ handle.finish();
9768
+ }
9769
+ };
9770
+ await Promise.all(items.map((_, index) => runOne(index)));
9771
+ return { results, approvals: coordinator.approvals };
9772
+ }
9773
+
9386
9774
  // src/scanning/scan.ts
9387
9775
  import _bs583 from "bs58";
9388
9776
  import {
9389
- PublicKey as PublicKey9
9777
+ PublicKey as PublicKey12
9390
9778
  } from "@solana/web3.js";
9391
9779
  import { getAssociatedTokenAddressSync as getAssociatedTokenAddressSync2 } from "@solana/spl-token";
9392
9780
  var bs583 = _bs583.default || _bs583;
@@ -9474,7 +9862,7 @@ function parseSwapOutputMint(data) {
9474
9862
  const mintBytes = data.slice(SWAP_OUTPUT_MINT_OFFSET, end);
9475
9863
  if (mintBytes.every((byte) => byte === 0)) return void 0;
9476
9864
  try {
9477
- return new PublicKey9(mintBytes).toBase58();
9865
+ return new PublicKey12(mintBytes).toBase58();
9478
9866
  } catch {
9479
9867
  return void 0;
9480
9868
  }
@@ -9564,7 +9952,7 @@ function parseSwapRecipientAta(data) {
9564
9952
  const recipientAtaBytes = data.slice(recipientAtaStart, recipientAtaEnd);
9565
9953
  if (recipientAtaBytes.every((byte) => byte === 0)) return void 0;
9566
9954
  try {
9567
- return new PublicKey9(recipientAtaBytes).toBase58();
9955
+ return new PublicKey12(recipientAtaBytes).toBase58();
9568
9956
  } catch {
9569
9957
  return void 0;
9570
9958
  }
@@ -9726,7 +10114,7 @@ async function scanSwapNoteCarriers(connection, programId, viewingKeyNk, swapCtx
9726
10114
  let rpcCalls = 0;
9727
10115
  const candidates = Array.from(swapCtxByCommitment.keys());
9728
10116
  if (candidates.length === 0) return { records, rpcCalls };
9729
- const [registry] = PublicKey9.findProgramAddressSync(
10117
+ const [registry] = PublicKey12.findProgramAddressSync(
9730
10118
  [Buffer.from("cloak_chain_note_registry")],
9731
10119
  programId
9732
10120
  );
@@ -10129,8 +10517,8 @@ async function scanTransactions(opts) {
10129
10517
  if (onChainAta) {
10130
10518
  try {
10131
10519
  const expectedAta = getAssociatedTokenAddressSync2(
10132
- new PublicKey9(asset.mint),
10133
- new PublicKey9(walletPublicKey)
10520
+ new PublicKey12(asset.mint),
10521
+ new PublicKey12(walletPublicKey)
10134
10522
  ).toBase58();
10135
10523
  if (onChainAta === expectedAta) {
10136
10524
  isOurs = true;
@@ -10344,7 +10732,7 @@ async function scanTransactions(opts) {
10344
10732
  // deposit — that is the public deposit amount. A deposit that also merged inputs
10345
10733
  // carries a larger output 0 and is not recoverable from public data alone.
10346
10734
  amount: grossAmount,
10347
- mintAddress: new PublicKey9(asset.mint),
10735
+ mintAddress: new PublicKey12(asset.mint),
10348
10736
  outputCommitments: ixCtx.outputCommitments ?? []
10349
10737
  });
10350
10738
  if (recoveredDeposit) {
@@ -10370,7 +10758,7 @@ async function scanTransactions(opts) {
10370
10758
  noteSalt: compactNote.noteSalt,
10371
10759
  amount: compactNote.outAmount0,
10372
10760
  keypair: { privateKey: 0n, publicKey: compactNote.outPubkey0 },
10373
- mintAddress: new PublicKey9(asset.mint),
10761
+ mintAddress: new PublicKey12(asset.mint),
10374
10762
  outputIndex: 0,
10375
10763
  // v4 describes output 0, which is where change lands
10376
10764
  outputCommitments: ixCtx.outputCommitments ?? []
@@ -10563,7 +10951,7 @@ function formatComplianceCsv(report) {
10563
10951
  }
10564
10952
 
10565
10953
  // src/wallet/utxo-wallet.ts
10566
- import { PublicKey as PublicKey10 } from "@solana/web3.js";
10954
+ import { PublicKey as PublicKey13 } from "@solana/web3.js";
10567
10955
  var UtxoWallet = class _UtxoWallet {
10568
10956
  constructor(viewingKey) {
10569
10957
  this.wallets = /* @__PURE__ */ new Map();
@@ -10775,7 +11163,7 @@ var UtxoWallet = class _UtxoWallet {
10775
11163
  data.viewingKey ? new Uint8Array(data.viewingKey) : void 0
10776
11164
  );
10777
11165
  for (const w of data.wallets) {
10778
- const mint = new PublicKey10(w.mint);
11166
+ const mint = new PublicKey13(w.mint);
10779
11167
  for (const u of w.utxos) {
10780
11168
  wallet.addUtxo({
10781
11169
  amount: BigInt(u.amount),
@@ -10857,12 +11245,799 @@ var SimpleWallet = class {
10857
11245
  }
10858
11246
  };
10859
11247
 
11248
+ // src/bridge/rail-verify.ts
11249
+ import nacl7 from "tweetnacl";
11250
+ import { sha256 as sha2565 } from "@noble/hashes/sha256";
11251
+ var ONECLICK_PUBKEY_B58 = "reYaWhvwu8Jzo3WUM3zhn6VrhuMEF4eADL17qtRVifc";
11252
+ var b58 = /* @__PURE__ */ (() => {
11253
+ const A = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
11254
+ return {
11255
+ decode(s) {
11256
+ let n = 0n;
11257
+ for (const c of s) {
11258
+ const i = A.indexOf(c);
11259
+ if (i < 0) throw new Error("bad base58");
11260
+ n = n * 58n + BigInt(i);
11261
+ }
11262
+ const bytes = [];
11263
+ while (n > 0n) {
11264
+ bytes.unshift(Number(n & 255n));
11265
+ n >>= 8n;
11266
+ }
11267
+ for (const c of s) {
11268
+ if (c === "1") bytes.unshift(0);
11269
+ else break;
11270
+ }
11271
+ return Uint8Array.from(bytes);
11272
+ },
11273
+ encode(b) {
11274
+ let n = 0n;
11275
+ for (const x of b) n = n * 256n + BigInt(x);
11276
+ let s = "";
11277
+ while (n > 0n) {
11278
+ s = A[Number(n % 58n)] + s;
11279
+ n /= 58n;
11280
+ }
11281
+ for (const x of b) {
11282
+ if (x === 0) s = "1" + s;
11283
+ else break;
11284
+ }
11285
+ return s;
11286
+ }
11287
+ };
11288
+ })();
11289
+ function stable(v) {
11290
+ if (v === null || typeof v !== "object") return JSON.stringify(v) ?? "null";
11291
+ if (Array.isArray(v)) return `[${v.map(stable).join(",")}]`;
11292
+ const o = v;
11293
+ const parts = Object.keys(o).sort().filter((k) => o[k] !== void 0).map((k) => `${JSON.stringify(k)}:${stable(o[k])}`);
11294
+ return `{${parts.join(",")}}`;
11295
+ }
11296
+ function signedRequest(r) {
11297
+ const q = r.quoteRequest ?? {};
11298
+ return {
11299
+ dry: q.dry,
11300
+ swapType: q.swapType,
11301
+ slippageTolerance: q.slippageTolerance,
11302
+ originAsset: q.originAsset,
11303
+ depositType: q.depositType,
11304
+ destinationAsset: q.destinationAsset,
11305
+ amount: q.amount,
11306
+ refundTo: q.refundTo,
11307
+ refundType: q.refundType,
11308
+ recipient: q.recipient,
11309
+ recipientType: q.recipientType,
11310
+ deadline: q.deadline,
11311
+ quoteWaitingTimeMs: q.quoteWaitingTimeMs || void 0,
11312
+ referral: q.referral || void 0,
11313
+ virtualChainRecipient: q.virtualChainRecipient || void 0,
11314
+ virtualChainRefundRecipient: q.virtualChainRefundRecipient || void 0,
11315
+ customRecipientMsg: q.customRecipientMsg || void 0
11316
+ // Deliberately unsigned by the rail: sessionId, connectedWallets, correlationId, appFees,
11317
+ // partnerId, userAccountId, depositMode. APP FEES ARE NOT AUTHENTICATED — never display a fee
11318
+ // caption as if the signature vouched for it.
11319
+ };
11320
+ }
11321
+ function signedQuote(r) {
11322
+ const q = r.quote ?? {};
11323
+ const base = {
11324
+ amountIn: q.amountIn,
11325
+ amountInFormatted: q.amountInFormatted,
11326
+ amountInUsd: q.amountInUsd,
11327
+ minAmountIn: q.minAmountIn,
11328
+ amountOut: q.amountOut,
11329
+ amountOutFormatted: q.amountOutFormatted,
11330
+ amountOutUsd: q.amountOutUsd,
11331
+ minAmountOut: q.minAmountOut
11332
+ };
11333
+ if (r.quoteRequest?.dry) return base;
11334
+ return {
11335
+ ...base,
11336
+ depositAddress: q.depositAddress || void 0,
11337
+ // <- the field that makes substitution detectable
11338
+ depositMemo: q.depositMemo || void 0,
11339
+ deadline: q.deadline || void 0,
11340
+ // <- a real, SIGNED expiry
11341
+ timeWhenInactive: q.timeWhenInactive || void 0,
11342
+ timeEstimate: q.timeEstimate || void 0,
11343
+ virtualChainRecipient: q.virtualChainRecipient || void 0,
11344
+ virtualChainRefundRecipient: q.virtualChainRefundRecipient || void 0,
11345
+ customRecipientMsg: q.customRecipientMsg || void 0,
11346
+ refundFee: q.refundFee || void 0,
11347
+ withdrawFee: q.withdrawFee || void 0
11348
+ };
11349
+ }
11350
+ function canonicalPayloadString(resp) {
11351
+ return stable({ ...signedRequest(resp), ...signedQuote(resp), timestamp: resp.timestamp });
11352
+ }
11353
+ function verifyQuoteSignature(resp) {
11354
+ if (!resp?.signature) return { valid: false, reason: "no signature on the response" };
11355
+ const payload = { ...signedRequest(resp), ...signedQuote(resp), timestamp: resp.timestamp };
11356
+ const digest = sha2565(new TextEncoder().encode(stable(payload)));
11357
+ const message = new TextEncoder().encode(b58.encode(new Uint8Array(digest)));
11358
+ const sig = resp.signature.replace(/^ed25519:/, "");
11359
+ try {
11360
+ return { valid: nacl7.sign.detached.verify(message, b58.decode(sig), b58.decode(ONECLICK_PUBKEY_B58)) };
11361
+ } catch (e) {
11362
+ return { valid: false, reason: e instanceof Error ? e.message : String(e) };
11363
+ }
11364
+ }
11365
+
11366
+ // src/bridge/rails.ts
11367
+ async function parseBridgeResponse(res, what) {
11368
+ let body;
11369
+ try {
11370
+ body = await res.json();
11371
+ } catch {
11372
+ throw new Error(`${what} failed: ${res.status} ${res.statusText} (response body was not JSON)`);
11373
+ }
11374
+ if (!res.ok) {
11375
+ const b = body ?? {};
11376
+ throw new Error(
11377
+ `${what} failed: ${b.message ?? res.statusText} (${b.code ?? res.status}${b.retryable ? ", retryable" : ""})`
11378
+ );
11379
+ }
11380
+ return body;
11381
+ }
11382
+ function toAttestation(raw) {
11383
+ if (raw.kind === "ed25519") {
11384
+ return { kind: "ed25519", verified: raw.verified ?? false, signer: raw.signer ?? "" };
11385
+ }
11386
+ return { kind: "none", checks: raw.checks ?? [], note: raw.note ?? "" };
11387
+ }
11388
+ async function fetchBridgeQuote(relayUrl, req) {
11389
+ const body = {
11390
+ recipient: req.recipient,
11391
+ origin_chain: req.originChain,
11392
+ amount_base_units: req.amountBaseUnits.toString(),
11393
+ allocate: req.allocate ?? false
11394
+ };
11395
+ if (req.refundTo !== void 0) body.refund_to = req.refundTo;
11396
+ if (req.rails !== void 0) body.rails = req.rails;
11397
+ const res = await relayFetch(`${relayUrl}/bridge/quote`, {
11398
+ method: "POST",
11399
+ headers: { "Content-Type": "application/json" },
11400
+ body: JSON.stringify(body)
11401
+ });
11402
+ const raw = await parseBridgeResponse(res, "bridge quote");
11403
+ const options = [];
11404
+ const unavailable = (raw.unavailable ?? []).map((p) => ({
11405
+ rail: p.rail,
11406
+ reason: p.reason
11407
+ }));
11408
+ for (const opt of raw.options) {
11409
+ const attestation = toAttestation(opt.attestation);
11410
+ if (attestation.kind === "ed25519") {
11411
+ const verdict = opt.rail_response ? verifyQuoteSignature(opt.rail_response) : { valid: false, reason: "no rail_response to verify against" };
11412
+ if (!verdict.valid) {
11413
+ unavailable.push({
11414
+ rail: opt.rail,
11415
+ reason: `deposit address failed independent signature verification, refusing to display it (${verdict.reason ?? "signature did not verify"})`
11416
+ });
11417
+ continue;
11418
+ }
11419
+ }
11420
+ options.push({
11421
+ rail: opt.rail,
11422
+ refunds: opt.refunds,
11423
+ addressLifetime: opt.address_lifetime,
11424
+ amountOut: BigInt(opt.amount_out),
11425
+ minAmountOut: BigInt(opt.min_amount_out),
11426
+ timeEstimateSeconds: opt.time_estimate_s,
11427
+ expiresAt: opt.expires_at,
11428
+ depositAddress: opt.deposit_address,
11429
+ attestation
11430
+ });
11431
+ }
11432
+ return { options, unavailable };
11433
+ }
11434
+ var STATUS_STATES = [
11435
+ "pending",
11436
+ "delivered",
11437
+ "refunded",
11438
+ "expired",
11439
+ "unknown"
11440
+ ];
11441
+ async function fetchBridgeStatus(relayUrl, depositAddress, rail) {
11442
+ const qs = new URLSearchParams({ deposit_address: depositAddress, rail });
11443
+ const res = await relayFetch(`${relayUrl}/bridge/status?${qs.toString()}`);
11444
+ const raw = await parseBridgeResponse(res, "bridge status");
11445
+ const state = STATUS_STATES.includes(raw.state ?? "") ? raw.state : "unknown";
11446
+ return { rail: raw.rail ?? rail, state, detail: raw.detail ?? "" };
11447
+ }
11448
+ function cloakBridgeRail(relayUrl) {
11449
+ assertAllowedRelayOrigin(relayUrl);
11450
+ return {
11451
+ id: "cloak-bridge",
11452
+ quote: (req) => fetchBridgeQuote(relayUrl, req),
11453
+ status: (depositAddress, rail) => fetchBridgeStatus(relayUrl, depositAddress, rail)
11454
+ };
11455
+ }
11456
+
11457
+ // src/bridge/paymaster-client.ts
11458
+ import {
11459
+ PublicKey as PublicKey14,
11460
+ SystemInstruction,
11461
+ SystemProgram as SystemProgram4,
11462
+ Transaction as Transaction4
11463
+ } from "@solana/web3.js";
11464
+ import { TOKEN_PROGRAM_ID as TOKEN_PROGRAM_ID3, decodeTransferInstruction, getAssociatedTokenAddressSync as getAssociatedTokenAddressSync3 } from "@solana/spl-token";
11465
+ function validatePaymasterTopUpTransaction(tx, expect) {
11466
+ const ixs = tx.instructions;
11467
+ if (ixs.length !== 1 && ixs.length !== 2) {
11468
+ throw new Error(
11469
+ `paymaster top-up must be exactly 1 or 2 instructions, got ${ixs.length} \u2014 refusing to sign a transaction whose shape does not match the documented top-up contract`
11470
+ );
11471
+ }
11472
+ const ix0 = ixs[0];
11473
+ if (!ix0.programId.equals(SystemProgram4.programId)) {
11474
+ throw new Error(
11475
+ `paymaster top-up instruction 0 is owned by ${ix0.programId.toBase58()}, not the System Program \u2014 refusing to sign an unrecognised first instruction`
11476
+ );
11477
+ }
11478
+ const transfer2 = SystemInstruction.decodeTransfer(ix0);
11479
+ if (!transfer2.toPubkey.equals(expect.recipient)) {
11480
+ throw new Error(
11481
+ `paymaster top-up sends SOL to ${transfer2.toPubkey.toBase58()}, which is not our recipient ${expect.recipient.toBase58()} \u2014 refusing to sign a transaction that funds a key we do not control`
11482
+ );
11483
+ }
11484
+ if (BigInt(transfer2.lamports) > expect.maxGrantLamports) {
11485
+ throw new Error(
11486
+ `paymaster top-up grants ${transfer2.lamports} lamports, more than the ${expect.maxGrantLamports} we asked for \u2014 refusing a transaction that funds beyond our own request`
11487
+ );
11488
+ }
11489
+ if (ixs.length === 1) return;
11490
+ const ix1 = ixs[1];
11491
+ if (!ix1.programId.equals(TOKEN_PROGRAM_ID3)) {
11492
+ throw new Error(
11493
+ `paymaster top-up instruction 1 is owned by ${ix1.programId.toBase58()}, not the SPL Token program \u2014 refusing to sign an unrecognised second instruction`
11494
+ );
11495
+ }
11496
+ const spl = decodeTransferInstruction(ix1, TOKEN_PROGRAM_ID3);
11497
+ const authority = spl.keys.owner.pubkey;
11498
+ if (!authority.equals(expect.recipient)) {
11499
+ throw new Error(
11500
+ `paymaster top-up's fee transfer is authorised by ${authority.toBase58()}, not our recipient ${expect.recipient.toBase58()} \u2014 refusing to sign away tokens from an account we do not control`
11501
+ );
11502
+ }
11503
+ const expectedDestination = getAssociatedTokenAddressSync3(
11504
+ expect.feeMint,
11505
+ expect.paymentAddress,
11506
+ false
11507
+ );
11508
+ const destination = spl.keys.destination.pubkey;
11509
+ if (!destination.equals(expectedDestination)) {
11510
+ throw new Error(
11511
+ `paymaster top-up's fee transfer is addressed to ${destination.toBase58()}, not the paymaster's own payment ATA ${expectedDestination.toBase58()} \u2014 refusing to sign a payment to an unverified destination`
11512
+ );
11513
+ }
11514
+ const paidAmount = BigInt(spl.data.amount);
11515
+ if (paidAmount !== expect.feeTokenAmount) {
11516
+ throw new Error(
11517
+ `paymaster top-up's fee amount is ${paidAmount}, not the ${expect.feeTokenAmount} Kora quoted at prepare time \u2014 refusing to sign a payment that does not match the quote`
11518
+ );
11519
+ }
11520
+ }
11521
+ async function parseBridgeResponse2(res, what) {
11522
+ let body;
11523
+ try {
11524
+ body = await res.json();
11525
+ } catch {
11526
+ throw new Error(`${what} failed: ${res.status} ${res.statusText} (response body was not JSON)`);
11527
+ }
11528
+ if (!res.ok) {
11529
+ const b = body ?? {};
11530
+ throw new Error(
11531
+ `${what} failed: ${b.message ?? res.statusText} (${b.code ?? res.status}${b.retryable ? ", retryable" : ""})`
11532
+ );
11533
+ }
11534
+ return body;
11535
+ }
11536
+ async function fundReceiverViaPaymaster(relayUrl, receiver, grantLamports) {
11537
+ assertAllowedRelayOrigin(relayUrl);
11538
+ if (grantLamports <= 0n || grantLamports > BigInt(Number.MAX_SAFE_INTEGER)) {
11539
+ throw new Error(
11540
+ `grantLamports must be a positive value representable as a JS number, got ${grantLamports}`
11541
+ );
11542
+ }
11543
+ const prepareRes = await relayFetch(`${relayUrl}/bridge/paymaster/prepare`, {
11544
+ method: "POST",
11545
+ headers: { "Content-Type": "application/json" },
11546
+ body: JSON.stringify({
11547
+ recipient: receiver.publicKey.toBase58(),
11548
+ grant_lamports: Number(grantLamports)
11549
+ })
11550
+ });
11551
+ const prepared = await parseBridgeResponse2(prepareRes, "paymaster prepare");
11552
+ if (Math.floor(Date.now() / 1e3) >= prepared.expires_at_unix) {
11553
+ throw new Error(
11554
+ "paymaster prepare returned a voucher that is already expired \u2014 refusing to sign a stale top-up rather than fail confusingly at cosign"
11555
+ );
11556
+ }
11557
+ const feeMint = new PublicKey14(prepared.fee_mint);
11558
+ const paymentAddress = new PublicKey14(prepared.payment_address);
11559
+ const feeTokenAmount = BigInt(prepared.fee_token_amount);
11560
+ const tx = Transaction4.from(Buffer.from(prepared.transaction, "base64"));
11561
+ validatePaymasterTopUpTransaction(tx, {
11562
+ recipient: receiver.publicKey,
11563
+ maxGrantLamports: grantLamports,
11564
+ feeMint,
11565
+ feeTokenAmount,
11566
+ paymentAddress
11567
+ });
11568
+ tx.partialSign(receiver);
11569
+ const cosignRes = await relayFetch(`${relayUrl}/bridge/paymaster/cosign`, {
11570
+ method: "POST",
11571
+ headers: { "Content-Type": "application/json" },
11572
+ body: JSON.stringify({
11573
+ transaction: tx.serialize({ requireAllSignatures: false, verifySignatures: false }).toString("base64"),
11574
+ voucher: prepared.voucher
11575
+ })
11576
+ });
11577
+ const cosigned = await parseBridgeResponse2(cosignRes, "paymaster cosign");
11578
+ return {
11579
+ transaction: Transaction4.from(Buffer.from(cosigned.transaction, "base64")),
11580
+ feeTokenAmount,
11581
+ feeMint: prepared.fee_mint,
11582
+ paymentAddress: prepared.payment_address
11583
+ };
11584
+ }
11585
+
11586
+ // src/bridge/derive.ts
11587
+ import { Keypair as Keypair6 } from "@solana/web3.js";
11588
+ import { hmac } from "@noble/hashes/hmac";
11589
+ import { sha512 } from "@noble/hashes/sha512";
11590
+ var BRIDGE_ESCROW_LABEL = "cloak_bridge_escrow";
11591
+ var MAX_RECEIVER_INDEX = 1e4;
11592
+ function deriveBridgeReceiver(nk, index) {
11593
+ if (!Number.isInteger(index) || index < 0) {
11594
+ throw new Error(`index must be a non-negative integer, got ${index}`);
11595
+ }
11596
+ if (index > MAX_RECEIVER_INDEX) {
11597
+ throw new Error(
11598
+ `index ${index} exceeds MAX_RECEIVER_INDEX (${MAX_RECEIVER_INDEX}). Indices must be a small sequential counter \u2014 discovery derives 0\u2026N and reads each on chain, so a timestamp index is unreachable by any scan and the deposit would be invisible to every device but this one.`
11599
+ );
11600
+ }
11601
+ const h = hmac(sha512, new Uint8Array(nk), new TextEncoder().encode(`${BRIDGE_ESCROW_LABEL}:${index}`));
11602
+ return Keypair6.fromSeed(h.slice(0, 32));
11603
+ }
11604
+
11605
+ // src/bridge/discover.ts
11606
+ import { SolanaJSONRPCError } from "@solana/web3.js";
11607
+ import { getAssociatedTokenAddressSync as getAssociatedTokenAddressSync4 } from "@solana/spl-token";
11608
+ function describeError(err) {
11609
+ return err instanceof Error ? err.message : String(err);
11610
+ }
11611
+ async function listBridgeDeposits(conn, nk, opts) {
11612
+ const scanDepth = Math.min(opts.scanDepth ?? 20, MAX_RECEIVER_INDEX + 1);
11613
+ const stopAfterUnused = opts.stopAfterUnused ?? 5;
11614
+ const out = [];
11615
+ let consecutiveUnused = 0;
11616
+ for (let index = 0; index < scanDepth; index++) {
11617
+ const kp = deriveBridgeReceiver(nk, index);
11618
+ const receiver = kp.publicKey;
11619
+ const tokenAccount = getAssociatedTokenAddressSync4(opts.mint, receiver, false);
11620
+ try {
11621
+ const [recvInfo, ataInfo] = await conn.getMultipleAccountsInfo([receiver, tokenAccount]);
11622
+ const lamports = recvInfo?.lamports ?? 0;
11623
+ let tokenBalance = 0n;
11624
+ if (ataInfo) {
11625
+ try {
11626
+ tokenBalance = BigInt((await conn.getTokenAccountBalance(tokenAccount)).value.amount);
11627
+ } catch (err) {
11628
+ if (!(err instanceof SolanaJSONRPCError && /could not find account/i.test(err.message))) {
11629
+ throw err;
11630
+ }
11631
+ }
11632
+ }
11633
+ if (lamports === 0 && !ataInfo) {
11634
+ consecutiveUnused++;
11635
+ if (consecutiveUnused >= stopAfterUnused) break;
11636
+ continue;
11637
+ }
11638
+ consecutiveUnused = 0;
11639
+ const shieldSignatures = [];
11640
+ const sigs = await conn.getSignaturesForAddress(receiver, { limit: 20 });
11641
+ for (const s of sigs) {
11642
+ if (s.err) continue;
11643
+ const tx = await conn.getTransaction(s.signature, { maxSupportedTransactionVersion: 0 });
11644
+ const keys = tx?.transaction.message.getAccountKeys({
11645
+ accountKeysFromLookups: tx.meta?.loadedAddresses
11646
+ });
11647
+ if (keys && [...Array(keys.length).keys()].some((i) => keys.get(i)?.equals(opts.programId))) {
11648
+ shieldSignatures.push(s.signature);
11649
+ }
11650
+ }
11651
+ const shieldSignature = shieldSignatures[0];
11652
+ const indexReused = shieldSignatures.length > 1;
11653
+ let state;
11654
+ if (tokenBalance > 0n) state = shieldSignature ? "needs-cleanup" : "arrived";
11655
+ else if (shieldSignature) state = ataInfo ? "needs-cleanup" : "complete";
11656
+ else state = "awaiting";
11657
+ out.push({
11658
+ index,
11659
+ receiver,
11660
+ tokenAccount,
11661
+ state,
11662
+ tokenBalance,
11663
+ lamports,
11664
+ shieldSignature,
11665
+ ...indexReused ? { indexReused, shieldSignatures } : {}
11666
+ });
11667
+ } catch (err) {
11668
+ out.push({
11669
+ index,
11670
+ receiver,
11671
+ tokenAccount,
11672
+ state: "unknown",
11673
+ tokenBalance: 0n,
11674
+ lamports: 0,
11675
+ error: describeError(err)
11676
+ });
11677
+ }
11678
+ }
11679
+ return out;
11680
+ }
11681
+
11682
+ // src/bridge/deposit-core.ts
11683
+ import {
11684
+ SystemProgram as SystemProgram5,
11685
+ Transaction as Transaction5,
11686
+ VersionedTransaction as VersionedTransaction2,
11687
+ sendAndConfirmTransaction as sendAndConfirmTransaction2
11688
+ } from "@solana/web3.js";
11689
+ import nacl8 from "tweetnacl";
11690
+
11691
+ // src/bridge/funder.ts
11692
+ import { PublicKey as PublicKey16 } from "@solana/web3.js";
11693
+ import { getAssociatedTokenAddressSync as getAssociatedTokenAddressSync5 } from "@solana/spl-token";
11694
+ var MAINNET_RENT_0 = 890880;
11695
+ var MAINNET_RENT_1 = 897840;
11696
+ var FEE_BUDGET = 13e4;
11697
+ var CLEANUP_FEE_BUDGET = 5e3;
11698
+ var pda = (seeds, programId) => PublicKey16.findProgramAddressSync(seeds.map((s) => Buffer.from(s)), programId)[0];
11699
+ function deriveFundingTargets(d) {
11700
+ const pool = pda([Buffer.from("pool"), d.mint.toBuffer()], d.programId);
11701
+ return {
11702
+ pool,
11703
+ // program: NullifierAccount::derive_pda_with_pool -> [b"nullifier", pool, nullifier]
11704
+ nullifier0: pda([Buffer.from("nullifier"), pool.toBuffer(), d.nullifiers[0]], d.programId),
11705
+ nullifier1: pda([Buffer.from("nullifier"), pool.toBuffer(), d.nullifiers[1]], d.programId),
11706
+ // relay: derive_risk_nonce_pda -> [b"risk_nonce", bind0]
11707
+ riskNonce: pda([Buffer.from("risk_nonce"), d.bind0], d.programId),
11708
+ depositorAta: getAssociatedTokenAddressSync5(d.mint, d.depositor, false)
11709
+ };
11710
+ }
11711
+ async function readRent(conn) {
11712
+ const [zero, one] = await Promise.all([
11713
+ conn.getMinimumBalanceForRentExemption(0),
11714
+ conn.getMinimumBalanceForRentExemption(1)
11715
+ ]);
11716
+ return { zero, one };
11717
+ }
11718
+
11719
+ // src/bridge/deposit-core.ts
11720
+ var DepositError = class extends Error {
11721
+ constructor(message, fundedAccounts, measuredTxSize) {
11722
+ super(message);
11723
+ this.fundedAccounts = fundedAccounts;
11724
+ this.measuredTxSize = measuredTxSize;
11725
+ this.name = "DepositError";
11726
+ }
11727
+ };
11728
+ async function depositFromDerivedKey(conn, R, funder, amount, log = console.log, grantOverride, opts) {
11729
+ if (!opts.noteSpendKey || opts.noteSpendKey.length !== 32) {
11730
+ throw new Error(
11731
+ "opts.noteSpendKey (32 bytes) is required. It derives the note's spending key, and a note whose key is not derived from something you can reproduce is unspendable forever. Pass the same Cloak seed you derived the receiving address from."
11732
+ );
11733
+ }
11734
+ if (!opts.relayUrl) {
11735
+ throw new Error(
11736
+ "opts.relayUrl is required. There is no default: a missing relay used to resolve silently to the local stack, which is how a live run built a deposit against infrastructure that only existed on someone's laptop. Say which relay, every call."
11737
+ );
11738
+ }
11739
+ if (!opts.programId) {
11740
+ throw new Error(
11741
+ "opts.programId is required. There is no default: this is the exact field a live mainnet run once inherited from this module's local-stack constant, built a deposit against a program that does not exist on mainnet, and failed hunting a merkle tree that was never there."
11742
+ );
11743
+ }
11744
+ if (!opts.mint) {
11745
+ throw new Error(
11746
+ "opts.mint is required. There is no default: a deposit built for the wrong mint produces a note for an asset that never arrived at R, and there is nothing to silently fall back to."
11747
+ );
11748
+ }
11749
+ assertAllowedRpcConnection(conn);
11750
+ const relayUrl = opts.relayUrl;
11751
+ const programId = opts.programId;
11752
+ const mint = opts.mint;
11753
+ setCircuitsPath(resolveCircuitsBase());
11754
+ const grantR = grantOverride ?? MAINNET_RENT_0 + FEE_BUDGET + CLEANUP_FEE_BUDGET;
11755
+ const heldByR = await conn.getBalance(R.publicKey);
11756
+ if (heldByR >= grantR) {
11757
+ log(` R already holds ${heldByR} (>= ${grantR}) \u2014 no top-up needed`);
11758
+ } else {
11759
+ await sendAndConfirmTransaction2(conn, new Transaction5().add(
11760
+ SystemProgram5.transfer({ fromPubkey: funder.publicKey, toPubkey: R.publicKey, lamports: grantR - heldByR })
11761
+ ), [funder]);
11762
+ log(` funder topped R up by ${grantR - heldByR} to ${grantR}${grantOverride !== void 0 ? " (OVERRIDDEN for a deliberate test)" : " \u2014 floor + deposit fee + cleanup fee, no rent"}`);
11763
+ }
11764
+ let measuredSize = -1;
11765
+ let fundedAccounts = [];
11766
+ const signTransaction2 = async (tx) => {
11767
+ if (tx instanceof VersionedTransaction2) {
11768
+ measuredSize = tx.serialize().length;
11769
+ const alts = await Promise.all(
11770
+ tx.message.addressTableLookups.map(async (l) => (await conn.getAddressLookupTable(l.accountKey)).value)
11771
+ );
11772
+ const keys = tx.message.getAccountKeys({ addressLookupTableAccounts: alts });
11773
+ const ix = tx.message.compiledInstructions.find((i) => keys.get(i.programIdIndex).equals(programId));
11774
+ const at = (n) => keys.get(ix.accountKeyIndexes[n]);
11775
+ const targets = [
11776
+ { a: at(12), need: MAINNET_RENT_0, name: "risk_nonce" },
11777
+ { a: at(4), need: MAINNET_RENT_1, name: "nullifier_0" },
11778
+ { a: at(5), need: MAINNET_RENT_1, name: "nullifier_1" }
11779
+ ];
11780
+ const infos = await conn.getMultipleAccountsInfo(targets.map((t) => t.a));
11781
+ fundedAccounts = targets.map((t, i) => ({
11782
+ name: t.name,
11783
+ address: t.a.toBase58(),
11784
+ lamportsSent: Math.max(0, t.need - (infos[i]?.lamports ?? 0))
11785
+ }));
11786
+ const ixs = targets.flatMap((t, i) => (infos[i]?.lamports ?? 0) >= t.need ? [] : [SystemProgram5.transfer({
11787
+ fromPubkey: funder.publicKey,
11788
+ toPubkey: t.a,
11789
+ lamports: t.need - (infos[i]?.lamports ?? 0)
11790
+ })]);
11791
+ if (ixs.length) {
11792
+ const sig = await sendAndConfirmTransaction2(conn, new Transaction5().add(...ixs), [funder]);
11793
+ log(` seam: funded ${ixs.length} program accounts read off the built tx \u2014 ${sig.slice(0, 16)}\u2026`);
11794
+ }
11795
+ tx.sign([R]);
11796
+ return tx;
11797
+ }
11798
+ tx.partialSign(R);
11799
+ return tx;
11800
+ };
11801
+ const signMessage = async (m) => nacl8.sign.detached(m, R.secretKey);
11802
+ const utxoKeypair = await deriveUtxoKeypairFromSpendKey(opts.noteSpendKey);
11803
+ const nk = getNkFromUtxoPrivateKey(utxoKeypair.privateKey);
11804
+ const { utxo, noteSalt } = await createRecoverableDepositUtxo(amount, nk, mint);
11805
+ const zeroInput = await createZeroUtxo(mint);
11806
+ const rBefore = await conn.getBalance(R.publicKey);
11807
+ let result;
11808
+ try {
11809
+ result = await transact(
11810
+ { inputUtxos: [zeroInput], outputUtxos: [utxo], externalAmount: amount, depositor: R.publicKey },
11811
+ {
11812
+ connection: conn,
11813
+ programId,
11814
+ relayUrl,
11815
+ // NO depositorKeypair: the SDK branches `if (keypair) … else if (signTransaction)`
11816
+ // (transact.ts:2786, :2936), so passing one silently skips the funding seam.
11817
+ depositorPublicKey: R.publicKey,
11818
+ walletPublicKey: R.publicKey,
11819
+ signTransaction: signTransaction2,
11820
+ signMessage,
11821
+ chainNoteViewingKeyNk: nk,
11822
+ chainNoteSalt: noteSalt,
11823
+ relaySupplementalAlt: true,
11824
+ onProgress: (s) => log(` \xB7 ${s}`)
11825
+ }
11826
+ );
11827
+ } catch (e) {
11828
+ const msg = e instanceof Error ? e.message : String(e);
11829
+ throw new DepositError(msg, fundedAccounts, measuredSize);
11830
+ }
11831
+ const rAfter = await conn.getBalance(R.publicKey);
11832
+ const noteIndex = result.outputUtxos[0].index;
11833
+ if (noteIndex === void 0) throw new Error("deposit landed but the note has no index \u2014 it cannot be discovered later");
11834
+ return {
11835
+ signature: result.signature,
11836
+ noteIndex,
11837
+ amount: result.outputUtxos[0].amount,
11838
+ txSize: measuredSize,
11839
+ rBefore,
11840
+ rAfter,
11841
+ rentExempt: rAfter >= MAINNET_RENT_0,
11842
+ inputNullifiers: result.inputNullifiers,
11843
+ fundedAccounts
11844
+ };
11845
+ }
11846
+
11847
+ // src/bridge/cleanup.ts
11848
+ import {
11849
+ PublicKey as PublicKey18,
11850
+ Transaction as Transaction6,
11851
+ sendAndConfirmTransaction as sendAndConfirmTransaction3
11852
+ } from "@solana/web3.js";
11853
+ import {
11854
+ getAssociatedTokenAddressSync as getAssociatedTokenAddressSync6,
11855
+ createCloseAccountInstruction,
11856
+ createTransferInstruction,
11857
+ createAssociatedTokenAccountIdempotentInstruction,
11858
+ getMinimumBalanceForRentExemptAccount
11859
+ } from "@solana/spl-token";
11860
+ var DEFAULT_MINT = new PublicKey18("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
11861
+ async function cleanupReceivingAddress(conn, R, dustDestination, mint = DEFAULT_MINT) {
11862
+ assertAllowedRpcConnection(conn);
11863
+ const rAta = getAssociatedTokenAddressSync6(mint, R.publicKey);
11864
+ const info = await conn.getAccountInfo(rAta);
11865
+ if (!info) {
11866
+ return {
11867
+ closed: false,
11868
+ dustSwept: 0n,
11869
+ rentReturned: 0,
11870
+ destination: null,
11871
+ signature: null,
11872
+ note: "no token account \u2014 nothing to recover"
11873
+ };
11874
+ }
11875
+ const bal = BigInt((await conn.getTokenAccountBalance(rAta)).value.amount);
11876
+ const rentHeld = info.lamports;
11877
+ const before = await conn.getBalance(R.publicKey);
11878
+ const ixs = [];
11879
+ let destination = null;
11880
+ if (bal > 0n && !dustDestination) {
11881
+ return {
11882
+ closed: false,
11883
+ dustSwept: 0n,
11884
+ rentReturned: 0,
11885
+ destination: null,
11886
+ signature: null,
11887
+ note: `dusted with ${bal} base units after the deposit; leaving the account open and ${rentHeld.toLocaleString()} lamports of rent unrecovered, because sweeping it would publish a link from this deposit to whoever receives it`
11888
+ };
11889
+ }
11890
+ if (bal > 0n && dustDestination) {
11891
+ const destAta = getAssociatedTokenAddressSync6(mint, dustDestination);
11892
+ const destInfo = await conn.getAccountInfo(destAta);
11893
+ if (!destInfo) {
11894
+ const ataRent = await getMinimumBalanceForRentExemptAccount(conn);
11895
+ if (before < ataRent + CLEANUP_FEE_BUDGET) {
11896
+ throw new Error(
11897
+ `dustDestination ${dustDestination.toBase58()} has no token account for mint ${mint.toBase58()}, and creating one costs ${ataRent} lamports of rent that R does not have: R holds ${before}, which deposit-core.ts reserved only for this transaction's own fee (CLEANUP_FEE_BUDGET = ${CLEANUP_FEE_BUDGET} in funder.ts), not for a fresh account's rent. Fund R with at least ${ataRent + CLEANUP_FEE_BUDGET - before} more lamports first, or choose a destination that already has a token account for this mint.`
11898
+ );
11899
+ }
11900
+ ixs.push(createAssociatedTokenAccountIdempotentInstruction(R.publicKey, destAta, dustDestination, mint));
11901
+ }
11902
+ ixs.push(createTransferInstruction(rAta, destAta, R.publicKey, bal));
11903
+ destination = destAta.toBase58();
11904
+ }
11905
+ ixs.push(createCloseAccountInstruction(rAta, R.publicKey, R.publicKey));
11906
+ const signature = await sendAndConfirmTransaction3(conn, new Transaction6().add(...ixs), [R], {
11907
+ commitment: "confirmed"
11908
+ });
11909
+ const after = await conn.getBalance(R.publicKey);
11910
+ const gone = await conn.getAccountInfo(rAta) === null;
11911
+ return {
11912
+ closed: gone,
11913
+ dustSwept: bal,
11914
+ rentReturned: after - before,
11915
+ destination,
11916
+ signature,
11917
+ note: bal > 0n ? `swept ${bal} base units of dust to the caller-chosen destination and closed, in one transaction \u2014 note this publishes a link from the deposit to that destination` : `closed cleanly, nothing to sweep`
11918
+ };
11919
+ }
11920
+
11921
+ // src/bridge/quote.ts
11922
+ var MIN_DEPOSIT_SPL_BASE_UNITS = 1000000n;
11923
+ var WITHDRAW_FIXED_FEE = 450000n;
11924
+ var WITHDRAW_FEE_BPS = 30n;
11925
+ function withdrawFeeAt(amount, fixedFee, feeBps) {
11926
+ return fixedFee + amount * feeBps / 10000n;
11927
+ }
11928
+ var MAX_FEE_FRACTION = 0.063;
11929
+ var FIXED_ROUND_TRIP = 903000n;
11930
+ var ECONOMIC_MINIMUM = FIXED_ROUND_TRIP * 1000000n / BigInt(Math.round((MAX_FEE_FRACTION - 3e-3) * 1e6));
11931
+ function withdrawFeeFor(amount) {
11932
+ return withdrawFeeAt(amount, WITHDRAW_FIXED_FEE, WITHDRAW_FEE_BPS);
11933
+ }
11934
+ function assessBridgeQuote(q) {
11935
+ if (q.sent < 0n) {
11936
+ throw new Error(`assessBridgeQuote: sent must not be negative (got ${q.sent}).`);
11937
+ }
11938
+ if (q.arrivesMin > q.sent) {
11939
+ throw new Error(
11940
+ `assessBridgeQuote: arrivesMin (${q.arrivesMin}) exceeds sent (${q.sent}) \u2014 no rail returns more than it was given, so this input did not come from a real quote.`
11941
+ );
11942
+ }
11943
+ const shieldedMinRaw = q.arrivesMin - q.paymasterFee;
11944
+ const shieldedTargetRaw = q.arrivesTarget - q.paymasterFee;
11945
+ const shieldedMin = shieldedMinRaw > 0n ? shieldedMinRaw : 0n;
11946
+ const shieldedTarget = shieldedTargetRaw > 0n ? shieldedTargetRaw : 0n;
11947
+ const viable = shieldedMinRaw >= MIN_DEPOSIT_SPL_BASE_UNITS;
11948
+ const economic = q.sent >= ECONOMIC_MINIMUM;
11949
+ const withdrawFee = shieldedMin > 0n ? withdrawFeeAt(shieldedMin, q.liveWithdrawFixedFee ?? WITHDRAW_FIXED_FEE, q.liveWithdrawFeeBps ?? WITHDRAW_FEE_BPS) : 0n;
11950
+ const roundTripCost = q.sent - (shieldedMin > withdrawFee ? shieldedMin - withdrawFee : 0n);
11951
+ const roundTripFraction = q.sent > 0n ? Number(roundTripCost) / Number(q.sent) : 0;
11952
+ const reasons = [];
11953
+ if (!viable) {
11954
+ const short = MIN_DEPOSIT_SPL_BASE_UNITS - shieldedMinRaw;
11955
+ reasons.push(
11956
+ (shieldedMinRaw <= 0n ? `The ${fmt(q.paymasterFee)} paymaster fee alone is ${shieldedMinRaw < 0n ? `${fmt(-shieldedMinRaw)} more than` : "all of"} the ${fmt(q.arrivesMin)} the rail guarantees, so nothing would be left to shield.` : `Only ${fmt(shieldedMinRaw)} would be left to shield after the ${fmt(q.paymasterFee)} paymaster fee.`) + ` The program's minimum deposit is ${fmt(MIN_DEPOSIT_SPL_BASE_UNITS)}. Send at least ${fmt(q.sent + short)} to clear it.`
11957
+ );
11958
+ }
11959
+ if (viable && !economic) {
11960
+ reasons.push(
11961
+ `This works, but ${fmt(roundTripCost)} of ${fmt(q.sent)} goes to fees (${(roundTripFraction * 100).toFixed(0)}%). The costs are almost all fixed, so bridging ${fmt(ECONOMIC_MINIMUM)} or more spreads them much further.`
11962
+ );
11963
+ }
11964
+ return {
11965
+ ...q,
11966
+ shieldedMin,
11967
+ shieldedTarget,
11968
+ viable,
11969
+ economic,
11970
+ withdrawFee,
11971
+ roundTripCost,
11972
+ roundTripFraction,
11973
+ reasons
11974
+ };
11975
+ }
11976
+ var fmt = (v) => `${(Number(v) / 1e6).toFixed(6)} USDC`;
11977
+ function renderAssessment(a) {
11978
+ const L = [];
11979
+ L.push(` you send ${fmt(a.sent)}`);
11980
+ L.push(` arrives at least ${fmt(a.arrivesMin)} (target ${fmt(a.arrivesTarget)}, not a promise)`);
11981
+ L.push(` paymaster fee ${fmt(a.paymasterFee)} buys the receiver its SOL, so your wallet stays off chain`);
11982
+ L.push(` SHIELDED ${fmt(a.shieldedMin)} at least \u2014 this is what you end up with`);
11983
+ L.push(` to withdraw later ${fmt(a.withdrawFee)} the program's fee, whenever you take it out`);
11984
+ for (const r of a.reasons) L.push(`
11985
+ ${a.viable ? "NOTE" : "REFUSED"}: ${r}`);
11986
+ return L.join("\n");
11987
+ }
11988
+
11989
+ // src/bridge/retry.ts
11990
+ var TERMINAL_FAILURE = /DepositTooSmall|minimum is 1\.000000|insufficient|holds only|below the program|kora wants|over the .* ceiling/i;
11991
+ function isTerminalFailure(message) {
11992
+ return TERMINAL_FAILURE.test(message);
11993
+ }
11994
+ function classifyPostFailure(attempted, after) {
11995
+ if (after === null) return "unknown";
11996
+ if (after === 0n) return "landed";
11997
+ if (after >= attempted) return "did-not-land";
11998
+ return "unknown";
11999
+ }
12000
+ function mayRetry(verdict, message) {
12001
+ return verdict === "did-not-land" && !isTerminalFailure(message);
12002
+ }
12003
+ function retryDelayMs(attempt) {
12004
+ return 15e3 * Math.max(0, attempt - 1);
12005
+ }
12006
+ var DoNotRetry = class extends Error {
12007
+ constructor(message) {
12008
+ super(message);
12009
+ this.name = "DoNotRetry";
12010
+ }
12011
+ };
12012
+ async function withShieldRetries(attempt, hooks = {}) {
12013
+ const attempts = hooks.attempts ?? 3;
12014
+ const sleep4 = hooks.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
12015
+ let last = "";
12016
+ for (let n = 1; n <= attempts; n++) {
12017
+ if (n > 1) {
12018
+ const delay = retryDelayMs(n);
12019
+ hooks.onRetry?.(n, delay, last);
12020
+ await sleep4(delay);
12021
+ }
12022
+ try {
12023
+ return await attempt(n);
12024
+ } catch (e) {
12025
+ if (e instanceof DoNotRetry) throw e;
12026
+ last = e instanceof Error ? e.message : String(e);
12027
+ if (isTerminalFailure(last) || n === attempts) throw e;
12028
+ }
12029
+ }
12030
+ throw new Error("unreachable");
12031
+ }
12032
+
10860
12033
  // src/index.ts
10861
- var VERSION = "0.2.1";
12034
+ var VERSION = "0.2.3";
10862
12035
  var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
10863
12036
  export {
12037
+ BRIDGE_ESCROW_LABEL,
10864
12038
  BUILD_ALLOWS_LOCAL_ENDPOINTS,
10865
12039
  CHAIN_NOTE_SALT_BITS,
12040
+ CLEANUP_FEE_BUDGET,
10866
12041
  CLOAK_PRODUCTION_RELAY_URL,
10867
12042
  CLOAK_PROGRAM_ID,
10868
12043
  CloakError,
@@ -10870,25 +12045,38 @@ export {
10870
12045
  DEFAULT_TRANSACTION_CIRCUITS_URL,
10871
12046
  DELIVERY_MEMO_TAG,
10872
12047
  DELIVERY_REGISTRY_SEED,
12048
+ DepositError,
12049
+ DoNotRetry,
12050
+ ECONOMIC_MINIMUM,
10873
12051
  EXPECTED_CIRCUIT_HASHES,
12052
+ FEE_BUDGET,
10874
12053
  FIXED_FEE_LAMPORTS,
10875
12054
  InsecureRandomnessError,
10876
12055
  LAMPORTS_PER_SOL,
10877
12056
  LocalStorageAdapter,
12057
+ MAINNET_RENT_0,
12058
+ MAINNET_RENT_1,
12059
+ MAX_RECEIVER_INDEX,
10878
12060
  MERKLE_TREE_HEIGHT2 as MERKLE_TREE_HEIGHT,
10879
12061
  MIN_DEPOSIT_LAMPORTS,
12062
+ MIN_DEPOSIT_SPL_BASE_UNITS,
10880
12063
  MemoryStorageAdapter,
10881
12064
  MerkleTree,
10882
12065
  NATIVE_SOL_MINT,
12066
+ ONECLICK_PUBKEY_B58,
10883
12067
  RECIPIENT_DELIVERY_CIPHERTEXT_LEN,
10884
12068
  RECIPIENT_DELIVERY_EPHEMERAL_PK_LEN,
10885
12069
  RECIPIENT_DELIVERY_NONCE_LEN,
10886
12070
  RECIPIENT_DELIVERY_NOTE_BYTES,
10887
12071
  RECIPIENT_DELIVERY_PLAINTEXT_LEN,
10888
12072
  RECIPIENT_DELIVERY_TAG_LEN,
12073
+ RELAY_BATCH_AUTH_MAX_ITEMS,
10889
12074
  RELAY_ORIGIN_ALLOWLIST,
12075
+ REQUEST_AUTH_BATCH_DOMAIN,
12076
+ REQUEST_AUTH_BATCH_MAX_AGE_SECONDS,
10890
12077
  REQUEST_AUTH_MAX_AGE_SECONDS,
10891
12078
  REQUEST_AUTH_MAX_FUTURE_SKEW_SECONDS,
12079
+ RelayBatchAuthCoordinator,
10892
12080
  RelayInternalError,
10893
12081
  RelayService,
10894
12082
  RootNotFoundError,
@@ -10898,6 +12086,7 @@ export {
10898
12086
  SettlementVerificationError,
10899
12087
  ShieldPoolErrors,
10900
12088
  SimpleWallet,
12089
+ TERMINAL_FAILURE,
10901
12090
  TRANSACTION_CIRCUITS_VERSION,
10902
12091
  TRANSACT_AUTH_FIELDS,
10903
12092
  TRANSACT_SWAP_AUTH_FIELDS,
@@ -10907,15 +12096,21 @@ export {
10907
12096
  VARIABLE_FEE_NUMERATOR,
10908
12097
  VARIABLE_FEE_RATE,
10909
12098
  VERSION,
12099
+ WITHDRAW_FEE_BPS,
12100
+ WITHDRAW_FIXED_FEE,
10910
12101
  assertDirectSubmissionLanded,
12102
+ assertInputMints,
10911
12103
  assertTransactionCircuitIntegrity,
12104
+ assessBridgeQuote,
10912
12105
  bigintToBytes32,
10913
12106
  bigintToHex,
12107
+ buildAuthTransactionMessage,
10914
12108
  buildMerkleTree,
10915
12109
  buildMerkleTreeFromChain,
10916
12110
  buildMerkleTreeFromRelay,
10917
12111
  buildRecipientDeliveryNotes,
10918
12112
  buildRelayAuthPreimage,
12113
+ buildRelayBatchAuthMessage,
10919
12114
  buildTransactRequestBody,
10920
12115
  bytesToHex,
10921
12116
  calculateFee,
@@ -10923,12 +12118,16 @@ export {
10923
12118
  calculateRelayFee,
10924
12119
  canRebuildMerkleTreeFromChain,
10925
12120
  canonicalJson,
12121
+ canonicalPayloadString,
10926
12122
  chainNoteFromBase64,
10927
12123
  chainNoteToBase64,
12124
+ classifyPostFailure,
10928
12125
  classifyRelayError,
12126
+ cleanupReceivingAddress,
10929
12127
  cleanupStalePendingOperations,
10930
12128
  clearPendingDeposits,
10931
12129
  clearPendingWithdrawals,
12130
+ cloakBridgeRail,
10932
12131
  computeChainNoteHash,
10933
12132
  computeExtDataHash,
10934
12133
  computeMerkleRoot,
@@ -10945,15 +12144,19 @@ export {
10945
12144
  createLogger,
10946
12145
  createRecoverableChangeUtxo,
10947
12146
  createRecoverableDepositUtxo,
12147
+ createRelayBatchAuthCoordinator,
10948
12148
  createUtxo,
10949
12149
  createZeroUtxo,
10950
12150
  decryptCompactChainNote,
10951
12151
  decryptComplianceMetadataWithMasterKey,
10952
12152
  decryptTransactionMetadata,
12153
+ depositFromDerivedKey,
12154
+ deriveBridgeReceiver,
10953
12155
  deriveChangeNoteBlinding,
10954
12156
  deriveDepositNoteSecrets,
10955
12157
  deriveDiversifiedViewingKey,
10956
12158
  deriveDiversifier,
12159
+ deriveFundingTargets,
10957
12160
  deriveInputNullifierPdas,
10958
12161
  derivePublicKey,
10959
12162
  deriveSpendKey,
@@ -10992,6 +12195,7 @@ export {
10992
12195
  formatErrorForLogging,
10993
12196
  formatSol,
10994
12197
  fullWithdraw,
12198
+ fundReceiverViaPaymaster,
10995
12199
  generateCloakKeys,
10996
12200
  generateCommitmentAsync,
10997
12201
  generateMasterSeed,
@@ -11027,18 +12231,21 @@ export {
11027
12231
  isReactNative,
11028
12232
  isRootNotFoundError,
11029
12233
  isSubmissionOutcomeUnknownResponse,
12234
+ isTerminalFailure,
11030
12235
  isValidHex,
11031
12236
  isValidRpcUrl,
11032
12237
  isValidSolanaAddress,
11033
12238
  isWithdrawAmountSufficient,
11034
12239
  isWithdrawable,
11035
12240
  keypairToAdapter,
12241
+ listBridgeDeposits,
11036
12242
  loadPendingDeposits,
11037
12243
  loadPendingWithdrawals,
11038
12244
  loadVerifiedCircuitArtifacts,
11039
12245
  matchChangeNote,
11040
12246
  matchDepositNote,
11041
12247
  matchSwapRefundLeaf,
12248
+ mayRetry,
11042
12249
  openRecipientDeliveryNote,
11043
12250
  parseAmount,
11044
12251
  parseDeliveryCarrierMemo,
@@ -11061,11 +12268,15 @@ export {
11061
12268
  randomDepositNoteSalt,
11062
12269
  randomFieldElement,
11063
12270
  readMerkleTreeState,
12271
+ readRent,
11064
12272
  recipientDeliveryNoteToBase64,
11065
12273
  registerViewingKey,
12274
+ relayRequestDigestHex,
11066
12275
  removePendingDeposit,
11067
12276
  removePendingWithdrawal,
12277
+ renderAssessment,
11068
12278
  resolveCircuitsBase,
12279
+ retryDelayMs,
11069
12280
  savePendingDeposit,
11070
12281
  savePendingWithdrawal,
11071
12282
  scanNotesForWallet,
@@ -11074,10 +12285,12 @@ export {
11074
12285
  sdkLogger,
11075
12286
  selectUtxos,
11076
12287
  sendTransaction,
12288
+ serializeAuthTransactionMessage,
11077
12289
  serializeNote,
11078
12290
  serializeUtxo,
11079
12291
  setCircuitsPath,
11080
12292
  setDebugMode,
12293
+ signRelayAuthPayload,
11081
12294
  signTransaction,
11082
12295
  splitTo2Limbs,
11083
12296
  submitTransactToRelay,
@@ -11086,6 +12299,7 @@ export {
11086
12299
  swapWithChange,
11087
12300
  toComplianceReport,
11088
12301
  transact,
12302
+ transactBatch,
11089
12303
  transfer,
11090
12304
  truncate,
11091
12305
  tryDecryptNote,
@@ -11098,13 +12312,17 @@ export {
11098
12312
  validateDepositParams,
11099
12313
  validateNote,
11100
12314
  validateOutputsSum,
12315
+ validatePaymasterTopUpTransaction,
11101
12316
  validateRoot,
11102
12317
  validateTransfers,
11103
12318
  validateWalletConnected,
11104
12319
  validateWithdrawableNote,
11105
12320
  verifyAllCircuits,
11106
12321
  verifyCircuitIntegrity,
12322
+ verifyQuoteSignature,
11107
12323
  verifyUtxos,
11108
12324
  waitForRoot,
11109
- withTiming
12325
+ withShieldRetries,
12326
+ withTiming,
12327
+ withdrawFeeFor
11110
12328
  };