@cloak.dev/sdk 0.2.3-staging.f2d7f2a → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +15 -847
- package/dist/index.d.cts +1 -678
- package/dist/index.d.ts +1 -678
- package/dist/index.js +14 -832
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -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 = [CLOAK_PRODUCTION_RELAY_URL];
|
|
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}$/;
|
|
@@ -2150,8 +2150,8 @@ async function verifyUtxos(utxos, connection, programId, commitment = "confirmed
|
|
|
2150
2150
|
const nullifierHex = nullifierBig.toString(16).padStart(64, "0");
|
|
2151
2151
|
const nullifierBytes = Buffer.from(nullifierHex, "hex");
|
|
2152
2152
|
const { pool } = getShieldPoolPDAs(programId, utxo.mintAddress);
|
|
2153
|
-
const [
|
|
2154
|
-
checkable.push({ utxo, pda
|
|
2153
|
+
const [pda] = getNullifierPDA(pool, nullifierBytes, programId);
|
|
2154
|
+
checkable.push({ utxo, pda });
|
|
2155
2155
|
} catch {
|
|
2156
2156
|
skipped.push(utxo);
|
|
2157
2157
|
}
|
|
@@ -2221,8 +2221,8 @@ function deriveInputNullifierPdas(programId, mint, inputNullifiers) {
|
|
|
2221
2221
|
const pdas = [];
|
|
2222
2222
|
for (const nullifier of inputNullifiers) {
|
|
2223
2223
|
if (nullifier === BigInt(0)) continue;
|
|
2224
|
-
const [
|
|
2225
|
-
pdas.push(
|
|
2224
|
+
const [pda] = getNullifierPDA(pool, bigintToBytes324(nullifier), programId);
|
|
2225
|
+
pdas.push(pda);
|
|
2226
2226
|
}
|
|
2227
2227
|
return pdas;
|
|
2228
2228
|
}
|
|
@@ -3543,7 +3543,7 @@ async function fetchWithRetry(url, options = {}) {
|
|
|
3543
3543
|
const {
|
|
3544
3544
|
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
3545
3545
|
maxRetries = DEFAULT_MAX_RETRIES,
|
|
3546
|
-
retryDelayMs
|
|
3546
|
+
retryDelayMs = 1e3
|
|
3547
3547
|
} = options;
|
|
3548
3548
|
assertAllowedRelayOrigin(url);
|
|
3549
3549
|
let lastError = null;
|
|
@@ -3567,7 +3567,7 @@ async function fetchWithRetry(url, options = {}) {
|
|
|
3567
3567
|
`Relay request failed after ${attempt + 1} attempts: ${lastError.message}`
|
|
3568
3568
|
);
|
|
3569
3569
|
}
|
|
3570
|
-
const delay =
|
|
3570
|
+
const delay = retryDelayMs * Math.pow(2, attempt) + Math.random() * 500;
|
|
3571
3571
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
3572
3572
|
}
|
|
3573
3573
|
}
|
|
@@ -7077,7 +7077,7 @@ async function transact(params, options) {
|
|
|
7077
7077
|
// names is refused.
|
|
7078
7078
|
maxRootRetries = 40,
|
|
7079
7079
|
// Increased to handle prolonged relay sync recovery under high concurrency
|
|
7080
|
-
retryDelayMs
|
|
7080
|
+
retryDelayMs = 500,
|
|
7081
7081
|
// Start with short delay, will exponentially back off
|
|
7082
7082
|
riskOracleQueue,
|
|
7083
7083
|
riskQuoteUrl: riskQuoteUrlOption,
|
|
@@ -7499,7 +7499,7 @@ async function transact(params, options) {
|
|
|
7499
7499
|
while (submissionAttempts <= maxRootRetries) {
|
|
7500
7500
|
const isRetry = submissionAttempts > 0;
|
|
7501
7501
|
if (isRetry) {
|
|
7502
|
-
const exponentialDelay = Math.min(
|
|
7502
|
+
const exponentialDelay = Math.min(retryDelayMs * Math.pow(1.5, submissionAttempts - 1), 5e3);
|
|
7503
7503
|
const jitter = Math.random() * 500;
|
|
7504
7504
|
const totalDelay = Math.floor(exponentialDelay + jitter);
|
|
7505
7505
|
await sleep2(totalDelay);
|
|
@@ -7662,7 +7662,7 @@ async function transact(params, options) {
|
|
|
7662
7662
|
}
|
|
7663
7663
|
treeState = null;
|
|
7664
7664
|
useSiblingInfo = false;
|
|
7665
|
-
const waitMs = Math.min(
|
|
7665
|
+
const waitMs = Math.min(retryDelayMs * Math.pow(2, Math.min(submissionAttempts, 4)), 5e3);
|
|
7666
7666
|
if (waitMs > 0) {
|
|
7667
7667
|
onProgress?.(`Waiting ${waitMs}ms for relay sync before retry...`);
|
|
7668
7668
|
await new Promise((r) => setTimeout(r, waitMs));
|
|
@@ -8356,7 +8356,7 @@ async function swapUtxo(params, options) {
|
|
|
8356
8356
|
riskQuoteUrl: riskQuoteUrlOption,
|
|
8357
8357
|
maxRootRetries = 40,
|
|
8358
8358
|
// Increased to handle prolonged relay sync recovery under high concurrency
|
|
8359
|
-
retryDelayMs
|
|
8359
|
+
retryDelayMs = 500,
|
|
8360
8360
|
// Start with short delay, will exponentially back off
|
|
8361
8361
|
useUniqueNullifiers,
|
|
8362
8362
|
useChainRootForProof = true
|
|
@@ -8767,7 +8767,7 @@ async function swapUtxo(params, options) {
|
|
|
8767
8767
|
let walletApprovals = 0;
|
|
8768
8768
|
for (let attempt = 0; attempt <= maxRootRetries; attempt++) {
|
|
8769
8769
|
if (attempt > 0) {
|
|
8770
|
-
const exponentialDelay = Math.min(
|
|
8770
|
+
const exponentialDelay = Math.min(retryDelayMs * Math.pow(1.5, attempt - 1), 5e3);
|
|
8771
8771
|
const jitter = Math.random() * 500;
|
|
8772
8772
|
const totalDelay = Math.floor(exponentialDelay + jitter);
|
|
8773
8773
|
await sleep2(totalDelay);
|
|
@@ -8905,7 +8905,7 @@ async function swapUtxo(params, options) {
|
|
|
8905
8905
|
}
|
|
8906
8906
|
merkleState = null;
|
|
8907
8907
|
useSiblingInfo = false;
|
|
8908
|
-
const waitMs = Math.min(
|
|
8908
|
+
const waitMs = Math.min(retryDelayMs * Math.pow(2, Math.min(attempt, 4)), 5e3);
|
|
8909
8909
|
if (waitMs > 0) {
|
|
8910
8910
|
onProgress?.(`Waiting ${waitMs}ms for relay sync before retry...`);
|
|
8911
8911
|
await new Promise((r) => setTimeout(r, waitMs));
|
|
@@ -10881,799 +10881,12 @@ var SimpleWallet = class {
|
|
|
10881
10881
|
}
|
|
10882
10882
|
};
|
|
10883
10883
|
|
|
10884
|
-
// src/bridge/rail-verify.ts
|
|
10885
|
-
import nacl7 from "tweetnacl";
|
|
10886
|
-
import { sha256 as sha2565 } from "@noble/hashes/sha256";
|
|
10887
|
-
var ONECLICK_PUBKEY_B58 = "reYaWhvwu8Jzo3WUM3zhn6VrhuMEF4eADL17qtRVifc";
|
|
10888
|
-
var b58 = /* @__PURE__ */ (() => {
|
|
10889
|
-
const A = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
10890
|
-
return {
|
|
10891
|
-
decode(s) {
|
|
10892
|
-
let n = 0n;
|
|
10893
|
-
for (const c of s) {
|
|
10894
|
-
const i = A.indexOf(c);
|
|
10895
|
-
if (i < 0) throw new Error("bad base58");
|
|
10896
|
-
n = n * 58n + BigInt(i);
|
|
10897
|
-
}
|
|
10898
|
-
const bytes = [];
|
|
10899
|
-
while (n > 0n) {
|
|
10900
|
-
bytes.unshift(Number(n & 255n));
|
|
10901
|
-
n >>= 8n;
|
|
10902
|
-
}
|
|
10903
|
-
for (const c of s) {
|
|
10904
|
-
if (c === "1") bytes.unshift(0);
|
|
10905
|
-
else break;
|
|
10906
|
-
}
|
|
10907
|
-
return Uint8Array.from(bytes);
|
|
10908
|
-
},
|
|
10909
|
-
encode(b) {
|
|
10910
|
-
let n = 0n;
|
|
10911
|
-
for (const x of b) n = n * 256n + BigInt(x);
|
|
10912
|
-
let s = "";
|
|
10913
|
-
while (n > 0n) {
|
|
10914
|
-
s = A[Number(n % 58n)] + s;
|
|
10915
|
-
n /= 58n;
|
|
10916
|
-
}
|
|
10917
|
-
for (const x of b) {
|
|
10918
|
-
if (x === 0) s = "1" + s;
|
|
10919
|
-
else break;
|
|
10920
|
-
}
|
|
10921
|
-
return s;
|
|
10922
|
-
}
|
|
10923
|
-
};
|
|
10924
|
-
})();
|
|
10925
|
-
function stable(v) {
|
|
10926
|
-
if (v === null || typeof v !== "object") return JSON.stringify(v) ?? "null";
|
|
10927
|
-
if (Array.isArray(v)) return `[${v.map(stable).join(",")}]`;
|
|
10928
|
-
const o = v;
|
|
10929
|
-
const parts = Object.keys(o).sort().filter((k) => o[k] !== void 0).map((k) => `${JSON.stringify(k)}:${stable(o[k])}`);
|
|
10930
|
-
return `{${parts.join(",")}}`;
|
|
10931
|
-
}
|
|
10932
|
-
function signedRequest(r) {
|
|
10933
|
-
const q = r.quoteRequest ?? {};
|
|
10934
|
-
return {
|
|
10935
|
-
dry: q.dry,
|
|
10936
|
-
swapType: q.swapType,
|
|
10937
|
-
slippageTolerance: q.slippageTolerance,
|
|
10938
|
-
originAsset: q.originAsset,
|
|
10939
|
-
depositType: q.depositType,
|
|
10940
|
-
destinationAsset: q.destinationAsset,
|
|
10941
|
-
amount: q.amount,
|
|
10942
|
-
refundTo: q.refundTo,
|
|
10943
|
-
refundType: q.refundType,
|
|
10944
|
-
recipient: q.recipient,
|
|
10945
|
-
recipientType: q.recipientType,
|
|
10946
|
-
deadline: q.deadline,
|
|
10947
|
-
quoteWaitingTimeMs: q.quoteWaitingTimeMs || void 0,
|
|
10948
|
-
referral: q.referral || void 0,
|
|
10949
|
-
virtualChainRecipient: q.virtualChainRecipient || void 0,
|
|
10950
|
-
virtualChainRefundRecipient: q.virtualChainRefundRecipient || void 0,
|
|
10951
|
-
customRecipientMsg: q.customRecipientMsg || void 0
|
|
10952
|
-
// Deliberately unsigned by the rail: sessionId, connectedWallets, correlationId, appFees,
|
|
10953
|
-
// partnerId, userAccountId, depositMode. APP FEES ARE NOT AUTHENTICATED — never display a fee
|
|
10954
|
-
// caption as if the signature vouched for it.
|
|
10955
|
-
};
|
|
10956
|
-
}
|
|
10957
|
-
function signedQuote(r) {
|
|
10958
|
-
const q = r.quote ?? {};
|
|
10959
|
-
const base = {
|
|
10960
|
-
amountIn: q.amountIn,
|
|
10961
|
-
amountInFormatted: q.amountInFormatted,
|
|
10962
|
-
amountInUsd: q.amountInUsd,
|
|
10963
|
-
minAmountIn: q.minAmountIn,
|
|
10964
|
-
amountOut: q.amountOut,
|
|
10965
|
-
amountOutFormatted: q.amountOutFormatted,
|
|
10966
|
-
amountOutUsd: q.amountOutUsd,
|
|
10967
|
-
minAmountOut: q.minAmountOut
|
|
10968
|
-
};
|
|
10969
|
-
if (r.quoteRequest?.dry) return base;
|
|
10970
|
-
return {
|
|
10971
|
-
...base,
|
|
10972
|
-
depositAddress: q.depositAddress || void 0,
|
|
10973
|
-
// <- the field that makes substitution detectable
|
|
10974
|
-
depositMemo: q.depositMemo || void 0,
|
|
10975
|
-
deadline: q.deadline || void 0,
|
|
10976
|
-
// <- a real, SIGNED expiry
|
|
10977
|
-
timeWhenInactive: q.timeWhenInactive || void 0,
|
|
10978
|
-
timeEstimate: q.timeEstimate || void 0,
|
|
10979
|
-
virtualChainRecipient: q.virtualChainRecipient || void 0,
|
|
10980
|
-
virtualChainRefundRecipient: q.virtualChainRefundRecipient || void 0,
|
|
10981
|
-
customRecipientMsg: q.customRecipientMsg || void 0,
|
|
10982
|
-
refundFee: q.refundFee || void 0,
|
|
10983
|
-
withdrawFee: q.withdrawFee || void 0
|
|
10984
|
-
};
|
|
10985
|
-
}
|
|
10986
|
-
function canonicalPayloadString(resp) {
|
|
10987
|
-
return stable({ ...signedRequest(resp), ...signedQuote(resp), timestamp: resp.timestamp });
|
|
10988
|
-
}
|
|
10989
|
-
function verifyQuoteSignature(resp) {
|
|
10990
|
-
if (!resp?.signature) return { valid: false, reason: "no signature on the response" };
|
|
10991
|
-
const payload = { ...signedRequest(resp), ...signedQuote(resp), timestamp: resp.timestamp };
|
|
10992
|
-
const digest = sha2565(new TextEncoder().encode(stable(payload)));
|
|
10993
|
-
const message = new TextEncoder().encode(b58.encode(new Uint8Array(digest)));
|
|
10994
|
-
const sig = resp.signature.replace(/^ed25519:/, "");
|
|
10995
|
-
try {
|
|
10996
|
-
return { valid: nacl7.sign.detached.verify(message, b58.decode(sig), b58.decode(ONECLICK_PUBKEY_B58)) };
|
|
10997
|
-
} catch (e) {
|
|
10998
|
-
return { valid: false, reason: e instanceof Error ? e.message : String(e) };
|
|
10999
|
-
}
|
|
11000
|
-
}
|
|
11001
|
-
|
|
11002
|
-
// src/bridge/rails.ts
|
|
11003
|
-
async function parseBridgeResponse(res, what) {
|
|
11004
|
-
let body;
|
|
11005
|
-
try {
|
|
11006
|
-
body = await res.json();
|
|
11007
|
-
} catch {
|
|
11008
|
-
throw new Error(`${what} failed: ${res.status} ${res.statusText} (response body was not JSON)`);
|
|
11009
|
-
}
|
|
11010
|
-
if (!res.ok) {
|
|
11011
|
-
const b = body ?? {};
|
|
11012
|
-
throw new Error(
|
|
11013
|
-
`${what} failed: ${b.message ?? res.statusText} (${b.code ?? res.status}${b.retryable ? ", retryable" : ""})`
|
|
11014
|
-
);
|
|
11015
|
-
}
|
|
11016
|
-
return body;
|
|
11017
|
-
}
|
|
11018
|
-
function toAttestation(raw) {
|
|
11019
|
-
if (raw.kind === "ed25519") {
|
|
11020
|
-
return { kind: "ed25519", verified: raw.verified ?? false, signer: raw.signer ?? "" };
|
|
11021
|
-
}
|
|
11022
|
-
return { kind: "none", checks: raw.checks ?? [], note: raw.note ?? "" };
|
|
11023
|
-
}
|
|
11024
|
-
async function fetchBridgeQuote(relayUrl, req) {
|
|
11025
|
-
const body = {
|
|
11026
|
-
recipient: req.recipient,
|
|
11027
|
-
origin_chain: req.originChain,
|
|
11028
|
-
amount_base_units: req.amountBaseUnits.toString(),
|
|
11029
|
-
allocate: req.allocate ?? false
|
|
11030
|
-
};
|
|
11031
|
-
if (req.refundTo !== void 0) body.refund_to = req.refundTo;
|
|
11032
|
-
if (req.rails !== void 0) body.rails = req.rails;
|
|
11033
|
-
const res = await relayFetch(`${relayUrl}/bridge/quote`, {
|
|
11034
|
-
method: "POST",
|
|
11035
|
-
headers: { "Content-Type": "application/json" },
|
|
11036
|
-
body: JSON.stringify(body)
|
|
11037
|
-
});
|
|
11038
|
-
const raw = await parseBridgeResponse(res, "bridge quote");
|
|
11039
|
-
const options = [];
|
|
11040
|
-
const unavailable = (raw.unavailable ?? []).map((p) => ({
|
|
11041
|
-
rail: p.rail,
|
|
11042
|
-
reason: p.reason
|
|
11043
|
-
}));
|
|
11044
|
-
for (const opt of raw.options) {
|
|
11045
|
-
const attestation = toAttestation(opt.attestation);
|
|
11046
|
-
if (attestation.kind === "ed25519") {
|
|
11047
|
-
const verdict = opt.rail_response ? verifyQuoteSignature(opt.rail_response) : { valid: false, reason: "no rail_response to verify against" };
|
|
11048
|
-
if (!verdict.valid) {
|
|
11049
|
-
unavailable.push({
|
|
11050
|
-
rail: opt.rail,
|
|
11051
|
-
reason: `deposit address failed independent signature verification, refusing to display it (${verdict.reason ?? "signature did not verify"})`
|
|
11052
|
-
});
|
|
11053
|
-
continue;
|
|
11054
|
-
}
|
|
11055
|
-
}
|
|
11056
|
-
options.push({
|
|
11057
|
-
rail: opt.rail,
|
|
11058
|
-
refunds: opt.refunds,
|
|
11059
|
-
addressLifetime: opt.address_lifetime,
|
|
11060
|
-
amountOut: BigInt(opt.amount_out),
|
|
11061
|
-
minAmountOut: BigInt(opt.min_amount_out),
|
|
11062
|
-
timeEstimateSeconds: opt.time_estimate_s,
|
|
11063
|
-
expiresAt: opt.expires_at,
|
|
11064
|
-
depositAddress: opt.deposit_address,
|
|
11065
|
-
attestation
|
|
11066
|
-
});
|
|
11067
|
-
}
|
|
11068
|
-
return { options, unavailable };
|
|
11069
|
-
}
|
|
11070
|
-
var STATUS_STATES = [
|
|
11071
|
-
"pending",
|
|
11072
|
-
"delivered",
|
|
11073
|
-
"refunded",
|
|
11074
|
-
"expired",
|
|
11075
|
-
"unknown"
|
|
11076
|
-
];
|
|
11077
|
-
async function fetchBridgeStatus(relayUrl, depositAddress, rail) {
|
|
11078
|
-
const qs = new URLSearchParams({ deposit_address: depositAddress, rail });
|
|
11079
|
-
const res = await relayFetch(`${relayUrl}/bridge/status?${qs.toString()}`);
|
|
11080
|
-
const raw = await parseBridgeResponse(res, "bridge status");
|
|
11081
|
-
const state = STATUS_STATES.includes(raw.state ?? "") ? raw.state : "unknown";
|
|
11082
|
-
return { rail: raw.rail ?? rail, state, detail: raw.detail ?? "" };
|
|
11083
|
-
}
|
|
11084
|
-
function cloakBridgeRail(relayUrl) {
|
|
11085
|
-
assertAllowedRelayOrigin(relayUrl);
|
|
11086
|
-
return {
|
|
11087
|
-
id: "cloak-bridge",
|
|
11088
|
-
quote: (req) => fetchBridgeQuote(relayUrl, req),
|
|
11089
|
-
status: (depositAddress, rail) => fetchBridgeStatus(relayUrl, depositAddress, rail)
|
|
11090
|
-
};
|
|
11091
|
-
}
|
|
11092
|
-
|
|
11093
|
-
// src/bridge/paymaster-client.ts
|
|
11094
|
-
import {
|
|
11095
|
-
PublicKey as PublicKey11,
|
|
11096
|
-
SystemInstruction,
|
|
11097
|
-
SystemProgram as SystemProgram3,
|
|
11098
|
-
Transaction as Transaction3
|
|
11099
|
-
} from "@solana/web3.js";
|
|
11100
|
-
import { TOKEN_PROGRAM_ID as TOKEN_PROGRAM_ID3, decodeTransferInstruction, getAssociatedTokenAddressSync as getAssociatedTokenAddressSync3 } from "@solana/spl-token";
|
|
11101
|
-
function validatePaymasterTopUpTransaction(tx, expect) {
|
|
11102
|
-
const ixs = tx.instructions;
|
|
11103
|
-
if (ixs.length !== 1 && ixs.length !== 2) {
|
|
11104
|
-
throw new Error(
|
|
11105
|
-
`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`
|
|
11106
|
-
);
|
|
11107
|
-
}
|
|
11108
|
-
const ix0 = ixs[0];
|
|
11109
|
-
if (!ix0.programId.equals(SystemProgram3.programId)) {
|
|
11110
|
-
throw new Error(
|
|
11111
|
-
`paymaster top-up instruction 0 is owned by ${ix0.programId.toBase58()}, not the System Program \u2014 refusing to sign an unrecognised first instruction`
|
|
11112
|
-
);
|
|
11113
|
-
}
|
|
11114
|
-
const transfer2 = SystemInstruction.decodeTransfer(ix0);
|
|
11115
|
-
if (!transfer2.toPubkey.equals(expect.recipient)) {
|
|
11116
|
-
throw new Error(
|
|
11117
|
-
`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`
|
|
11118
|
-
);
|
|
11119
|
-
}
|
|
11120
|
-
if (BigInt(transfer2.lamports) > expect.maxGrantLamports) {
|
|
11121
|
-
throw new Error(
|
|
11122
|
-
`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`
|
|
11123
|
-
);
|
|
11124
|
-
}
|
|
11125
|
-
if (ixs.length === 1) return;
|
|
11126
|
-
const ix1 = ixs[1];
|
|
11127
|
-
if (!ix1.programId.equals(TOKEN_PROGRAM_ID3)) {
|
|
11128
|
-
throw new Error(
|
|
11129
|
-
`paymaster top-up instruction 1 is owned by ${ix1.programId.toBase58()}, not the SPL Token program \u2014 refusing to sign an unrecognised second instruction`
|
|
11130
|
-
);
|
|
11131
|
-
}
|
|
11132
|
-
const spl = decodeTransferInstruction(ix1, TOKEN_PROGRAM_ID3);
|
|
11133
|
-
const authority = spl.keys.owner.pubkey;
|
|
11134
|
-
if (!authority.equals(expect.recipient)) {
|
|
11135
|
-
throw new Error(
|
|
11136
|
-
`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`
|
|
11137
|
-
);
|
|
11138
|
-
}
|
|
11139
|
-
const expectedDestination = getAssociatedTokenAddressSync3(
|
|
11140
|
-
expect.feeMint,
|
|
11141
|
-
expect.paymentAddress,
|
|
11142
|
-
false
|
|
11143
|
-
);
|
|
11144
|
-
const destination = spl.keys.destination.pubkey;
|
|
11145
|
-
if (!destination.equals(expectedDestination)) {
|
|
11146
|
-
throw new Error(
|
|
11147
|
-
`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`
|
|
11148
|
-
);
|
|
11149
|
-
}
|
|
11150
|
-
const paidAmount = BigInt(spl.data.amount);
|
|
11151
|
-
if (paidAmount !== expect.feeTokenAmount) {
|
|
11152
|
-
throw new Error(
|
|
11153
|
-
`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`
|
|
11154
|
-
);
|
|
11155
|
-
}
|
|
11156
|
-
}
|
|
11157
|
-
async function parseBridgeResponse2(res, what) {
|
|
11158
|
-
let body;
|
|
11159
|
-
try {
|
|
11160
|
-
body = await res.json();
|
|
11161
|
-
} catch {
|
|
11162
|
-
throw new Error(`${what} failed: ${res.status} ${res.statusText} (response body was not JSON)`);
|
|
11163
|
-
}
|
|
11164
|
-
if (!res.ok) {
|
|
11165
|
-
const b = body ?? {};
|
|
11166
|
-
throw new Error(
|
|
11167
|
-
`${what} failed: ${b.message ?? res.statusText} (${b.code ?? res.status}${b.retryable ? ", retryable" : ""})`
|
|
11168
|
-
);
|
|
11169
|
-
}
|
|
11170
|
-
return body;
|
|
11171
|
-
}
|
|
11172
|
-
async function fundReceiverViaPaymaster(relayUrl, receiver, grantLamports) {
|
|
11173
|
-
assertAllowedRelayOrigin(relayUrl);
|
|
11174
|
-
if (grantLamports <= 0n || grantLamports > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
11175
|
-
throw new Error(
|
|
11176
|
-
`grantLamports must be a positive value representable as a JS number, got ${grantLamports}`
|
|
11177
|
-
);
|
|
11178
|
-
}
|
|
11179
|
-
const prepareRes = await relayFetch(`${relayUrl}/bridge/paymaster/prepare`, {
|
|
11180
|
-
method: "POST",
|
|
11181
|
-
headers: { "Content-Type": "application/json" },
|
|
11182
|
-
body: JSON.stringify({
|
|
11183
|
-
recipient: receiver.publicKey.toBase58(),
|
|
11184
|
-
grant_lamports: Number(grantLamports)
|
|
11185
|
-
})
|
|
11186
|
-
});
|
|
11187
|
-
const prepared = await parseBridgeResponse2(prepareRes, "paymaster prepare");
|
|
11188
|
-
if (Math.floor(Date.now() / 1e3) >= prepared.expires_at_unix) {
|
|
11189
|
-
throw new Error(
|
|
11190
|
-
"paymaster prepare returned a voucher that is already expired \u2014 refusing to sign a stale top-up rather than fail confusingly at cosign"
|
|
11191
|
-
);
|
|
11192
|
-
}
|
|
11193
|
-
const feeMint = new PublicKey11(prepared.fee_mint);
|
|
11194
|
-
const paymentAddress = new PublicKey11(prepared.payment_address);
|
|
11195
|
-
const feeTokenAmount = BigInt(prepared.fee_token_amount);
|
|
11196
|
-
const tx = Transaction3.from(Buffer.from(prepared.transaction, "base64"));
|
|
11197
|
-
validatePaymasterTopUpTransaction(tx, {
|
|
11198
|
-
recipient: receiver.publicKey,
|
|
11199
|
-
maxGrantLamports: grantLamports,
|
|
11200
|
-
feeMint,
|
|
11201
|
-
feeTokenAmount,
|
|
11202
|
-
paymentAddress
|
|
11203
|
-
});
|
|
11204
|
-
tx.partialSign(receiver);
|
|
11205
|
-
const cosignRes = await relayFetch(`${relayUrl}/bridge/paymaster/cosign`, {
|
|
11206
|
-
method: "POST",
|
|
11207
|
-
headers: { "Content-Type": "application/json" },
|
|
11208
|
-
body: JSON.stringify({
|
|
11209
|
-
transaction: tx.serialize({ requireAllSignatures: false, verifySignatures: false }).toString("base64"),
|
|
11210
|
-
voucher: prepared.voucher
|
|
11211
|
-
})
|
|
11212
|
-
});
|
|
11213
|
-
const cosigned = await parseBridgeResponse2(cosignRes, "paymaster cosign");
|
|
11214
|
-
return {
|
|
11215
|
-
transaction: Transaction3.from(Buffer.from(cosigned.transaction, "base64")),
|
|
11216
|
-
feeTokenAmount,
|
|
11217
|
-
feeMint: prepared.fee_mint,
|
|
11218
|
-
paymentAddress: prepared.payment_address
|
|
11219
|
-
};
|
|
11220
|
-
}
|
|
11221
|
-
|
|
11222
|
-
// src/bridge/derive.ts
|
|
11223
|
-
import { Keypair as Keypair4 } from "@solana/web3.js";
|
|
11224
|
-
import { hmac } from "@noble/hashes/hmac";
|
|
11225
|
-
import { sha512 } from "@noble/hashes/sha512";
|
|
11226
|
-
var BRIDGE_ESCROW_LABEL = "cloak_bridge_escrow";
|
|
11227
|
-
var MAX_RECEIVER_INDEX = 1e4;
|
|
11228
|
-
function deriveBridgeReceiver(nk, index) {
|
|
11229
|
-
if (!Number.isInteger(index) || index < 0) {
|
|
11230
|
-
throw new Error(`index must be a non-negative integer, got ${index}`);
|
|
11231
|
-
}
|
|
11232
|
-
if (index > MAX_RECEIVER_INDEX) {
|
|
11233
|
-
throw new Error(
|
|
11234
|
-
`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.`
|
|
11235
|
-
);
|
|
11236
|
-
}
|
|
11237
|
-
const h = hmac(sha512, new Uint8Array(nk), new TextEncoder().encode(`${BRIDGE_ESCROW_LABEL}:${index}`));
|
|
11238
|
-
return Keypair4.fromSeed(h.slice(0, 32));
|
|
11239
|
-
}
|
|
11240
|
-
|
|
11241
|
-
// src/bridge/discover.ts
|
|
11242
|
-
import { SolanaJSONRPCError } from "@solana/web3.js";
|
|
11243
|
-
import { getAssociatedTokenAddressSync as getAssociatedTokenAddressSync4 } from "@solana/spl-token";
|
|
11244
|
-
function describeError(err) {
|
|
11245
|
-
return err instanceof Error ? err.message : String(err);
|
|
11246
|
-
}
|
|
11247
|
-
async function listBridgeDeposits(conn, nk, opts) {
|
|
11248
|
-
const scanDepth = Math.min(opts.scanDepth ?? 20, MAX_RECEIVER_INDEX + 1);
|
|
11249
|
-
const stopAfterUnused = opts.stopAfterUnused ?? 5;
|
|
11250
|
-
const out = [];
|
|
11251
|
-
let consecutiveUnused = 0;
|
|
11252
|
-
for (let index = 0; index < scanDepth; index++) {
|
|
11253
|
-
const kp = deriveBridgeReceiver(nk, index);
|
|
11254
|
-
const receiver = kp.publicKey;
|
|
11255
|
-
const tokenAccount = getAssociatedTokenAddressSync4(opts.mint, receiver, false);
|
|
11256
|
-
try {
|
|
11257
|
-
const [recvInfo, ataInfo] = await conn.getMultipleAccountsInfo([receiver, tokenAccount]);
|
|
11258
|
-
const lamports = recvInfo?.lamports ?? 0;
|
|
11259
|
-
let tokenBalance = 0n;
|
|
11260
|
-
if (ataInfo) {
|
|
11261
|
-
try {
|
|
11262
|
-
tokenBalance = BigInt((await conn.getTokenAccountBalance(tokenAccount)).value.amount);
|
|
11263
|
-
} catch (err) {
|
|
11264
|
-
if (!(err instanceof SolanaJSONRPCError && /could not find account/i.test(err.message))) {
|
|
11265
|
-
throw err;
|
|
11266
|
-
}
|
|
11267
|
-
}
|
|
11268
|
-
}
|
|
11269
|
-
if (lamports === 0 && !ataInfo) {
|
|
11270
|
-
consecutiveUnused++;
|
|
11271
|
-
if (consecutiveUnused >= stopAfterUnused) break;
|
|
11272
|
-
continue;
|
|
11273
|
-
}
|
|
11274
|
-
consecutiveUnused = 0;
|
|
11275
|
-
const shieldSignatures = [];
|
|
11276
|
-
const sigs = await conn.getSignaturesForAddress(receiver, { limit: 20 });
|
|
11277
|
-
for (const s of sigs) {
|
|
11278
|
-
if (s.err) continue;
|
|
11279
|
-
const tx = await conn.getTransaction(s.signature, { maxSupportedTransactionVersion: 0 });
|
|
11280
|
-
const keys = tx?.transaction.message.getAccountKeys({
|
|
11281
|
-
accountKeysFromLookups: tx.meta?.loadedAddresses
|
|
11282
|
-
});
|
|
11283
|
-
if (keys && [...Array(keys.length).keys()].some((i) => keys.get(i)?.equals(opts.programId))) {
|
|
11284
|
-
shieldSignatures.push(s.signature);
|
|
11285
|
-
}
|
|
11286
|
-
}
|
|
11287
|
-
const shieldSignature = shieldSignatures[0];
|
|
11288
|
-
const indexReused = shieldSignatures.length > 1;
|
|
11289
|
-
let state;
|
|
11290
|
-
if (tokenBalance > 0n) state = shieldSignature ? "needs-cleanup" : "arrived";
|
|
11291
|
-
else if (shieldSignature) state = ataInfo ? "needs-cleanup" : "complete";
|
|
11292
|
-
else state = "awaiting";
|
|
11293
|
-
out.push({
|
|
11294
|
-
index,
|
|
11295
|
-
receiver,
|
|
11296
|
-
tokenAccount,
|
|
11297
|
-
state,
|
|
11298
|
-
tokenBalance,
|
|
11299
|
-
lamports,
|
|
11300
|
-
shieldSignature,
|
|
11301
|
-
...indexReused ? { indexReused, shieldSignatures } : {}
|
|
11302
|
-
});
|
|
11303
|
-
} catch (err) {
|
|
11304
|
-
out.push({
|
|
11305
|
-
index,
|
|
11306
|
-
receiver,
|
|
11307
|
-
tokenAccount,
|
|
11308
|
-
state: "unknown",
|
|
11309
|
-
tokenBalance: 0n,
|
|
11310
|
-
lamports: 0,
|
|
11311
|
-
error: describeError(err)
|
|
11312
|
-
});
|
|
11313
|
-
}
|
|
11314
|
-
}
|
|
11315
|
-
return out;
|
|
11316
|
-
}
|
|
11317
|
-
|
|
11318
|
-
// src/bridge/deposit-core.ts
|
|
11319
|
-
import {
|
|
11320
|
-
SystemProgram as SystemProgram4,
|
|
11321
|
-
Transaction as Transaction4,
|
|
11322
|
-
VersionedTransaction as VersionedTransaction2,
|
|
11323
|
-
sendAndConfirmTransaction as sendAndConfirmTransaction2
|
|
11324
|
-
} from "@solana/web3.js";
|
|
11325
|
-
import nacl8 from "tweetnacl";
|
|
11326
|
-
|
|
11327
|
-
// src/bridge/funder.ts
|
|
11328
|
-
import { PublicKey as PublicKey13 } from "@solana/web3.js";
|
|
11329
|
-
import { getAssociatedTokenAddressSync as getAssociatedTokenAddressSync5 } from "@solana/spl-token";
|
|
11330
|
-
var MAINNET_RENT_0 = 890880;
|
|
11331
|
-
var MAINNET_RENT_1 = 897840;
|
|
11332
|
-
var FEE_BUDGET = 13e4;
|
|
11333
|
-
var CLEANUP_FEE_BUDGET = 5e3;
|
|
11334
|
-
var pda = (seeds, programId) => PublicKey13.findProgramAddressSync(seeds.map((s) => Buffer.from(s)), programId)[0];
|
|
11335
|
-
function deriveFundingTargets(d) {
|
|
11336
|
-
const pool = pda([Buffer.from("pool"), d.mint.toBuffer()], d.programId);
|
|
11337
|
-
return {
|
|
11338
|
-
pool,
|
|
11339
|
-
// program: NullifierAccount::derive_pda_with_pool -> [b"nullifier", pool, nullifier]
|
|
11340
|
-
nullifier0: pda([Buffer.from("nullifier"), pool.toBuffer(), d.nullifiers[0]], d.programId),
|
|
11341
|
-
nullifier1: pda([Buffer.from("nullifier"), pool.toBuffer(), d.nullifiers[1]], d.programId),
|
|
11342
|
-
// relay: derive_risk_nonce_pda -> [b"risk_nonce", bind0]
|
|
11343
|
-
riskNonce: pda([Buffer.from("risk_nonce"), d.bind0], d.programId),
|
|
11344
|
-
depositorAta: getAssociatedTokenAddressSync5(d.mint, d.depositor, false)
|
|
11345
|
-
};
|
|
11346
|
-
}
|
|
11347
|
-
async function readRent(conn) {
|
|
11348
|
-
const [zero, one] = await Promise.all([
|
|
11349
|
-
conn.getMinimumBalanceForRentExemption(0),
|
|
11350
|
-
conn.getMinimumBalanceForRentExemption(1)
|
|
11351
|
-
]);
|
|
11352
|
-
return { zero, one };
|
|
11353
|
-
}
|
|
11354
|
-
|
|
11355
|
-
// src/bridge/deposit-core.ts
|
|
11356
|
-
var DepositError = class extends Error {
|
|
11357
|
-
constructor(message, fundedAccounts, measuredTxSize) {
|
|
11358
|
-
super(message);
|
|
11359
|
-
this.fundedAccounts = fundedAccounts;
|
|
11360
|
-
this.measuredTxSize = measuredTxSize;
|
|
11361
|
-
this.name = "DepositError";
|
|
11362
|
-
}
|
|
11363
|
-
};
|
|
11364
|
-
async function depositFromDerivedKey(conn, R, funder, amount, log = console.log, grantOverride, opts) {
|
|
11365
|
-
if (!opts.noteSpendKey || opts.noteSpendKey.length !== 32) {
|
|
11366
|
-
throw new Error(
|
|
11367
|
-
"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."
|
|
11368
|
-
);
|
|
11369
|
-
}
|
|
11370
|
-
if (!opts.relayUrl) {
|
|
11371
|
-
throw new Error(
|
|
11372
|
-
"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."
|
|
11373
|
-
);
|
|
11374
|
-
}
|
|
11375
|
-
if (!opts.programId) {
|
|
11376
|
-
throw new Error(
|
|
11377
|
-
"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."
|
|
11378
|
-
);
|
|
11379
|
-
}
|
|
11380
|
-
if (!opts.mint) {
|
|
11381
|
-
throw new Error(
|
|
11382
|
-
"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."
|
|
11383
|
-
);
|
|
11384
|
-
}
|
|
11385
|
-
assertAllowedRpcConnection(conn);
|
|
11386
|
-
const relayUrl = opts.relayUrl;
|
|
11387
|
-
const programId = opts.programId;
|
|
11388
|
-
const mint = opts.mint;
|
|
11389
|
-
setCircuitsPath(resolveCircuitsBase());
|
|
11390
|
-
const grantR = grantOverride ?? MAINNET_RENT_0 + FEE_BUDGET + CLEANUP_FEE_BUDGET;
|
|
11391
|
-
const heldByR = await conn.getBalance(R.publicKey);
|
|
11392
|
-
if (heldByR >= grantR) {
|
|
11393
|
-
log(` R already holds ${heldByR} (>= ${grantR}) \u2014 no top-up needed`);
|
|
11394
|
-
} else {
|
|
11395
|
-
await sendAndConfirmTransaction2(conn, new Transaction4().add(
|
|
11396
|
-
SystemProgram4.transfer({ fromPubkey: funder.publicKey, toPubkey: R.publicKey, lamports: grantR - heldByR })
|
|
11397
|
-
), [funder]);
|
|
11398
|
-
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"}`);
|
|
11399
|
-
}
|
|
11400
|
-
let measuredSize = -1;
|
|
11401
|
-
let fundedAccounts = [];
|
|
11402
|
-
const signTransaction2 = async (tx) => {
|
|
11403
|
-
if (tx instanceof VersionedTransaction2) {
|
|
11404
|
-
measuredSize = tx.serialize().length;
|
|
11405
|
-
const alts = await Promise.all(
|
|
11406
|
-
tx.message.addressTableLookups.map(async (l) => (await conn.getAddressLookupTable(l.accountKey)).value)
|
|
11407
|
-
);
|
|
11408
|
-
const keys = tx.message.getAccountKeys({ addressLookupTableAccounts: alts });
|
|
11409
|
-
const ix = tx.message.compiledInstructions.find((i) => keys.get(i.programIdIndex).equals(programId));
|
|
11410
|
-
const at = (n) => keys.get(ix.accountKeyIndexes[n]);
|
|
11411
|
-
const targets = [
|
|
11412
|
-
{ a: at(12), need: MAINNET_RENT_0, name: "risk_nonce" },
|
|
11413
|
-
{ a: at(4), need: MAINNET_RENT_1, name: "nullifier_0" },
|
|
11414
|
-
{ a: at(5), need: MAINNET_RENT_1, name: "nullifier_1" }
|
|
11415
|
-
];
|
|
11416
|
-
const infos = await conn.getMultipleAccountsInfo(targets.map((t) => t.a));
|
|
11417
|
-
fundedAccounts = targets.map((t, i) => ({
|
|
11418
|
-
name: t.name,
|
|
11419
|
-
address: t.a.toBase58(),
|
|
11420
|
-
lamportsSent: Math.max(0, t.need - (infos[i]?.lamports ?? 0))
|
|
11421
|
-
}));
|
|
11422
|
-
const ixs = targets.flatMap((t, i) => (infos[i]?.lamports ?? 0) >= t.need ? [] : [SystemProgram4.transfer({
|
|
11423
|
-
fromPubkey: funder.publicKey,
|
|
11424
|
-
toPubkey: t.a,
|
|
11425
|
-
lamports: t.need - (infos[i]?.lamports ?? 0)
|
|
11426
|
-
})]);
|
|
11427
|
-
if (ixs.length) {
|
|
11428
|
-
const sig = await sendAndConfirmTransaction2(conn, new Transaction4().add(...ixs), [funder]);
|
|
11429
|
-
log(` seam: funded ${ixs.length} program accounts read off the built tx \u2014 ${sig.slice(0, 16)}\u2026`);
|
|
11430
|
-
}
|
|
11431
|
-
tx.sign([R]);
|
|
11432
|
-
return tx;
|
|
11433
|
-
}
|
|
11434
|
-
tx.partialSign(R);
|
|
11435
|
-
return tx;
|
|
11436
|
-
};
|
|
11437
|
-
const signMessage = async (m) => nacl8.sign.detached(m, R.secretKey);
|
|
11438
|
-
const utxoKeypair = await deriveUtxoKeypairFromSpendKey(opts.noteSpendKey);
|
|
11439
|
-
const nk = getNkFromUtxoPrivateKey(utxoKeypair.privateKey);
|
|
11440
|
-
const { utxo, noteSalt } = await createRecoverableDepositUtxo(amount, nk, mint);
|
|
11441
|
-
const zeroInput = await createZeroUtxo(mint);
|
|
11442
|
-
const rBefore = await conn.getBalance(R.publicKey);
|
|
11443
|
-
let result;
|
|
11444
|
-
try {
|
|
11445
|
-
result = await transact(
|
|
11446
|
-
{ inputUtxos: [zeroInput], outputUtxos: [utxo], externalAmount: amount, depositor: R.publicKey },
|
|
11447
|
-
{
|
|
11448
|
-
connection: conn,
|
|
11449
|
-
programId,
|
|
11450
|
-
relayUrl,
|
|
11451
|
-
// NO depositorKeypair: the SDK branches `if (keypair) … else if (signTransaction)`
|
|
11452
|
-
// (transact.ts:2786, :2936), so passing one silently skips the funding seam.
|
|
11453
|
-
depositorPublicKey: R.publicKey,
|
|
11454
|
-
walletPublicKey: R.publicKey,
|
|
11455
|
-
signTransaction: signTransaction2,
|
|
11456
|
-
signMessage,
|
|
11457
|
-
chainNoteViewingKeyNk: nk,
|
|
11458
|
-
chainNoteSalt: noteSalt,
|
|
11459
|
-
relaySupplementalAlt: true,
|
|
11460
|
-
onProgress: (s) => log(` \xB7 ${s}`)
|
|
11461
|
-
}
|
|
11462
|
-
);
|
|
11463
|
-
} catch (e) {
|
|
11464
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
11465
|
-
throw new DepositError(msg, fundedAccounts, measuredSize);
|
|
11466
|
-
}
|
|
11467
|
-
const rAfter = await conn.getBalance(R.publicKey);
|
|
11468
|
-
const noteIndex = result.outputUtxos[0].index;
|
|
11469
|
-
if (noteIndex === void 0) throw new Error("deposit landed but the note has no index \u2014 it cannot be discovered later");
|
|
11470
|
-
return {
|
|
11471
|
-
signature: result.signature,
|
|
11472
|
-
noteIndex,
|
|
11473
|
-
amount: result.outputUtxos[0].amount,
|
|
11474
|
-
txSize: measuredSize,
|
|
11475
|
-
rBefore,
|
|
11476
|
-
rAfter,
|
|
11477
|
-
rentExempt: rAfter >= MAINNET_RENT_0,
|
|
11478
|
-
inputNullifiers: result.inputNullifiers,
|
|
11479
|
-
fundedAccounts
|
|
11480
|
-
};
|
|
11481
|
-
}
|
|
11482
|
-
|
|
11483
|
-
// src/bridge/cleanup.ts
|
|
11484
|
-
import {
|
|
11485
|
-
PublicKey as PublicKey15,
|
|
11486
|
-
Transaction as Transaction5,
|
|
11487
|
-
sendAndConfirmTransaction as sendAndConfirmTransaction3
|
|
11488
|
-
} from "@solana/web3.js";
|
|
11489
|
-
import {
|
|
11490
|
-
getAssociatedTokenAddressSync as getAssociatedTokenAddressSync6,
|
|
11491
|
-
createCloseAccountInstruction,
|
|
11492
|
-
createTransferInstruction,
|
|
11493
|
-
createAssociatedTokenAccountIdempotentInstruction,
|
|
11494
|
-
getMinimumBalanceForRentExemptAccount
|
|
11495
|
-
} from "@solana/spl-token";
|
|
11496
|
-
var DEFAULT_MINT = new PublicKey15("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
|
|
11497
|
-
async function cleanupReceivingAddress(conn, R, dustDestination, mint = DEFAULT_MINT) {
|
|
11498
|
-
assertAllowedRpcConnection(conn);
|
|
11499
|
-
const rAta = getAssociatedTokenAddressSync6(mint, R.publicKey);
|
|
11500
|
-
const info = await conn.getAccountInfo(rAta);
|
|
11501
|
-
if (!info) {
|
|
11502
|
-
return {
|
|
11503
|
-
closed: false,
|
|
11504
|
-
dustSwept: 0n,
|
|
11505
|
-
rentReturned: 0,
|
|
11506
|
-
destination: null,
|
|
11507
|
-
signature: null,
|
|
11508
|
-
note: "no token account \u2014 nothing to recover"
|
|
11509
|
-
};
|
|
11510
|
-
}
|
|
11511
|
-
const bal = BigInt((await conn.getTokenAccountBalance(rAta)).value.amount);
|
|
11512
|
-
const rentHeld = info.lamports;
|
|
11513
|
-
const before = await conn.getBalance(R.publicKey);
|
|
11514
|
-
const ixs = [];
|
|
11515
|
-
let destination = null;
|
|
11516
|
-
if (bal > 0n && !dustDestination) {
|
|
11517
|
-
return {
|
|
11518
|
-
closed: false,
|
|
11519
|
-
dustSwept: 0n,
|
|
11520
|
-
rentReturned: 0,
|
|
11521
|
-
destination: null,
|
|
11522
|
-
signature: null,
|
|
11523
|
-
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`
|
|
11524
|
-
};
|
|
11525
|
-
}
|
|
11526
|
-
if (bal > 0n && dustDestination) {
|
|
11527
|
-
const destAta = getAssociatedTokenAddressSync6(mint, dustDestination);
|
|
11528
|
-
const destInfo = await conn.getAccountInfo(destAta);
|
|
11529
|
-
if (!destInfo) {
|
|
11530
|
-
const ataRent = await getMinimumBalanceForRentExemptAccount(conn);
|
|
11531
|
-
if (before < ataRent + CLEANUP_FEE_BUDGET) {
|
|
11532
|
-
throw new Error(
|
|
11533
|
-
`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.`
|
|
11534
|
-
);
|
|
11535
|
-
}
|
|
11536
|
-
ixs.push(createAssociatedTokenAccountIdempotentInstruction(R.publicKey, destAta, dustDestination, mint));
|
|
11537
|
-
}
|
|
11538
|
-
ixs.push(createTransferInstruction(rAta, destAta, R.publicKey, bal));
|
|
11539
|
-
destination = destAta.toBase58();
|
|
11540
|
-
}
|
|
11541
|
-
ixs.push(createCloseAccountInstruction(rAta, R.publicKey, R.publicKey));
|
|
11542
|
-
const signature = await sendAndConfirmTransaction3(conn, new Transaction5().add(...ixs), [R], {
|
|
11543
|
-
commitment: "confirmed"
|
|
11544
|
-
});
|
|
11545
|
-
const after = await conn.getBalance(R.publicKey);
|
|
11546
|
-
const gone = await conn.getAccountInfo(rAta) === null;
|
|
11547
|
-
return {
|
|
11548
|
-
closed: gone,
|
|
11549
|
-
dustSwept: bal,
|
|
11550
|
-
rentReturned: after - before,
|
|
11551
|
-
destination,
|
|
11552
|
-
signature,
|
|
11553
|
-
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`
|
|
11554
|
-
};
|
|
11555
|
-
}
|
|
11556
|
-
|
|
11557
|
-
// src/bridge/quote.ts
|
|
11558
|
-
var MIN_DEPOSIT_SPL_BASE_UNITS = 1000000n;
|
|
11559
|
-
var WITHDRAW_FIXED_FEE = 450000n;
|
|
11560
|
-
var WITHDRAW_FEE_BPS = 30n;
|
|
11561
|
-
function withdrawFeeAt(amount, fixedFee, feeBps) {
|
|
11562
|
-
return fixedFee + amount * feeBps / 10000n;
|
|
11563
|
-
}
|
|
11564
|
-
var MAX_FEE_FRACTION = 0.063;
|
|
11565
|
-
var FIXED_ROUND_TRIP = 903000n;
|
|
11566
|
-
var ECONOMIC_MINIMUM = FIXED_ROUND_TRIP * 1000000n / BigInt(Math.round((MAX_FEE_FRACTION - 3e-3) * 1e6));
|
|
11567
|
-
function withdrawFeeFor(amount) {
|
|
11568
|
-
return withdrawFeeAt(amount, WITHDRAW_FIXED_FEE, WITHDRAW_FEE_BPS);
|
|
11569
|
-
}
|
|
11570
|
-
function assessBridgeQuote(q) {
|
|
11571
|
-
if (q.sent < 0n) {
|
|
11572
|
-
throw new Error(`assessBridgeQuote: sent must not be negative (got ${q.sent}).`);
|
|
11573
|
-
}
|
|
11574
|
-
if (q.arrivesMin > q.sent) {
|
|
11575
|
-
throw new Error(
|
|
11576
|
-
`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.`
|
|
11577
|
-
);
|
|
11578
|
-
}
|
|
11579
|
-
const shieldedMinRaw = q.arrivesMin - q.paymasterFee;
|
|
11580
|
-
const shieldedTargetRaw = q.arrivesTarget - q.paymasterFee;
|
|
11581
|
-
const shieldedMin = shieldedMinRaw > 0n ? shieldedMinRaw : 0n;
|
|
11582
|
-
const shieldedTarget = shieldedTargetRaw > 0n ? shieldedTargetRaw : 0n;
|
|
11583
|
-
const viable = shieldedMinRaw >= MIN_DEPOSIT_SPL_BASE_UNITS;
|
|
11584
|
-
const economic = q.sent >= ECONOMIC_MINIMUM;
|
|
11585
|
-
const withdrawFee = shieldedMin > 0n ? withdrawFeeAt(shieldedMin, q.liveWithdrawFixedFee ?? WITHDRAW_FIXED_FEE, q.liveWithdrawFeeBps ?? WITHDRAW_FEE_BPS) : 0n;
|
|
11586
|
-
const roundTripCost = q.sent - (shieldedMin > withdrawFee ? shieldedMin - withdrawFee : 0n);
|
|
11587
|
-
const roundTripFraction = q.sent > 0n ? Number(roundTripCost) / Number(q.sent) : 0;
|
|
11588
|
-
const reasons = [];
|
|
11589
|
-
if (!viable) {
|
|
11590
|
-
const short = MIN_DEPOSIT_SPL_BASE_UNITS - shieldedMinRaw;
|
|
11591
|
-
reasons.push(
|
|
11592
|
-
(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.`
|
|
11593
|
-
);
|
|
11594
|
-
}
|
|
11595
|
-
if (viable && !economic) {
|
|
11596
|
-
reasons.push(
|
|
11597
|
-
`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.`
|
|
11598
|
-
);
|
|
11599
|
-
}
|
|
11600
|
-
return {
|
|
11601
|
-
...q,
|
|
11602
|
-
shieldedMin,
|
|
11603
|
-
shieldedTarget,
|
|
11604
|
-
viable,
|
|
11605
|
-
economic,
|
|
11606
|
-
withdrawFee,
|
|
11607
|
-
roundTripCost,
|
|
11608
|
-
roundTripFraction,
|
|
11609
|
-
reasons
|
|
11610
|
-
};
|
|
11611
|
-
}
|
|
11612
|
-
var fmt = (v) => `${(Number(v) / 1e6).toFixed(6)} USDC`;
|
|
11613
|
-
function renderAssessment(a) {
|
|
11614
|
-
const L = [];
|
|
11615
|
-
L.push(` you send ${fmt(a.sent)}`);
|
|
11616
|
-
L.push(` arrives at least ${fmt(a.arrivesMin)} (target ${fmt(a.arrivesTarget)}, not a promise)`);
|
|
11617
|
-
L.push(` paymaster fee ${fmt(a.paymasterFee)} buys the receiver its SOL, so your wallet stays off chain`);
|
|
11618
|
-
L.push(` SHIELDED ${fmt(a.shieldedMin)} at least \u2014 this is what you end up with`);
|
|
11619
|
-
L.push(` to withdraw later ${fmt(a.withdrawFee)} the program's fee, whenever you take it out`);
|
|
11620
|
-
for (const r of a.reasons) L.push(`
|
|
11621
|
-
${a.viable ? "NOTE" : "REFUSED"}: ${r}`);
|
|
11622
|
-
return L.join("\n");
|
|
11623
|
-
}
|
|
11624
|
-
|
|
11625
|
-
// src/bridge/retry.ts
|
|
11626
|
-
var TERMINAL_FAILURE = /DepositTooSmall|minimum is 1\.000000|insufficient|holds only|below the program|kora wants|over the .* ceiling/i;
|
|
11627
|
-
function isTerminalFailure(message) {
|
|
11628
|
-
return TERMINAL_FAILURE.test(message);
|
|
11629
|
-
}
|
|
11630
|
-
function classifyPostFailure(attempted, after) {
|
|
11631
|
-
if (after === null) return "unknown";
|
|
11632
|
-
if (after === 0n) return "landed";
|
|
11633
|
-
if (after >= attempted) return "did-not-land";
|
|
11634
|
-
return "unknown";
|
|
11635
|
-
}
|
|
11636
|
-
function mayRetry(verdict, message) {
|
|
11637
|
-
return verdict === "did-not-land" && !isTerminalFailure(message);
|
|
11638
|
-
}
|
|
11639
|
-
function retryDelayMs(attempt) {
|
|
11640
|
-
return 15e3 * Math.max(0, attempt - 1);
|
|
11641
|
-
}
|
|
11642
|
-
var DoNotRetry = class extends Error {
|
|
11643
|
-
constructor(message) {
|
|
11644
|
-
super(message);
|
|
11645
|
-
this.name = "DoNotRetry";
|
|
11646
|
-
}
|
|
11647
|
-
};
|
|
11648
|
-
async function withShieldRetries(attempt, hooks = {}) {
|
|
11649
|
-
const attempts = hooks.attempts ?? 3;
|
|
11650
|
-
const sleep4 = hooks.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
11651
|
-
let last = "";
|
|
11652
|
-
for (let n = 1; n <= attempts; n++) {
|
|
11653
|
-
if (n > 1) {
|
|
11654
|
-
const delay = retryDelayMs(n);
|
|
11655
|
-
hooks.onRetry?.(n, delay, last);
|
|
11656
|
-
await sleep4(delay);
|
|
11657
|
-
}
|
|
11658
|
-
try {
|
|
11659
|
-
return await attempt(n);
|
|
11660
|
-
} catch (e) {
|
|
11661
|
-
if (e instanceof DoNotRetry) throw e;
|
|
11662
|
-
last = e instanceof Error ? e.message : String(e);
|
|
11663
|
-
if (isTerminalFailure(last) || n === attempts) throw e;
|
|
11664
|
-
}
|
|
11665
|
-
}
|
|
11666
|
-
throw new Error("unreachable");
|
|
11667
|
-
}
|
|
11668
|
-
|
|
11669
10884
|
// src/index.ts
|
|
11670
10885
|
var VERSION = "0.2.3";
|
|
11671
10886
|
var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
11672
10887
|
export {
|
|
11673
|
-
BRIDGE_ESCROW_LABEL,
|
|
11674
10888
|
BUILD_ALLOWS_LOCAL_ENDPOINTS,
|
|
11675
10889
|
CHAIN_NOTE_SALT_BITS,
|
|
11676
|
-
CLEANUP_FEE_BUDGET,
|
|
11677
10890
|
CLOAK_PRODUCTION_RELAY_URL,
|
|
11678
10891
|
CLOAK_PROGRAM_ID,
|
|
11679
10892
|
CloakError,
|
|
@@ -11681,25 +10894,16 @@ export {
|
|
|
11681
10894
|
DEFAULT_TRANSACTION_CIRCUITS_URL,
|
|
11682
10895
|
DELIVERY_MEMO_TAG,
|
|
11683
10896
|
DELIVERY_REGISTRY_SEED,
|
|
11684
|
-
DepositError,
|
|
11685
|
-
DoNotRetry,
|
|
11686
|
-
ECONOMIC_MINIMUM,
|
|
11687
10897
|
EXPECTED_CIRCUIT_HASHES,
|
|
11688
|
-
FEE_BUDGET,
|
|
11689
10898
|
FIXED_FEE_LAMPORTS,
|
|
11690
10899
|
InsecureRandomnessError,
|
|
11691
10900
|
LAMPORTS_PER_SOL,
|
|
11692
10901
|
LocalStorageAdapter,
|
|
11693
|
-
MAINNET_RENT_0,
|
|
11694
|
-
MAINNET_RENT_1,
|
|
11695
|
-
MAX_RECEIVER_INDEX,
|
|
11696
10902
|
MERKLE_TREE_HEIGHT2 as MERKLE_TREE_HEIGHT,
|
|
11697
10903
|
MIN_DEPOSIT_LAMPORTS,
|
|
11698
|
-
MIN_DEPOSIT_SPL_BASE_UNITS,
|
|
11699
10904
|
MemoryStorageAdapter,
|
|
11700
10905
|
MerkleTree,
|
|
11701
10906
|
NATIVE_SOL_MINT,
|
|
11702
|
-
ONECLICK_PUBKEY_B58,
|
|
11703
10907
|
RECIPIENT_DELIVERY_CIPHERTEXT_LEN,
|
|
11704
10908
|
RECIPIENT_DELIVERY_EPHEMERAL_PK_LEN,
|
|
11705
10909
|
RECIPIENT_DELIVERY_NONCE_LEN,
|
|
@@ -11718,7 +10922,6 @@ export {
|
|
|
11718
10922
|
SettlementVerificationError,
|
|
11719
10923
|
ShieldPoolErrors,
|
|
11720
10924
|
SimpleWallet,
|
|
11721
|
-
TERMINAL_FAILURE,
|
|
11722
10925
|
TRANSACTION_CIRCUITS_VERSION,
|
|
11723
10926
|
TRANSACT_AUTH_FIELDS,
|
|
11724
10927
|
TRANSACT_SWAP_AUTH_FIELDS,
|
|
@@ -11728,12 +10931,9 @@ export {
|
|
|
11728
10931
|
VARIABLE_FEE_NUMERATOR,
|
|
11729
10932
|
VARIABLE_FEE_RATE,
|
|
11730
10933
|
VERSION,
|
|
11731
|
-
WITHDRAW_FEE_BPS,
|
|
11732
|
-
WITHDRAW_FIXED_FEE,
|
|
11733
10934
|
assertDirectSubmissionLanded,
|
|
11734
10935
|
assertInputMints,
|
|
11735
10936
|
assertTransactionCircuitIntegrity,
|
|
11736
|
-
assessBridgeQuote,
|
|
11737
10937
|
bigintToBytes32,
|
|
11738
10938
|
bigintToHex,
|
|
11739
10939
|
buildMerkleTree,
|
|
@@ -11748,16 +10948,12 @@ export {
|
|
|
11748
10948
|
calculateRelayFee,
|
|
11749
10949
|
canRebuildMerkleTreeFromChain,
|
|
11750
10950
|
canonicalJson,
|
|
11751
|
-
canonicalPayloadString,
|
|
11752
10951
|
chainNoteFromBase64,
|
|
11753
10952
|
chainNoteToBase64,
|
|
11754
|
-
classifyPostFailure,
|
|
11755
10953
|
classifyRelayError,
|
|
11756
|
-
cleanupReceivingAddress,
|
|
11757
10954
|
cleanupStalePendingOperations,
|
|
11758
10955
|
clearPendingDeposits,
|
|
11759
10956
|
clearPendingWithdrawals,
|
|
11760
|
-
cloakBridgeRail,
|
|
11761
10957
|
computeChainNoteHash,
|
|
11762
10958
|
computeExtDataHash,
|
|
11763
10959
|
computeMerkleRoot,
|
|
@@ -11779,13 +10975,10 @@ export {
|
|
|
11779
10975
|
decryptCompactChainNote,
|
|
11780
10976
|
decryptComplianceMetadataWithMasterKey,
|
|
11781
10977
|
decryptTransactionMetadata,
|
|
11782
|
-
depositFromDerivedKey,
|
|
11783
|
-
deriveBridgeReceiver,
|
|
11784
10978
|
deriveChangeNoteBlinding,
|
|
11785
10979
|
deriveDepositNoteSecrets,
|
|
11786
10980
|
deriveDiversifiedViewingKey,
|
|
11787
10981
|
deriveDiversifier,
|
|
11788
|
-
deriveFundingTargets,
|
|
11789
10982
|
deriveInputNullifierPdas,
|
|
11790
10983
|
derivePublicKey,
|
|
11791
10984
|
deriveSpendKey,
|
|
@@ -11824,7 +11017,6 @@ export {
|
|
|
11824
11017
|
formatErrorForLogging,
|
|
11825
11018
|
formatSol,
|
|
11826
11019
|
fullWithdraw,
|
|
11827
|
-
fundReceiverViaPaymaster,
|
|
11828
11020
|
generateCloakKeys,
|
|
11829
11021
|
generateCommitmentAsync,
|
|
11830
11022
|
generateMasterSeed,
|
|
@@ -11860,21 +11052,18 @@ export {
|
|
|
11860
11052
|
isReactNative,
|
|
11861
11053
|
isRootNotFoundError,
|
|
11862
11054
|
isSubmissionOutcomeUnknownResponse,
|
|
11863
|
-
isTerminalFailure,
|
|
11864
11055
|
isValidHex,
|
|
11865
11056
|
isValidRpcUrl,
|
|
11866
11057
|
isValidSolanaAddress,
|
|
11867
11058
|
isWithdrawAmountSufficient,
|
|
11868
11059
|
isWithdrawable,
|
|
11869
11060
|
keypairToAdapter,
|
|
11870
|
-
listBridgeDeposits,
|
|
11871
11061
|
loadPendingDeposits,
|
|
11872
11062
|
loadPendingWithdrawals,
|
|
11873
11063
|
loadVerifiedCircuitArtifacts,
|
|
11874
11064
|
matchChangeNote,
|
|
11875
11065
|
matchDepositNote,
|
|
11876
11066
|
matchSwapRefundLeaf,
|
|
11877
|
-
mayRetry,
|
|
11878
11067
|
openRecipientDeliveryNote,
|
|
11879
11068
|
parseAmount,
|
|
11880
11069
|
parseDeliveryCarrierMemo,
|
|
@@ -11897,14 +11086,11 @@ export {
|
|
|
11897
11086
|
randomDepositNoteSalt,
|
|
11898
11087
|
randomFieldElement,
|
|
11899
11088
|
readMerkleTreeState,
|
|
11900
|
-
readRent,
|
|
11901
11089
|
recipientDeliveryNoteToBase64,
|
|
11902
11090
|
registerViewingKey,
|
|
11903
11091
|
removePendingDeposit,
|
|
11904
11092
|
removePendingWithdrawal,
|
|
11905
|
-
renderAssessment,
|
|
11906
11093
|
resolveCircuitsBase,
|
|
11907
|
-
retryDelayMs,
|
|
11908
11094
|
savePendingDeposit,
|
|
11909
11095
|
savePendingWithdrawal,
|
|
11910
11096
|
scanNotesForWallet,
|
|
@@ -11937,17 +11123,13 @@ export {
|
|
|
11937
11123
|
validateDepositParams,
|
|
11938
11124
|
validateNote,
|
|
11939
11125
|
validateOutputsSum,
|
|
11940
|
-
validatePaymasterTopUpTransaction,
|
|
11941
11126
|
validateRoot,
|
|
11942
11127
|
validateTransfers,
|
|
11943
11128
|
validateWalletConnected,
|
|
11944
11129
|
validateWithdrawableNote,
|
|
11945
11130
|
verifyAllCircuits,
|
|
11946
11131
|
verifyCircuitIntegrity,
|
|
11947
|
-
verifyQuoteSignature,
|
|
11948
11132
|
verifyUtxos,
|
|
11949
11133
|
waitForRoot,
|
|
11950
|
-
|
|
11951
|
-
withTiming,
|
|
11952
|
-
withdrawFeeFor
|
|
11134
|
+
withTiming
|
|
11953
11135
|
};
|