@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/README.md +17 -11
- package/dist/index.cjs +1620 -378
- package/dist/index.d.cts +1077 -109
- package/dist/index.d.ts +1077 -109
- package/dist/index.js +1545 -327
- package/package.json +1 -1
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
|
|
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 = [
|
|
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 =
|
|
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 =
|
|
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
|
|
1173
|
+
return nacl3.scalarMult(factor, masterCompliancePublicKey);
|
|
905
1174
|
}
|
|
906
|
-
function
|
|
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
|
|
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 =
|
|
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
|
|
938
|
-
|
|
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
|
|
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 =
|
|
1054
|
-
const sharedSecret =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1146
|
-
const sharedSecret =
|
|
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
|
|
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] =
|
|
2352
|
+
const [pool] = PublicKey4.findProgramAddressSync(
|
|
2080
2353
|
[Buffer.from("pool"), mint.toBuffer()],
|
|
2081
2354
|
pid
|
|
2082
2355
|
);
|
|
2083
|
-
const [merkleTree] =
|
|
2356
|
+
const [merkleTree] = PublicKey4.findProgramAddressSync(
|
|
2084
2357
|
[Buffer.from("merkle_tree"), mint.toBuffer()],
|
|
2085
2358
|
pid
|
|
2086
2359
|
);
|
|
2087
|
-
const [treasury] =
|
|
2360
|
+
const [treasury] = PublicKey4.findProgramAddressSync(
|
|
2088
2361
|
[Buffer.from("treasury"), mint.toBuffer()],
|
|
2089
2362
|
pid
|
|
2090
2363
|
);
|
|
2091
|
-
const [vaultAuthority] =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 [
|
|
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 [
|
|
2225
|
-
pdas.push(
|
|
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
|
|
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
|
|
2783
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
3342
|
+
if (!(params.programId instanceof PublicKey7)) {
|
|
3065
3343
|
throw new Error("programId must be a PublicKey");
|
|
3066
3344
|
}
|
|
3067
|
-
if (!(params.payer instanceof
|
|
3345
|
+
if (!(params.payer instanceof PublicKey7)) {
|
|
3068
3346
|
throw new Error("payer must be a PublicKey");
|
|
3069
3347
|
}
|
|
3070
|
-
if (!(params.pool instanceof
|
|
3348
|
+
if (!(params.pool instanceof PublicKey7)) {
|
|
3071
3349
|
throw new Error("pool must be a PublicKey");
|
|
3072
3350
|
}
|
|
3073
|
-
if (!(params.merkleTree instanceof
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
3821
|
-
Transaction as
|
|
4098
|
+
PublicKey as PublicKey10,
|
|
4099
|
+
Transaction as Transaction3,
|
|
3822
4100
|
TransactionInstruction as TransactionInstruction2,
|
|
3823
4101
|
sendAndConfirmTransaction,
|
|
3824
|
-
SystemProgram as
|
|
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
|
|
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 =
|
|
4115
|
-
const shared =
|
|
4116
|
-
const nonce =
|
|
4117
|
-
const ciphertext =
|
|
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 =
|
|
4144
|
-
const plaintext =
|
|
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
|
|
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] =
|
|
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] =
|
|
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
|
|
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
|
|
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] =
|
|
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
|
|
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
|
|
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 ??
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
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 ? "
|
|
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\`
|
|
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(
|
|
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(
|
|
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] =
|
|
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(
|
|
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(
|
|
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] =
|
|
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
|
|
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
|
|
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
|
|
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] =
|
|
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
|
|
10133
|
-
new
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
12325
|
+
withShieldRetries,
|
|
12326
|
+
withTiming,
|
|
12327
|
+
withdrawFeeFor
|
|
11110
12328
|
};
|