@cloak.dev/sdk 0.2.2 → 0.2.3-staging.f2d7f2a
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 +874 -16
- package/dist/index.d.cts +725 -2
- package/dist/index.d.ts +725 -2
- package/dist/index.js +858 -15
- 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 = ["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}$/;
|
|
@@ -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 [pda2] = getNullifierPDA(pool, nullifierBytes, programId);
|
|
2154
|
+
checkable.push({ utxo, pda: pda2 });
|
|
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 [pda2] = getNullifierPDA(pool, bigintToBytes324(nullifier), programId);
|
|
2225
|
+
pdas.push(pda2);
|
|
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 = 1e3
|
|
3546
|
+
retryDelayMs: retryDelayMs2 = 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 = retryDelayMs2 * Math.pow(2, attempt) + Math.random() * 500;
|
|
3571
3571
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
3572
3572
|
}
|
|
3573
3573
|
}
|
|
@@ -5449,6 +5449,23 @@ async function computeSwapExtDataHash(outputMint, recipientAta, minOutputAmount,
|
|
|
5449
5449
|
]);
|
|
5450
5450
|
return poseidonHasher.F.toObject(hash);
|
|
5451
5451
|
}
|
|
5452
|
+
function assertInputMints(inputUtxos, expectedMint) {
|
|
5453
|
+
const funded = inputUtxos.filter((u) => u.amount > BigInt(0));
|
|
5454
|
+
if (funded.length === 0) return;
|
|
5455
|
+
const first = funded[0].mintAddress;
|
|
5456
|
+
for (const utxo of funded) {
|
|
5457
|
+
if (!utxo.mintAddress.equals(first)) {
|
|
5458
|
+
throw new Error(
|
|
5459
|
+
`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.`
|
|
5460
|
+
);
|
|
5461
|
+
}
|
|
5462
|
+
}
|
|
5463
|
+
if (expectedMint && !first.equals(expectedMint)) {
|
|
5464
|
+
throw new Error(
|
|
5465
|
+
`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.`
|
|
5466
|
+
);
|
|
5467
|
+
}
|
|
5468
|
+
}
|
|
5452
5469
|
function parseNkInput(input) {
|
|
5453
5470
|
if (typeof input !== "string") {
|
|
5454
5471
|
if (input.length !== 32) {
|
|
@@ -7060,7 +7077,7 @@ async function transact(params, options) {
|
|
|
7060
7077
|
// names is refused.
|
|
7061
7078
|
maxRootRetries = 40,
|
|
7062
7079
|
// Increased to handle prolonged relay sync recovery under high concurrency
|
|
7063
|
-
retryDelayMs = 500,
|
|
7080
|
+
retryDelayMs: retryDelayMs2 = 500,
|
|
7064
7081
|
// Start with short delay, will exponentially back off
|
|
7065
7082
|
riskOracleQueue,
|
|
7066
7083
|
riskQuoteUrl: riskQuoteUrlOption,
|
|
@@ -7115,6 +7132,7 @@ async function transact(params, options) {
|
|
|
7115
7132
|
}
|
|
7116
7133
|
}
|
|
7117
7134
|
assertAllowedRpcConnection(connection);
|
|
7135
|
+
assertInputMints(inputUtxos, options.expectedMint);
|
|
7118
7136
|
await ensureViewingKeyRegistered(options, onProgress, fallbackNk);
|
|
7119
7137
|
const resolvedChainNoteNk = explicitChainNoteNk ?? fallbackNk ?? null;
|
|
7120
7138
|
if (inputUtxos.length > 2) {
|
|
@@ -7481,7 +7499,7 @@ async function transact(params, options) {
|
|
|
7481
7499
|
while (submissionAttempts <= maxRootRetries) {
|
|
7482
7500
|
const isRetry = submissionAttempts > 0;
|
|
7483
7501
|
if (isRetry) {
|
|
7484
|
-
const exponentialDelay = Math.min(
|
|
7502
|
+
const exponentialDelay = Math.min(retryDelayMs2 * Math.pow(1.5, submissionAttempts - 1), 5e3);
|
|
7485
7503
|
const jitter = Math.random() * 500;
|
|
7486
7504
|
const totalDelay = Math.floor(exponentialDelay + jitter);
|
|
7487
7505
|
await sleep2(totalDelay);
|
|
@@ -7644,7 +7662,7 @@ async function transact(params, options) {
|
|
|
7644
7662
|
}
|
|
7645
7663
|
treeState = null;
|
|
7646
7664
|
useSiblingInfo = false;
|
|
7647
|
-
const waitMs = Math.min(
|
|
7665
|
+
const waitMs = Math.min(retryDelayMs2 * Math.pow(2, Math.min(submissionAttempts, 4)), 5e3);
|
|
7648
7666
|
if (waitMs > 0) {
|
|
7649
7667
|
onProgress?.(`Waiting ${waitMs}ms for relay sync before retry...`);
|
|
7650
7668
|
await new Promise((r) => setTimeout(r, waitMs));
|
|
@@ -8338,7 +8356,7 @@ async function swapUtxo(params, options) {
|
|
|
8338
8356
|
riskQuoteUrl: riskQuoteUrlOption,
|
|
8339
8357
|
maxRootRetries = 40,
|
|
8340
8358
|
// Increased to handle prolonged relay sync recovery under high concurrency
|
|
8341
|
-
retryDelayMs = 500,
|
|
8359
|
+
retryDelayMs: retryDelayMs2 = 500,
|
|
8342
8360
|
// Start with short delay, will exponentially back off
|
|
8343
8361
|
useUniqueNullifiers,
|
|
8344
8362
|
useChainRootForProof = true
|
|
@@ -8357,6 +8375,12 @@ async function swapUtxo(params, options) {
|
|
|
8357
8375
|
if (inputUtxos.length > 2) {
|
|
8358
8376
|
throw new Error("Maximum 2 input UTXOs allowed");
|
|
8359
8377
|
}
|
|
8378
|
+
assertInputMints(inputUtxos, NATIVE_SOL_MINT);
|
|
8379
|
+
if (options.expectedMint && !options.expectedMint.equals(NATIVE_SOL_MINT)) {
|
|
8380
|
+
throw new Error(
|
|
8381
|
+
`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.`
|
|
8382
|
+
);
|
|
8383
|
+
}
|
|
8360
8384
|
await preflightNullifiers(inputUtxos, connection, programId);
|
|
8361
8385
|
const outputMintAccount = await connection.getAccountInfo(outputMint, {
|
|
8362
8386
|
commitment: "confirmed"
|
|
@@ -8743,7 +8767,7 @@ async function swapUtxo(params, options) {
|
|
|
8743
8767
|
let walletApprovals = 0;
|
|
8744
8768
|
for (let attempt = 0; attempt <= maxRootRetries; attempt++) {
|
|
8745
8769
|
if (attempt > 0) {
|
|
8746
|
-
const exponentialDelay = Math.min(
|
|
8770
|
+
const exponentialDelay = Math.min(retryDelayMs2 * Math.pow(1.5, attempt - 1), 5e3);
|
|
8747
8771
|
const jitter = Math.random() * 500;
|
|
8748
8772
|
const totalDelay = Math.floor(exponentialDelay + jitter);
|
|
8749
8773
|
await sleep2(totalDelay);
|
|
@@ -8881,7 +8905,7 @@ async function swapUtxo(params, options) {
|
|
|
8881
8905
|
}
|
|
8882
8906
|
merkleState = null;
|
|
8883
8907
|
useSiblingInfo = false;
|
|
8884
|
-
const waitMs = Math.min(
|
|
8908
|
+
const waitMs = Math.min(retryDelayMs2 * Math.pow(2, Math.min(attempt, 4)), 5e3);
|
|
8885
8909
|
if (waitMs > 0) {
|
|
8886
8910
|
onProgress?.(`Waiting ${waitMs}ms for relay sync before retry...`);
|
|
8887
8911
|
await new Promise((r) => setTimeout(r, waitMs));
|
|
@@ -10857,12 +10881,799 @@ var SimpleWallet = class {
|
|
|
10857
10881
|
}
|
|
10858
10882
|
};
|
|
10859
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
|
+
|
|
10860
11669
|
// src/index.ts
|
|
10861
|
-
var VERSION = "0.2.
|
|
11670
|
+
var VERSION = "0.2.3";
|
|
10862
11671
|
var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
10863
11672
|
export {
|
|
11673
|
+
BRIDGE_ESCROW_LABEL,
|
|
10864
11674
|
BUILD_ALLOWS_LOCAL_ENDPOINTS,
|
|
10865
11675
|
CHAIN_NOTE_SALT_BITS,
|
|
11676
|
+
CLEANUP_FEE_BUDGET,
|
|
10866
11677
|
CLOAK_PRODUCTION_RELAY_URL,
|
|
10867
11678
|
CLOAK_PROGRAM_ID,
|
|
10868
11679
|
CloakError,
|
|
@@ -10870,16 +11681,25 @@ export {
|
|
|
10870
11681
|
DEFAULT_TRANSACTION_CIRCUITS_URL,
|
|
10871
11682
|
DELIVERY_MEMO_TAG,
|
|
10872
11683
|
DELIVERY_REGISTRY_SEED,
|
|
11684
|
+
DepositError,
|
|
11685
|
+
DoNotRetry,
|
|
11686
|
+
ECONOMIC_MINIMUM,
|
|
10873
11687
|
EXPECTED_CIRCUIT_HASHES,
|
|
11688
|
+
FEE_BUDGET,
|
|
10874
11689
|
FIXED_FEE_LAMPORTS,
|
|
10875
11690
|
InsecureRandomnessError,
|
|
10876
11691
|
LAMPORTS_PER_SOL,
|
|
10877
11692
|
LocalStorageAdapter,
|
|
11693
|
+
MAINNET_RENT_0,
|
|
11694
|
+
MAINNET_RENT_1,
|
|
11695
|
+
MAX_RECEIVER_INDEX,
|
|
10878
11696
|
MERKLE_TREE_HEIGHT2 as MERKLE_TREE_HEIGHT,
|
|
10879
11697
|
MIN_DEPOSIT_LAMPORTS,
|
|
11698
|
+
MIN_DEPOSIT_SPL_BASE_UNITS,
|
|
10880
11699
|
MemoryStorageAdapter,
|
|
10881
11700
|
MerkleTree,
|
|
10882
11701
|
NATIVE_SOL_MINT,
|
|
11702
|
+
ONECLICK_PUBKEY_B58,
|
|
10883
11703
|
RECIPIENT_DELIVERY_CIPHERTEXT_LEN,
|
|
10884
11704
|
RECIPIENT_DELIVERY_EPHEMERAL_PK_LEN,
|
|
10885
11705
|
RECIPIENT_DELIVERY_NONCE_LEN,
|
|
@@ -10898,6 +11718,7 @@ export {
|
|
|
10898
11718
|
SettlementVerificationError,
|
|
10899
11719
|
ShieldPoolErrors,
|
|
10900
11720
|
SimpleWallet,
|
|
11721
|
+
TERMINAL_FAILURE,
|
|
10901
11722
|
TRANSACTION_CIRCUITS_VERSION,
|
|
10902
11723
|
TRANSACT_AUTH_FIELDS,
|
|
10903
11724
|
TRANSACT_SWAP_AUTH_FIELDS,
|
|
@@ -10907,8 +11728,12 @@ export {
|
|
|
10907
11728
|
VARIABLE_FEE_NUMERATOR,
|
|
10908
11729
|
VARIABLE_FEE_RATE,
|
|
10909
11730
|
VERSION,
|
|
11731
|
+
WITHDRAW_FEE_BPS,
|
|
11732
|
+
WITHDRAW_FIXED_FEE,
|
|
10910
11733
|
assertDirectSubmissionLanded,
|
|
11734
|
+
assertInputMints,
|
|
10911
11735
|
assertTransactionCircuitIntegrity,
|
|
11736
|
+
assessBridgeQuote,
|
|
10912
11737
|
bigintToBytes32,
|
|
10913
11738
|
bigintToHex,
|
|
10914
11739
|
buildMerkleTree,
|
|
@@ -10923,12 +11748,16 @@ export {
|
|
|
10923
11748
|
calculateRelayFee,
|
|
10924
11749
|
canRebuildMerkleTreeFromChain,
|
|
10925
11750
|
canonicalJson,
|
|
11751
|
+
canonicalPayloadString,
|
|
10926
11752
|
chainNoteFromBase64,
|
|
10927
11753
|
chainNoteToBase64,
|
|
11754
|
+
classifyPostFailure,
|
|
10928
11755
|
classifyRelayError,
|
|
11756
|
+
cleanupReceivingAddress,
|
|
10929
11757
|
cleanupStalePendingOperations,
|
|
10930
11758
|
clearPendingDeposits,
|
|
10931
11759
|
clearPendingWithdrawals,
|
|
11760
|
+
cloakBridgeRail,
|
|
10932
11761
|
computeChainNoteHash,
|
|
10933
11762
|
computeExtDataHash,
|
|
10934
11763
|
computeMerkleRoot,
|
|
@@ -10950,10 +11779,13 @@ export {
|
|
|
10950
11779
|
decryptCompactChainNote,
|
|
10951
11780
|
decryptComplianceMetadataWithMasterKey,
|
|
10952
11781
|
decryptTransactionMetadata,
|
|
11782
|
+
depositFromDerivedKey,
|
|
11783
|
+
deriveBridgeReceiver,
|
|
10953
11784
|
deriveChangeNoteBlinding,
|
|
10954
11785
|
deriveDepositNoteSecrets,
|
|
10955
11786
|
deriveDiversifiedViewingKey,
|
|
10956
11787
|
deriveDiversifier,
|
|
11788
|
+
deriveFundingTargets,
|
|
10957
11789
|
deriveInputNullifierPdas,
|
|
10958
11790
|
derivePublicKey,
|
|
10959
11791
|
deriveSpendKey,
|
|
@@ -10992,6 +11824,7 @@ export {
|
|
|
10992
11824
|
formatErrorForLogging,
|
|
10993
11825
|
formatSol,
|
|
10994
11826
|
fullWithdraw,
|
|
11827
|
+
fundReceiverViaPaymaster,
|
|
10995
11828
|
generateCloakKeys,
|
|
10996
11829
|
generateCommitmentAsync,
|
|
10997
11830
|
generateMasterSeed,
|
|
@@ -11027,18 +11860,21 @@ export {
|
|
|
11027
11860
|
isReactNative,
|
|
11028
11861
|
isRootNotFoundError,
|
|
11029
11862
|
isSubmissionOutcomeUnknownResponse,
|
|
11863
|
+
isTerminalFailure,
|
|
11030
11864
|
isValidHex,
|
|
11031
11865
|
isValidRpcUrl,
|
|
11032
11866
|
isValidSolanaAddress,
|
|
11033
11867
|
isWithdrawAmountSufficient,
|
|
11034
11868
|
isWithdrawable,
|
|
11035
11869
|
keypairToAdapter,
|
|
11870
|
+
listBridgeDeposits,
|
|
11036
11871
|
loadPendingDeposits,
|
|
11037
11872
|
loadPendingWithdrawals,
|
|
11038
11873
|
loadVerifiedCircuitArtifacts,
|
|
11039
11874
|
matchChangeNote,
|
|
11040
11875
|
matchDepositNote,
|
|
11041
11876
|
matchSwapRefundLeaf,
|
|
11877
|
+
mayRetry,
|
|
11042
11878
|
openRecipientDeliveryNote,
|
|
11043
11879
|
parseAmount,
|
|
11044
11880
|
parseDeliveryCarrierMemo,
|
|
@@ -11061,11 +11897,14 @@ export {
|
|
|
11061
11897
|
randomDepositNoteSalt,
|
|
11062
11898
|
randomFieldElement,
|
|
11063
11899
|
readMerkleTreeState,
|
|
11900
|
+
readRent,
|
|
11064
11901
|
recipientDeliveryNoteToBase64,
|
|
11065
11902
|
registerViewingKey,
|
|
11066
11903
|
removePendingDeposit,
|
|
11067
11904
|
removePendingWithdrawal,
|
|
11905
|
+
renderAssessment,
|
|
11068
11906
|
resolveCircuitsBase,
|
|
11907
|
+
retryDelayMs,
|
|
11069
11908
|
savePendingDeposit,
|
|
11070
11909
|
savePendingWithdrawal,
|
|
11071
11910
|
scanNotesForWallet,
|
|
@@ -11098,13 +11937,17 @@ export {
|
|
|
11098
11937
|
validateDepositParams,
|
|
11099
11938
|
validateNote,
|
|
11100
11939
|
validateOutputsSum,
|
|
11940
|
+
validatePaymasterTopUpTransaction,
|
|
11101
11941
|
validateRoot,
|
|
11102
11942
|
validateTransfers,
|
|
11103
11943
|
validateWalletConnected,
|
|
11104
11944
|
validateWithdrawableNote,
|
|
11105
11945
|
verifyAllCircuits,
|
|
11106
11946
|
verifyCircuitIntegrity,
|
|
11947
|
+
verifyQuoteSignature,
|
|
11107
11948
|
verifyUtxos,
|
|
11108
11949
|
waitForRoot,
|
|
11109
|
-
|
|
11950
|
+
withShieldRetries,
|
|
11951
|
+
withTiming,
|
|
11952
|
+
withdrawFeeFor
|
|
11110
11953
|
};
|