@cloak.dev/sdk 0.2.2 → 0.2.3-staging.16d1078
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 +1203 -31
- package/dist/index.d.cts +919 -2
- package/dist/index.d.ts +919 -2
- package/dist/index.js +1179 -30
- 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
|
}
|
|
@@ -4778,12 +4778,12 @@ function signRelayRequest(endpoint, programId, body, signer, nowSeconds, fields
|
|
|
4778
4778
|
auth_signature: Buffer.from(signature).toString("base64")
|
|
4779
4779
|
};
|
|
4780
4780
|
}
|
|
4781
|
-
function assertApprovalWithinFreshnessWindow(elapsedMs) {
|
|
4781
|
+
function assertApprovalWithinFreshnessWindow(elapsedMs, maxAgeSeconds = REQUEST_AUTH_MAX_AGE_SECONDS) {
|
|
4782
4782
|
const elapsedSeconds = elapsedMs / 1e3;
|
|
4783
|
-
const budget =
|
|
4783
|
+
const budget = maxAgeSeconds - RELAY_AUTH_APPROVAL_MARGIN_SECONDS;
|
|
4784
4784
|
if (elapsedSeconds <= budget) return;
|
|
4785
4785
|
throw new Error(
|
|
4786
|
-
`The wallet approval took ${Math.round(elapsedSeconds)} seconds, and Cloak requests stay valid for ${
|
|
4786
|
+
`The wallet approval took ${Math.round(elapsedSeconds)} seconds, and Cloak requests stay valid for ${maxAgeSeconds} seconds from the moment they are signed (${RELAY_AUTH_APPROVAL_MARGIN_SECONDS} of those are held back for the request to reach the network). The timestamp is inside the signature, so it cannot be refreshed without your approval again. NOTHING WAS SUBMITTED and no funds moved. Start the same operation again and approve the prompt when it appears.`
|
|
4787
4787
|
);
|
|
4788
4788
|
}
|
|
4789
4789
|
function explainRelayAuthRejection(responseText) {
|
|
@@ -4841,6 +4841,16 @@ async function buildRelayAuthFields(endpoint, programId, body, signers, fields =
|
|
|
4841
4841
|
auth_signature: Buffer.from(signature).toString("base64")
|
|
4842
4842
|
};
|
|
4843
4843
|
}
|
|
4844
|
+
var REQUEST_AUTH_BATCH_DOMAIN = "CLOAK_RELAY_BATCH_AUTH_V1";
|
|
4845
|
+
var REQUEST_AUTH_BATCH_MAX_AGE_SECONDS = 600;
|
|
4846
|
+
var RELAY_BATCH_AUTH_MAX_ITEMS = 64;
|
|
4847
|
+
function relayRequestDigestHex(preimage) {
|
|
4848
|
+
return bytesToHex3(sha2563(preimage.message));
|
|
4849
|
+
}
|
|
4850
|
+
function buildRelayBatchAuthMessage(programId, issuedAt, digests) {
|
|
4851
|
+
const lines = [REQUEST_AUTH_BATCH_DOMAIN, programId.toBase58(), issuedAt, String(digests.length)];
|
|
4852
|
+
return new TextEncoder().encode([...lines, ...digests].join("\n"));
|
|
4853
|
+
}
|
|
4844
4854
|
|
|
4845
4855
|
// src/proving/artifacts.ts
|
|
4846
4856
|
import { sha256 as sha2564 } from "@noble/hashes/sha2";
|
|
@@ -5117,6 +5127,7 @@ async function resolveTransactionCircuitFiles() {
|
|
|
5117
5127
|
}
|
|
5118
5128
|
var _transactPaddingSaltCounter = 0;
|
|
5119
5129
|
var _registeredViewingKeys = /* @__PURE__ */ new Set();
|
|
5130
|
+
var _viewingKeyRegistrationsInFlight = /* @__PURE__ */ new Map();
|
|
5120
5131
|
var _resolvedAltAccountsCache = /* @__PURE__ */ new Map();
|
|
5121
5132
|
var _relayMerkleDisabledMints = /* @__PURE__ */ new Set();
|
|
5122
5133
|
var _relayMerkleFallbackNotifiedMints = /* @__PURE__ */ new Set();
|
|
@@ -5449,6 +5460,23 @@ async function computeSwapExtDataHash(outputMint, recipientAta, minOutputAmount,
|
|
|
5449
5460
|
]);
|
|
5450
5461
|
return poseidonHasher.F.toObject(hash);
|
|
5451
5462
|
}
|
|
5463
|
+
function assertInputMints(inputUtxos, expectedMint) {
|
|
5464
|
+
const funded = inputUtxos.filter((u) => u.amount > BigInt(0));
|
|
5465
|
+
if (funded.length === 0) return;
|
|
5466
|
+
const first = funded[0].mintAddress;
|
|
5467
|
+
for (const utxo of funded) {
|
|
5468
|
+
if (!utxo.mintAddress.equals(first)) {
|
|
5469
|
+
throw new Error(
|
|
5470
|
+
`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.`
|
|
5471
|
+
);
|
|
5472
|
+
}
|
|
5473
|
+
}
|
|
5474
|
+
if (expectedMint && !first.equals(expectedMint)) {
|
|
5475
|
+
throw new Error(
|
|
5476
|
+
`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.`
|
|
5477
|
+
);
|
|
5478
|
+
}
|
|
5479
|
+
}
|
|
5452
5480
|
function parseNkInput(input) {
|
|
5453
5481
|
if (typeof input !== "string") {
|
|
5454
5482
|
if (input.length !== 32) {
|
|
@@ -5504,6 +5532,26 @@ async function ensureViewingKeyRegistered(options, onProgress, fallbackNk) {
|
|
|
5504
5532
|
if (_registeredViewingKeys.has(cacheKey)) {
|
|
5505
5533
|
return;
|
|
5506
5534
|
}
|
|
5535
|
+
const inFlight = _viewingKeyRegistrationsInFlight.get(cacheKey);
|
|
5536
|
+
if (inFlight) {
|
|
5537
|
+
return inFlight;
|
|
5538
|
+
}
|
|
5539
|
+
const registration = registerViewingKeyOnce(
|
|
5540
|
+
relayUrl,
|
|
5541
|
+
userPubkey,
|
|
5542
|
+
viewingKeyHex,
|
|
5543
|
+
cacheKey,
|
|
5544
|
+
options,
|
|
5545
|
+
onProgress
|
|
5546
|
+
);
|
|
5547
|
+
_viewingKeyRegistrationsInFlight.set(cacheKey, registration);
|
|
5548
|
+
try {
|
|
5549
|
+
await registration;
|
|
5550
|
+
} finally {
|
|
5551
|
+
_viewingKeyRegistrationsInFlight.delete(cacheKey);
|
|
5552
|
+
}
|
|
5553
|
+
}
|
|
5554
|
+
async function registerViewingKeyOnce(relayUrl, userPubkey, viewingKeyHex, cacheKey, options, onProgress) {
|
|
5507
5555
|
const challengeResponse = await relayFetch(`${relayUrl}/viewing-key/challenge`, {
|
|
5508
5556
|
method: "POST",
|
|
5509
5557
|
headers: { "Content-Type": "application/json" },
|
|
@@ -6859,12 +6907,12 @@ async function submitTransactToRelay(args) {
|
|
|
6859
6907
|
const doFetch = args.fetchImpl ?? fetch;
|
|
6860
6908
|
const maxNetworkRetries = args.maxNetworkRetries ?? relayNetworkRetries();
|
|
6861
6909
|
const requestTimeoutMs = args.requestTimeoutMs ?? relayRequestTimeoutMs();
|
|
6862
|
-
if (!depositorKeypair && args.relayAuthSigner) {
|
|
6910
|
+
if (!depositorKeypair && args.relayAuthSigner && !args.relayAuthBatch) {
|
|
6863
6911
|
onProgress?.(
|
|
6864
6912
|
"Approve the request in your wallet. This signs the request for the relay, not a transaction, and it must be approved within a few minutes to stay valid."
|
|
6865
6913
|
);
|
|
6866
6914
|
}
|
|
6867
|
-
const authFields = await buildRelayAuthFields("/transact", programId, requestBody, {
|
|
6915
|
+
const authFields = args.relayAuthBatch ? await args.relayAuthBatch.authorize("/transact", programId, requestBody, TRANSACT_AUTH_FIELDS) : await buildRelayAuthFields("/transact", programId, requestBody, {
|
|
6868
6916
|
depositorKeypair,
|
|
6869
6917
|
relayAuthSigner: args.relayAuthSigner
|
|
6870
6918
|
});
|
|
@@ -7060,7 +7108,7 @@ async function transact(params, options) {
|
|
|
7060
7108
|
// names is refused.
|
|
7061
7109
|
maxRootRetries = 40,
|
|
7062
7110
|
// Increased to handle prolonged relay sync recovery under high concurrency
|
|
7063
|
-
retryDelayMs = 500,
|
|
7111
|
+
retryDelayMs: retryDelayMs2 = 500,
|
|
7064
7112
|
// Start with short delay, will exponentially back off
|
|
7065
7113
|
riskOracleQueue,
|
|
7066
7114
|
riskQuoteUrl: riskQuoteUrlOption,
|
|
@@ -7115,6 +7163,7 @@ async function transact(params, options) {
|
|
|
7115
7163
|
}
|
|
7116
7164
|
}
|
|
7117
7165
|
assertAllowedRpcConnection(connection);
|
|
7166
|
+
assertInputMints(inputUtxos, options.expectedMint);
|
|
7118
7167
|
await ensureViewingKeyRegistered(options, onProgress, fallbackNk);
|
|
7119
7168
|
const resolvedChainNoteNk = explicitChainNoteNk ?? fallbackNk ?? null;
|
|
7120
7169
|
if (inputUtxos.length > 2) {
|
|
@@ -7481,7 +7530,7 @@ async function transact(params, options) {
|
|
|
7481
7530
|
while (submissionAttempts <= maxRootRetries) {
|
|
7482
7531
|
const isRetry = submissionAttempts > 0;
|
|
7483
7532
|
if (isRetry) {
|
|
7484
|
-
const exponentialDelay = Math.min(
|
|
7533
|
+
const exponentialDelay = Math.min(retryDelayMs2 * Math.pow(1.5, submissionAttempts - 1), 5e3);
|
|
7485
7534
|
const jitter = Math.random() * 500;
|
|
7486
7535
|
const totalDelay = Math.floor(exponentialDelay + jitter);
|
|
7487
7536
|
await sleep2(totalDelay);
|
|
@@ -7620,11 +7669,14 @@ async function transact(params, options) {
|
|
|
7620
7669
|
}
|
|
7621
7670
|
let proof;
|
|
7622
7671
|
let publicSignals;
|
|
7672
|
+
const releaseProofSlot = options.relayAuthBatch ? await options.relayAuthBatch.proofSlot() : void 0;
|
|
7623
7673
|
try {
|
|
7624
7674
|
const result = await generateTransactionProof(circuitInputs, onProofProgress);
|
|
7625
7675
|
proof = result.proof;
|
|
7626
7676
|
publicSignals = result.publicSignals;
|
|
7677
|
+
releaseProofSlot?.();
|
|
7627
7678
|
} catch (proofError) {
|
|
7679
|
+
releaseProofSlot?.();
|
|
7628
7680
|
const errorMsg = proofError?.message || String(proofError);
|
|
7629
7681
|
if ((errorMsg.includes("ForceEqual") || errorMsg.includes("Assert Failed")) && submissionAttempts < maxRootRetries) {
|
|
7630
7682
|
onProgress?.(`Merkle proof error (stale sibling data), will regenerate with fresh tree...`);
|
|
@@ -7644,7 +7696,7 @@ async function transact(params, options) {
|
|
|
7644
7696
|
}
|
|
7645
7697
|
treeState = null;
|
|
7646
7698
|
useSiblingInfo = false;
|
|
7647
|
-
const waitMs = Math.min(
|
|
7699
|
+
const waitMs = Math.min(retryDelayMs2 * Math.pow(2, Math.min(submissionAttempts, 4)), 5e3);
|
|
7648
7700
|
if (waitMs > 0) {
|
|
7649
7701
|
onProgress?.(`Waiting ${waitMs}ms for relay sync before retry...`);
|
|
7650
7702
|
await new Promise((r) => setTimeout(r, waitMs));
|
|
@@ -7937,6 +7989,7 @@ async function transact(params, options) {
|
|
|
7937
7989
|
// point where they reach the relay submission instead of stopping at viewing-key
|
|
7938
7990
|
// registration. The keypair still wins when both are present.
|
|
7939
7991
|
relayAuthSigner: relayAuthPlan.kind === "wallet" ? relayAuthPlan.signer : void 0,
|
|
7992
|
+
relayAuthBatch: options.relayAuthBatch,
|
|
7940
7993
|
settlement: {
|
|
7941
7994
|
connection,
|
|
7942
7995
|
programId,
|
|
@@ -8338,7 +8391,7 @@ async function swapUtxo(params, options) {
|
|
|
8338
8391
|
riskQuoteUrl: riskQuoteUrlOption,
|
|
8339
8392
|
maxRootRetries = 40,
|
|
8340
8393
|
// Increased to handle prolonged relay sync recovery under high concurrency
|
|
8341
|
-
retryDelayMs = 500,
|
|
8394
|
+
retryDelayMs: retryDelayMs2 = 500,
|
|
8342
8395
|
// Start with short delay, will exponentially back off
|
|
8343
8396
|
useUniqueNullifiers,
|
|
8344
8397
|
useChainRootForProof = true
|
|
@@ -8357,6 +8410,12 @@ async function swapUtxo(params, options) {
|
|
|
8357
8410
|
if (inputUtxos.length > 2) {
|
|
8358
8411
|
throw new Error("Maximum 2 input UTXOs allowed");
|
|
8359
8412
|
}
|
|
8413
|
+
assertInputMints(inputUtxos, NATIVE_SOL_MINT);
|
|
8414
|
+
if (options.expectedMint && !options.expectedMint.equals(NATIVE_SOL_MINT)) {
|
|
8415
|
+
throw new Error(
|
|
8416
|
+
`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.`
|
|
8417
|
+
);
|
|
8418
|
+
}
|
|
8360
8419
|
await preflightNullifiers(inputUtxos, connection, programId);
|
|
8361
8420
|
const outputMintAccount = await connection.getAccountInfo(outputMint, {
|
|
8362
8421
|
commitment: "confirmed"
|
|
@@ -8743,7 +8802,7 @@ async function swapUtxo(params, options) {
|
|
|
8743
8802
|
let walletApprovals = 0;
|
|
8744
8803
|
for (let attempt = 0; attempt <= maxRootRetries; attempt++) {
|
|
8745
8804
|
if (attempt > 0) {
|
|
8746
|
-
const exponentialDelay = Math.min(
|
|
8805
|
+
const exponentialDelay = Math.min(retryDelayMs2 * Math.pow(1.5, attempt - 1), 5e3);
|
|
8747
8806
|
const jitter = Math.random() * 500;
|
|
8748
8807
|
const totalDelay = Math.floor(exponentialDelay + jitter);
|
|
8749
8808
|
await sleep2(totalDelay);
|
|
@@ -8881,7 +8940,7 @@ async function swapUtxo(params, options) {
|
|
|
8881
8940
|
}
|
|
8882
8941
|
merkleState = null;
|
|
8883
8942
|
useSiblingInfo = false;
|
|
8884
|
-
const waitMs = Math.min(
|
|
8943
|
+
const waitMs = Math.min(retryDelayMs2 * Math.pow(2, Math.min(attempt, 4)), 5e3);
|
|
8885
8944
|
if (waitMs > 0) {
|
|
8886
8945
|
onProgress?.(`Waiting ${waitMs}ms for relay sync before retry...`);
|
|
8887
8946
|
await new Promise((r) => setTimeout(r, waitMs));
|
|
@@ -9383,10 +9442,273 @@ function cleanupStalePendingOperations(maxAgeMs = 24 * 60 * 60 * 1e3) {
|
|
|
9383
9442
|
return { removedDeposits, removedWithdrawals };
|
|
9384
9443
|
}
|
|
9385
9444
|
|
|
9445
|
+
// src/relay/batch-auth.ts
|
|
9446
|
+
import { Keypair as Keypair3 } from "@solana/web3.js";
|
|
9447
|
+
import nacl7 from "tweetnacl";
|
|
9448
|
+
var RelayBatchAuthCoordinator = class {
|
|
9449
|
+
constructor(options) {
|
|
9450
|
+
this.proofSlotsInUse = 0;
|
|
9451
|
+
this.proofQueue = [];
|
|
9452
|
+
this.items = [];
|
|
9453
|
+
this.signing = false;
|
|
9454
|
+
this.slotsInUse = 0;
|
|
9455
|
+
this.ready = [];
|
|
9456
|
+
this.waves = 0;
|
|
9457
|
+
if (!Number.isInteger(options.items) || options.items < 1) {
|
|
9458
|
+
throw new Error(`A batch needs at least one item (got ${options.items}).`);
|
|
9459
|
+
}
|
|
9460
|
+
if (options.items > RELAY_BATCH_AUTH_MAX_ITEMS) {
|
|
9461
|
+
throw new Error(
|
|
9462
|
+
`A batch may cover at most ${RELAY_BATCH_AUTH_MAX_ITEMS} requests (got ${options.items}); the relay refuses longer digest lists. Chunk the payout. The practical bound is lower still: each spend appends two roots to the on-chain ring of 100, so a batch proved against one root evicts it by itself before 50 items.`
|
|
9463
|
+
);
|
|
9464
|
+
}
|
|
9465
|
+
const concurrency = options.submitConcurrency ?? 3;
|
|
9466
|
+
if (!Number.isInteger(concurrency) || concurrency < 1) {
|
|
9467
|
+
throw new Error(`submitConcurrency must be a positive integer (got ${concurrency}).`);
|
|
9468
|
+
}
|
|
9469
|
+
this.signer = options.signer;
|
|
9470
|
+
this.sender = options.signer instanceof Keypair3 ? options.signer.publicKey : options.signer.walletPublicKey;
|
|
9471
|
+
this.expectedItems = options.items;
|
|
9472
|
+
this.submitConcurrency = concurrency;
|
|
9473
|
+
const proofConcurrency = options.proofConcurrency ?? 2;
|
|
9474
|
+
if (!Number.isInteger(proofConcurrency) || proofConcurrency < 1) {
|
|
9475
|
+
throw new Error(`proofConcurrency must be a positive integer (got ${proofConcurrency}).`);
|
|
9476
|
+
}
|
|
9477
|
+
this.proofConcurrency = proofConcurrency;
|
|
9478
|
+
this.onProgress = options.onProgress;
|
|
9479
|
+
}
|
|
9480
|
+
/** The wallet that signs and is every item's authenticated `sender`. */
|
|
9481
|
+
get walletPublicKey() {
|
|
9482
|
+
return this.sender;
|
|
9483
|
+
}
|
|
9484
|
+
/** How many times the signer has been asked so far: one per wave. */
|
|
9485
|
+
get approvals() {
|
|
9486
|
+
return this.waves;
|
|
9487
|
+
}
|
|
9488
|
+
/** Where every item is right now. For progress UIs and for diagnosing a batch that never signs. */
|
|
9489
|
+
snapshot() {
|
|
9490
|
+
const count = (state) => this.items.filter((i) => i.state === state).length;
|
|
9491
|
+
return {
|
|
9492
|
+
building: count("building"),
|
|
9493
|
+
waiting: count("waiting"),
|
|
9494
|
+
submitting: count("submitting"),
|
|
9495
|
+
done: count("done"),
|
|
9496
|
+
proving: this.proofSlotsInUse,
|
|
9497
|
+
proofQueue: this.proofQueue.length,
|
|
9498
|
+
registered: this.items.length,
|
|
9499
|
+
expected: this.expectedItems
|
|
9500
|
+
};
|
|
9501
|
+
}
|
|
9502
|
+
/** Register one item. Throws past `items`: the wave condition would never be reachable. */
|
|
9503
|
+
item() {
|
|
9504
|
+
if (this.items.length >= this.expectedItems) {
|
|
9505
|
+
throw new Error(
|
|
9506
|
+
`This batch was created for ${this.expectedItems} item(s) and all of them are registered.`
|
|
9507
|
+
);
|
|
9508
|
+
}
|
|
9509
|
+
const item = { state: "building", waiting: null, holdsSlot: false };
|
|
9510
|
+
this.items.push(item);
|
|
9511
|
+
return {
|
|
9512
|
+
proofSlot: () => this.acquireProofSlot(),
|
|
9513
|
+
authorize: (endpoint, programId, body, fields) => this.authorize(item, endpoint, programId, body, fields),
|
|
9514
|
+
finish: () => this.finish(item)
|
|
9515
|
+
};
|
|
9516
|
+
}
|
|
9517
|
+
acquireProofSlot() {
|
|
9518
|
+
return new Promise((resolve) => {
|
|
9519
|
+
const grant = () => {
|
|
9520
|
+
this.proofSlotsInUse += 1;
|
|
9521
|
+
let released = false;
|
|
9522
|
+
resolve(() => {
|
|
9523
|
+
if (released) return;
|
|
9524
|
+
released = true;
|
|
9525
|
+
this.proofSlotsInUse -= 1;
|
|
9526
|
+
const next = this.proofQueue.shift();
|
|
9527
|
+
if (next) next();
|
|
9528
|
+
});
|
|
9529
|
+
};
|
|
9530
|
+
if (this.proofSlotsInUse < this.proofConcurrency) grant();
|
|
9531
|
+
else this.proofQueue.push(grant);
|
|
9532
|
+
});
|
|
9533
|
+
}
|
|
9534
|
+
authorize(item, endpoint, programId, body, fields) {
|
|
9535
|
+
if (item.state === "done") {
|
|
9536
|
+
return Promise.reject(new Error("This batch item already finished; it cannot authorize."));
|
|
9537
|
+
}
|
|
9538
|
+
if (item.state === "waiting") {
|
|
9539
|
+
return Promise.reject(new Error("This batch item is already waiting for a signature."));
|
|
9540
|
+
}
|
|
9541
|
+
this.releaseSlot(item);
|
|
9542
|
+
return new Promise((resolve, reject) => {
|
|
9543
|
+
item.state = "waiting";
|
|
9544
|
+
item.waiting = { endpoint, programId, body, fields, resolve, reject };
|
|
9545
|
+
this.maybeSign();
|
|
9546
|
+
});
|
|
9547
|
+
}
|
|
9548
|
+
finish(item) {
|
|
9549
|
+
if (item.state === "done") return;
|
|
9550
|
+
if (item.state === "waiting" && item.waiting) {
|
|
9551
|
+
item.waiting.reject(new Error("Batch item finished before it was signed."));
|
|
9552
|
+
item.waiting = null;
|
|
9553
|
+
}
|
|
9554
|
+
this.releaseSlot(item);
|
|
9555
|
+
item.state = "done";
|
|
9556
|
+
this.maybeSign();
|
|
9557
|
+
this.pump();
|
|
9558
|
+
}
|
|
9559
|
+
releaseSlot(item) {
|
|
9560
|
+
if (!item.holdsSlot) return;
|
|
9561
|
+
item.holdsSlot = false;
|
|
9562
|
+
this.slotsInUse -= 1;
|
|
9563
|
+
}
|
|
9564
|
+
/** Sign when every registered item is either waiting here or finished, and nothing is building. */
|
|
9565
|
+
maybeSign() {
|
|
9566
|
+
if (this.signing) return;
|
|
9567
|
+
if (this.items.length < this.expectedItems) return;
|
|
9568
|
+
if (this.items.some((item) => item.state === "building" || item.state === "submitting")) return;
|
|
9569
|
+
const wave = this.items.filter((item) => item.state === "waiting");
|
|
9570
|
+
if (wave.length === 0) return;
|
|
9571
|
+
this.signing = true;
|
|
9572
|
+
void this.signWave(wave).finally(() => {
|
|
9573
|
+
this.signing = false;
|
|
9574
|
+
this.maybeSign();
|
|
9575
|
+
});
|
|
9576
|
+
}
|
|
9577
|
+
async signWave(wave) {
|
|
9578
|
+
const waiting = wave.map((item) => item.waiting);
|
|
9579
|
+
try {
|
|
9580
|
+
const programId = waiting[0].programId;
|
|
9581
|
+
for (const w of waiting) {
|
|
9582
|
+
if (!w.programId.equals(programId)) {
|
|
9583
|
+
throw new Error("Every item of a batch must target the same program id.");
|
|
9584
|
+
}
|
|
9585
|
+
}
|
|
9586
|
+
const nowSeconds = Math.floor(Date.now() / 1e3);
|
|
9587
|
+
const preimages = waiting.map(
|
|
9588
|
+
(w) => buildRelayAuthPreimage(w.endpoint, programId, w.body, this.sender, nowSeconds, w.fields)
|
|
9589
|
+
);
|
|
9590
|
+
const digests = preimages.map(relayRequestDigestHex);
|
|
9591
|
+
const issuedAt = preimages[0].auth_issued_at;
|
|
9592
|
+
const message = buildRelayBatchAuthMessage(programId, issuedAt, digests);
|
|
9593
|
+
this.waves += 1;
|
|
9594
|
+
let signature;
|
|
9595
|
+
if (this.signer instanceof Keypair3) {
|
|
9596
|
+
signature = nacl7.sign.detached(message, this.signer.secretKey);
|
|
9597
|
+
} else {
|
|
9598
|
+
this.onProgress?.(
|
|
9599
|
+
`Approve ${waiting.length} request(s) in your wallet` + (this.waves > 1 ? ` (approval ${this.waves}, for the rows that had to re-prove)` : "") + `. This signs the requests for the relay, not a transaction, and must be approved within a few minutes to stay valid.`
|
|
9600
|
+
);
|
|
9601
|
+
const approvalStartedMs = Date.now();
|
|
9602
|
+
signature = await this.signer.signMessage(message);
|
|
9603
|
+
assertApprovalWithinFreshnessWindow(
|
|
9604
|
+
Date.now() - approvalStartedMs,
|
|
9605
|
+
REQUEST_AUTH_BATCH_MAX_AGE_SECONDS
|
|
9606
|
+
);
|
|
9607
|
+
if (!(signature instanceof Uint8Array) || signature.length !== 64) {
|
|
9608
|
+
throw new Error(
|
|
9609
|
+
`The wallet's signMessage did not return a 64-byte ed25519 detached signature (got ${signature instanceof Uint8Array ? `${signature.length} bytes` : typeof signature}).`
|
|
9610
|
+
);
|
|
9611
|
+
}
|
|
9612
|
+
}
|
|
9613
|
+
const signatureB64 = Buffer.from(signature).toString("base64");
|
|
9614
|
+
wave.forEach((item, i) => {
|
|
9615
|
+
const preimage = preimages[i];
|
|
9616
|
+
this.ready.push({
|
|
9617
|
+
item,
|
|
9618
|
+
fields: {
|
|
9619
|
+
sender: preimage.sender,
|
|
9620
|
+
auth_issued_at: preimage.auth_issued_at,
|
|
9621
|
+
auth_nonce: preimage.auth_nonce,
|
|
9622
|
+
auth_signature: signatureB64,
|
|
9623
|
+
// A fresh array per item: `Object.assign` puts this on the wire body, and the body is
|
|
9624
|
+
// serialized again on every network retry.
|
|
9625
|
+
auth_batch: { digests: [...digests] }
|
|
9626
|
+
}
|
|
9627
|
+
});
|
|
9628
|
+
});
|
|
9629
|
+
this.pump();
|
|
9630
|
+
} catch (error) {
|
|
9631
|
+
for (const item of wave) {
|
|
9632
|
+
const w = item.waiting;
|
|
9633
|
+
item.waiting = null;
|
|
9634
|
+
item.state = "done";
|
|
9635
|
+
w?.reject(error);
|
|
9636
|
+
}
|
|
9637
|
+
}
|
|
9638
|
+
}
|
|
9639
|
+
/** Release signed items to submit, `submitConcurrency` at a time, in signing order. */
|
|
9640
|
+
pump() {
|
|
9641
|
+
while (this.ready.length > 0 && this.slotsInUse < this.submitConcurrency) {
|
|
9642
|
+
const next = this.ready.shift();
|
|
9643
|
+
const w = next.item.waiting;
|
|
9644
|
+
next.item.waiting = null;
|
|
9645
|
+
if (next.item.state !== "waiting" || !w) continue;
|
|
9646
|
+
next.item.state = "submitting";
|
|
9647
|
+
next.item.holdsSlot = true;
|
|
9648
|
+
this.slotsInUse += 1;
|
|
9649
|
+
w.resolve(next.fields);
|
|
9650
|
+
}
|
|
9651
|
+
}
|
|
9652
|
+
};
|
|
9653
|
+
function createRelayBatchAuthCoordinator(options) {
|
|
9654
|
+
return new RelayBatchAuthCoordinator(options);
|
|
9655
|
+
}
|
|
9656
|
+
|
|
9657
|
+
// src/flows/transact-batch.ts
|
|
9658
|
+
async function transactBatch(items, options) {
|
|
9659
|
+
if (items.length === 0) {
|
|
9660
|
+
return { results: [], approvals: 0 };
|
|
9661
|
+
}
|
|
9662
|
+
if (items.length > RELAY_BATCH_AUTH_MAX_ITEMS) {
|
|
9663
|
+
throw new Error(
|
|
9664
|
+
`transactBatch accepts at most ${RELAY_BATCH_AUTH_MAX_ITEMS} items per call (got ${items.length}). Chunk the payout; see RELAY_BATCH_AUTH_MAX_ITEMS for why the practical bound is lower.`
|
|
9665
|
+
);
|
|
9666
|
+
}
|
|
9667
|
+
if (!options.relayUrl) {
|
|
9668
|
+
throw new Error("transactBatch requires relayUrl: batch approval is a relay authentication scheme.");
|
|
9669
|
+
}
|
|
9670
|
+
const { proofConcurrency, submitConcurrency, ...shared } = options;
|
|
9671
|
+
const signer = shared.depositorKeypair ? shared.depositorKeypair : shared.walletPublicKey && shared.signMessage ? { walletPublicKey: shared.walletPublicKey, signMessage: shared.signMessage } : shared.depositorPublicKey && shared.signMessage ? { walletPublicKey: shared.depositorPublicKey, signMessage: shared.signMessage } : null;
|
|
9672
|
+
if (!signer) {
|
|
9673
|
+
throw new Error(
|
|
9674
|
+
"transactBatch requires an authenticated sender: pass `depositorKeypair` (a local Keypair) or `signMessage` together with `walletPublicKey` (a browser wallet adapter). Checked before proof generation, so nothing has been computed or submitted yet."
|
|
9675
|
+
);
|
|
9676
|
+
}
|
|
9677
|
+
const coordinator = createRelayBatchAuthCoordinator({
|
|
9678
|
+
signer,
|
|
9679
|
+
items: items.length,
|
|
9680
|
+
proofConcurrency,
|
|
9681
|
+
submitConcurrency,
|
|
9682
|
+
onProgress: shared.onProgress
|
|
9683
|
+
});
|
|
9684
|
+
const handles = items.map(() => coordinator.item());
|
|
9685
|
+
const results = new Array(items.length);
|
|
9686
|
+
const runOne = async (index) => {
|
|
9687
|
+
const item = items[index];
|
|
9688
|
+
const handle = handles[index];
|
|
9689
|
+
const label = `[batch ${index + 1}/${items.length}]`;
|
|
9690
|
+
try {
|
|
9691
|
+
const value = await transact(item.params, {
|
|
9692
|
+
...shared,
|
|
9693
|
+
...item.options,
|
|
9694
|
+
relayAuthBatch: handle,
|
|
9695
|
+
onProgress: shared.onProgress ? (status) => shared.onProgress?.(`${label} ${status}`) : void 0
|
|
9696
|
+
});
|
|
9697
|
+
results[index] = { status: "fulfilled", value };
|
|
9698
|
+
} catch (reason) {
|
|
9699
|
+
results[index] = { status: "rejected", reason };
|
|
9700
|
+
} finally {
|
|
9701
|
+
handle.finish();
|
|
9702
|
+
}
|
|
9703
|
+
};
|
|
9704
|
+
await Promise.all(items.map((_, index) => runOne(index)));
|
|
9705
|
+
return { results, approvals: coordinator.approvals };
|
|
9706
|
+
}
|
|
9707
|
+
|
|
9386
9708
|
// src/scanning/scan.ts
|
|
9387
9709
|
import _bs583 from "bs58";
|
|
9388
9710
|
import {
|
|
9389
|
-
PublicKey as
|
|
9711
|
+
PublicKey as PublicKey10
|
|
9390
9712
|
} from "@solana/web3.js";
|
|
9391
9713
|
import { getAssociatedTokenAddressSync as getAssociatedTokenAddressSync2 } from "@solana/spl-token";
|
|
9392
9714
|
var bs583 = _bs583.default || _bs583;
|
|
@@ -9474,7 +9796,7 @@ function parseSwapOutputMint(data) {
|
|
|
9474
9796
|
const mintBytes = data.slice(SWAP_OUTPUT_MINT_OFFSET, end);
|
|
9475
9797
|
if (mintBytes.every((byte) => byte === 0)) return void 0;
|
|
9476
9798
|
try {
|
|
9477
|
-
return new
|
|
9799
|
+
return new PublicKey10(mintBytes).toBase58();
|
|
9478
9800
|
} catch {
|
|
9479
9801
|
return void 0;
|
|
9480
9802
|
}
|
|
@@ -9564,7 +9886,7 @@ function parseSwapRecipientAta(data) {
|
|
|
9564
9886
|
const recipientAtaBytes = data.slice(recipientAtaStart, recipientAtaEnd);
|
|
9565
9887
|
if (recipientAtaBytes.every((byte) => byte === 0)) return void 0;
|
|
9566
9888
|
try {
|
|
9567
|
-
return new
|
|
9889
|
+
return new PublicKey10(recipientAtaBytes).toBase58();
|
|
9568
9890
|
} catch {
|
|
9569
9891
|
return void 0;
|
|
9570
9892
|
}
|
|
@@ -9726,7 +10048,7 @@ async function scanSwapNoteCarriers(connection, programId, viewingKeyNk, swapCtx
|
|
|
9726
10048
|
let rpcCalls = 0;
|
|
9727
10049
|
const candidates = Array.from(swapCtxByCommitment.keys());
|
|
9728
10050
|
if (candidates.length === 0) return { records, rpcCalls };
|
|
9729
|
-
const [registry] =
|
|
10051
|
+
const [registry] = PublicKey10.findProgramAddressSync(
|
|
9730
10052
|
[Buffer.from("cloak_chain_note_registry")],
|
|
9731
10053
|
programId
|
|
9732
10054
|
);
|
|
@@ -10129,8 +10451,8 @@ async function scanTransactions(opts) {
|
|
|
10129
10451
|
if (onChainAta) {
|
|
10130
10452
|
try {
|
|
10131
10453
|
const expectedAta = getAssociatedTokenAddressSync2(
|
|
10132
|
-
new
|
|
10133
|
-
new
|
|
10454
|
+
new PublicKey10(asset.mint),
|
|
10455
|
+
new PublicKey10(walletPublicKey)
|
|
10134
10456
|
).toBase58();
|
|
10135
10457
|
if (onChainAta === expectedAta) {
|
|
10136
10458
|
isOurs = true;
|
|
@@ -10344,7 +10666,7 @@ async function scanTransactions(opts) {
|
|
|
10344
10666
|
// deposit — that is the public deposit amount. A deposit that also merged inputs
|
|
10345
10667
|
// carries a larger output 0 and is not recoverable from public data alone.
|
|
10346
10668
|
amount: grossAmount,
|
|
10347
|
-
mintAddress: new
|
|
10669
|
+
mintAddress: new PublicKey10(asset.mint),
|
|
10348
10670
|
outputCommitments: ixCtx.outputCommitments ?? []
|
|
10349
10671
|
});
|
|
10350
10672
|
if (recoveredDeposit) {
|
|
@@ -10370,7 +10692,7 @@ async function scanTransactions(opts) {
|
|
|
10370
10692
|
noteSalt: compactNote.noteSalt,
|
|
10371
10693
|
amount: compactNote.outAmount0,
|
|
10372
10694
|
keypair: { privateKey: 0n, publicKey: compactNote.outPubkey0 },
|
|
10373
|
-
mintAddress: new
|
|
10695
|
+
mintAddress: new PublicKey10(asset.mint),
|
|
10374
10696
|
outputIndex: 0,
|
|
10375
10697
|
// v4 describes output 0, which is where change lands
|
|
10376
10698
|
outputCommitments: ixCtx.outputCommitments ?? []
|
|
@@ -10563,7 +10885,7 @@ function formatComplianceCsv(report) {
|
|
|
10563
10885
|
}
|
|
10564
10886
|
|
|
10565
10887
|
// src/wallet/utxo-wallet.ts
|
|
10566
|
-
import { PublicKey as
|
|
10888
|
+
import { PublicKey as PublicKey11 } from "@solana/web3.js";
|
|
10567
10889
|
var UtxoWallet = class _UtxoWallet {
|
|
10568
10890
|
constructor(viewingKey) {
|
|
10569
10891
|
this.wallets = /* @__PURE__ */ new Map();
|
|
@@ -10775,7 +11097,7 @@ var UtxoWallet = class _UtxoWallet {
|
|
|
10775
11097
|
data.viewingKey ? new Uint8Array(data.viewingKey) : void 0
|
|
10776
11098
|
);
|
|
10777
11099
|
for (const w of data.wallets) {
|
|
10778
|
-
const mint = new
|
|
11100
|
+
const mint = new PublicKey11(w.mint);
|
|
10779
11101
|
for (const u of w.utxos) {
|
|
10780
11102
|
wallet.addUtxo({
|
|
10781
11103
|
amount: BigInt(u.amount),
|
|
@@ -10857,12 +11179,799 @@ var SimpleWallet = class {
|
|
|
10857
11179
|
}
|
|
10858
11180
|
};
|
|
10859
11181
|
|
|
11182
|
+
// src/bridge/rail-verify.ts
|
|
11183
|
+
import nacl8 from "tweetnacl";
|
|
11184
|
+
import { sha256 as sha2565 } from "@noble/hashes/sha256";
|
|
11185
|
+
var ONECLICK_PUBKEY_B58 = "reYaWhvwu8Jzo3WUM3zhn6VrhuMEF4eADL17qtRVifc";
|
|
11186
|
+
var b58 = /* @__PURE__ */ (() => {
|
|
11187
|
+
const A = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
11188
|
+
return {
|
|
11189
|
+
decode(s) {
|
|
11190
|
+
let n = 0n;
|
|
11191
|
+
for (const c of s) {
|
|
11192
|
+
const i = A.indexOf(c);
|
|
11193
|
+
if (i < 0) throw new Error("bad base58");
|
|
11194
|
+
n = n * 58n + BigInt(i);
|
|
11195
|
+
}
|
|
11196
|
+
const bytes = [];
|
|
11197
|
+
while (n > 0n) {
|
|
11198
|
+
bytes.unshift(Number(n & 255n));
|
|
11199
|
+
n >>= 8n;
|
|
11200
|
+
}
|
|
11201
|
+
for (const c of s) {
|
|
11202
|
+
if (c === "1") bytes.unshift(0);
|
|
11203
|
+
else break;
|
|
11204
|
+
}
|
|
11205
|
+
return Uint8Array.from(bytes);
|
|
11206
|
+
},
|
|
11207
|
+
encode(b) {
|
|
11208
|
+
let n = 0n;
|
|
11209
|
+
for (const x of b) n = n * 256n + BigInt(x);
|
|
11210
|
+
let s = "";
|
|
11211
|
+
while (n > 0n) {
|
|
11212
|
+
s = A[Number(n % 58n)] + s;
|
|
11213
|
+
n /= 58n;
|
|
11214
|
+
}
|
|
11215
|
+
for (const x of b) {
|
|
11216
|
+
if (x === 0) s = "1" + s;
|
|
11217
|
+
else break;
|
|
11218
|
+
}
|
|
11219
|
+
return s;
|
|
11220
|
+
}
|
|
11221
|
+
};
|
|
11222
|
+
})();
|
|
11223
|
+
function stable(v) {
|
|
11224
|
+
if (v === null || typeof v !== "object") return JSON.stringify(v) ?? "null";
|
|
11225
|
+
if (Array.isArray(v)) return `[${v.map(stable).join(",")}]`;
|
|
11226
|
+
const o = v;
|
|
11227
|
+
const parts = Object.keys(o).sort().filter((k) => o[k] !== void 0).map((k) => `${JSON.stringify(k)}:${stable(o[k])}`);
|
|
11228
|
+
return `{${parts.join(",")}}`;
|
|
11229
|
+
}
|
|
11230
|
+
function signedRequest(r) {
|
|
11231
|
+
const q = r.quoteRequest ?? {};
|
|
11232
|
+
return {
|
|
11233
|
+
dry: q.dry,
|
|
11234
|
+
swapType: q.swapType,
|
|
11235
|
+
slippageTolerance: q.slippageTolerance,
|
|
11236
|
+
originAsset: q.originAsset,
|
|
11237
|
+
depositType: q.depositType,
|
|
11238
|
+
destinationAsset: q.destinationAsset,
|
|
11239
|
+
amount: q.amount,
|
|
11240
|
+
refundTo: q.refundTo,
|
|
11241
|
+
refundType: q.refundType,
|
|
11242
|
+
recipient: q.recipient,
|
|
11243
|
+
recipientType: q.recipientType,
|
|
11244
|
+
deadline: q.deadline,
|
|
11245
|
+
quoteWaitingTimeMs: q.quoteWaitingTimeMs || void 0,
|
|
11246
|
+
referral: q.referral || void 0,
|
|
11247
|
+
virtualChainRecipient: q.virtualChainRecipient || void 0,
|
|
11248
|
+
virtualChainRefundRecipient: q.virtualChainRefundRecipient || void 0,
|
|
11249
|
+
customRecipientMsg: q.customRecipientMsg || void 0
|
|
11250
|
+
// Deliberately unsigned by the rail: sessionId, connectedWallets, correlationId, appFees,
|
|
11251
|
+
// partnerId, userAccountId, depositMode. APP FEES ARE NOT AUTHENTICATED — never display a fee
|
|
11252
|
+
// caption as if the signature vouched for it.
|
|
11253
|
+
};
|
|
11254
|
+
}
|
|
11255
|
+
function signedQuote(r) {
|
|
11256
|
+
const q = r.quote ?? {};
|
|
11257
|
+
const base = {
|
|
11258
|
+
amountIn: q.amountIn,
|
|
11259
|
+
amountInFormatted: q.amountInFormatted,
|
|
11260
|
+
amountInUsd: q.amountInUsd,
|
|
11261
|
+
minAmountIn: q.minAmountIn,
|
|
11262
|
+
amountOut: q.amountOut,
|
|
11263
|
+
amountOutFormatted: q.amountOutFormatted,
|
|
11264
|
+
amountOutUsd: q.amountOutUsd,
|
|
11265
|
+
minAmountOut: q.minAmountOut
|
|
11266
|
+
};
|
|
11267
|
+
if (r.quoteRequest?.dry) return base;
|
|
11268
|
+
return {
|
|
11269
|
+
...base,
|
|
11270
|
+
depositAddress: q.depositAddress || void 0,
|
|
11271
|
+
// <- the field that makes substitution detectable
|
|
11272
|
+
depositMemo: q.depositMemo || void 0,
|
|
11273
|
+
deadline: q.deadline || void 0,
|
|
11274
|
+
// <- a real, SIGNED expiry
|
|
11275
|
+
timeWhenInactive: q.timeWhenInactive || void 0,
|
|
11276
|
+
timeEstimate: q.timeEstimate || void 0,
|
|
11277
|
+
virtualChainRecipient: q.virtualChainRecipient || void 0,
|
|
11278
|
+
virtualChainRefundRecipient: q.virtualChainRefundRecipient || void 0,
|
|
11279
|
+
customRecipientMsg: q.customRecipientMsg || void 0,
|
|
11280
|
+
refundFee: q.refundFee || void 0,
|
|
11281
|
+
withdrawFee: q.withdrawFee || void 0
|
|
11282
|
+
};
|
|
11283
|
+
}
|
|
11284
|
+
function canonicalPayloadString(resp) {
|
|
11285
|
+
return stable({ ...signedRequest(resp), ...signedQuote(resp), timestamp: resp.timestamp });
|
|
11286
|
+
}
|
|
11287
|
+
function verifyQuoteSignature(resp) {
|
|
11288
|
+
if (!resp?.signature) return { valid: false, reason: "no signature on the response" };
|
|
11289
|
+
const payload = { ...signedRequest(resp), ...signedQuote(resp), timestamp: resp.timestamp };
|
|
11290
|
+
const digest = sha2565(new TextEncoder().encode(stable(payload)));
|
|
11291
|
+
const message = new TextEncoder().encode(b58.encode(new Uint8Array(digest)));
|
|
11292
|
+
const sig = resp.signature.replace(/^ed25519:/, "");
|
|
11293
|
+
try {
|
|
11294
|
+
return { valid: nacl8.sign.detached.verify(message, b58.decode(sig), b58.decode(ONECLICK_PUBKEY_B58)) };
|
|
11295
|
+
} catch (e) {
|
|
11296
|
+
return { valid: false, reason: e instanceof Error ? e.message : String(e) };
|
|
11297
|
+
}
|
|
11298
|
+
}
|
|
11299
|
+
|
|
11300
|
+
// src/bridge/rails.ts
|
|
11301
|
+
async function parseBridgeResponse(res, what) {
|
|
11302
|
+
let body;
|
|
11303
|
+
try {
|
|
11304
|
+
body = await res.json();
|
|
11305
|
+
} catch {
|
|
11306
|
+
throw new Error(`${what} failed: ${res.status} ${res.statusText} (response body was not JSON)`);
|
|
11307
|
+
}
|
|
11308
|
+
if (!res.ok) {
|
|
11309
|
+
const b = body ?? {};
|
|
11310
|
+
throw new Error(
|
|
11311
|
+
`${what} failed: ${b.message ?? res.statusText} (${b.code ?? res.status}${b.retryable ? ", retryable" : ""})`
|
|
11312
|
+
);
|
|
11313
|
+
}
|
|
11314
|
+
return body;
|
|
11315
|
+
}
|
|
11316
|
+
function toAttestation(raw) {
|
|
11317
|
+
if (raw.kind === "ed25519") {
|
|
11318
|
+
return { kind: "ed25519", verified: raw.verified ?? false, signer: raw.signer ?? "" };
|
|
11319
|
+
}
|
|
11320
|
+
return { kind: "none", checks: raw.checks ?? [], note: raw.note ?? "" };
|
|
11321
|
+
}
|
|
11322
|
+
async function fetchBridgeQuote(relayUrl, req) {
|
|
11323
|
+
const body = {
|
|
11324
|
+
recipient: req.recipient,
|
|
11325
|
+
origin_chain: req.originChain,
|
|
11326
|
+
amount_base_units: req.amountBaseUnits.toString(),
|
|
11327
|
+
allocate: req.allocate ?? false
|
|
11328
|
+
};
|
|
11329
|
+
if (req.refundTo !== void 0) body.refund_to = req.refundTo;
|
|
11330
|
+
if (req.rails !== void 0) body.rails = req.rails;
|
|
11331
|
+
const res = await relayFetch(`${relayUrl}/bridge/quote`, {
|
|
11332
|
+
method: "POST",
|
|
11333
|
+
headers: { "Content-Type": "application/json" },
|
|
11334
|
+
body: JSON.stringify(body)
|
|
11335
|
+
});
|
|
11336
|
+
const raw = await parseBridgeResponse(res, "bridge quote");
|
|
11337
|
+
const options = [];
|
|
11338
|
+
const unavailable = (raw.unavailable ?? []).map((p) => ({
|
|
11339
|
+
rail: p.rail,
|
|
11340
|
+
reason: p.reason
|
|
11341
|
+
}));
|
|
11342
|
+
for (const opt of raw.options) {
|
|
11343
|
+
const attestation = toAttestation(opt.attestation);
|
|
11344
|
+
if (attestation.kind === "ed25519") {
|
|
11345
|
+
const verdict = opt.rail_response ? verifyQuoteSignature(opt.rail_response) : { valid: false, reason: "no rail_response to verify against" };
|
|
11346
|
+
if (!verdict.valid) {
|
|
11347
|
+
unavailable.push({
|
|
11348
|
+
rail: opt.rail,
|
|
11349
|
+
reason: `deposit address failed independent signature verification, refusing to display it (${verdict.reason ?? "signature did not verify"})`
|
|
11350
|
+
});
|
|
11351
|
+
continue;
|
|
11352
|
+
}
|
|
11353
|
+
}
|
|
11354
|
+
options.push({
|
|
11355
|
+
rail: opt.rail,
|
|
11356
|
+
refunds: opt.refunds,
|
|
11357
|
+
addressLifetime: opt.address_lifetime,
|
|
11358
|
+
amountOut: BigInt(opt.amount_out),
|
|
11359
|
+
minAmountOut: BigInt(opt.min_amount_out),
|
|
11360
|
+
timeEstimateSeconds: opt.time_estimate_s,
|
|
11361
|
+
expiresAt: opt.expires_at,
|
|
11362
|
+
depositAddress: opt.deposit_address,
|
|
11363
|
+
attestation
|
|
11364
|
+
});
|
|
11365
|
+
}
|
|
11366
|
+
return { options, unavailable };
|
|
11367
|
+
}
|
|
11368
|
+
var STATUS_STATES = [
|
|
11369
|
+
"pending",
|
|
11370
|
+
"delivered",
|
|
11371
|
+
"refunded",
|
|
11372
|
+
"expired",
|
|
11373
|
+
"unknown"
|
|
11374
|
+
];
|
|
11375
|
+
async function fetchBridgeStatus(relayUrl, depositAddress, rail) {
|
|
11376
|
+
const qs = new URLSearchParams({ deposit_address: depositAddress, rail });
|
|
11377
|
+
const res = await relayFetch(`${relayUrl}/bridge/status?${qs.toString()}`);
|
|
11378
|
+
const raw = await parseBridgeResponse(res, "bridge status");
|
|
11379
|
+
const state = STATUS_STATES.includes(raw.state ?? "") ? raw.state : "unknown";
|
|
11380
|
+
return { rail: raw.rail ?? rail, state, detail: raw.detail ?? "" };
|
|
11381
|
+
}
|
|
11382
|
+
function cloakBridgeRail(relayUrl) {
|
|
11383
|
+
assertAllowedRelayOrigin(relayUrl);
|
|
11384
|
+
return {
|
|
11385
|
+
id: "cloak-bridge",
|
|
11386
|
+
quote: (req) => fetchBridgeQuote(relayUrl, req),
|
|
11387
|
+
status: (depositAddress, rail) => fetchBridgeStatus(relayUrl, depositAddress, rail)
|
|
11388
|
+
};
|
|
11389
|
+
}
|
|
11390
|
+
|
|
11391
|
+
// src/bridge/paymaster-client.ts
|
|
11392
|
+
import {
|
|
11393
|
+
PublicKey as PublicKey12,
|
|
11394
|
+
SystemInstruction,
|
|
11395
|
+
SystemProgram as SystemProgram3,
|
|
11396
|
+
Transaction as Transaction3
|
|
11397
|
+
} from "@solana/web3.js";
|
|
11398
|
+
import { TOKEN_PROGRAM_ID as TOKEN_PROGRAM_ID3, decodeTransferInstruction, getAssociatedTokenAddressSync as getAssociatedTokenAddressSync3 } from "@solana/spl-token";
|
|
11399
|
+
function validatePaymasterTopUpTransaction(tx, expect) {
|
|
11400
|
+
const ixs = tx.instructions;
|
|
11401
|
+
if (ixs.length !== 1 && ixs.length !== 2) {
|
|
11402
|
+
throw new Error(
|
|
11403
|
+
`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`
|
|
11404
|
+
);
|
|
11405
|
+
}
|
|
11406
|
+
const ix0 = ixs[0];
|
|
11407
|
+
if (!ix0.programId.equals(SystemProgram3.programId)) {
|
|
11408
|
+
throw new Error(
|
|
11409
|
+
`paymaster top-up instruction 0 is owned by ${ix0.programId.toBase58()}, not the System Program \u2014 refusing to sign an unrecognised first instruction`
|
|
11410
|
+
);
|
|
11411
|
+
}
|
|
11412
|
+
const transfer2 = SystemInstruction.decodeTransfer(ix0);
|
|
11413
|
+
if (!transfer2.toPubkey.equals(expect.recipient)) {
|
|
11414
|
+
throw new Error(
|
|
11415
|
+
`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`
|
|
11416
|
+
);
|
|
11417
|
+
}
|
|
11418
|
+
if (BigInt(transfer2.lamports) > expect.maxGrantLamports) {
|
|
11419
|
+
throw new Error(
|
|
11420
|
+
`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`
|
|
11421
|
+
);
|
|
11422
|
+
}
|
|
11423
|
+
if (ixs.length === 1) return;
|
|
11424
|
+
const ix1 = ixs[1];
|
|
11425
|
+
if (!ix1.programId.equals(TOKEN_PROGRAM_ID3)) {
|
|
11426
|
+
throw new Error(
|
|
11427
|
+
`paymaster top-up instruction 1 is owned by ${ix1.programId.toBase58()}, not the SPL Token program \u2014 refusing to sign an unrecognised second instruction`
|
|
11428
|
+
);
|
|
11429
|
+
}
|
|
11430
|
+
const spl = decodeTransferInstruction(ix1, TOKEN_PROGRAM_ID3);
|
|
11431
|
+
const authority = spl.keys.owner.pubkey;
|
|
11432
|
+
if (!authority.equals(expect.recipient)) {
|
|
11433
|
+
throw new Error(
|
|
11434
|
+
`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`
|
|
11435
|
+
);
|
|
11436
|
+
}
|
|
11437
|
+
const expectedDestination = getAssociatedTokenAddressSync3(
|
|
11438
|
+
expect.feeMint,
|
|
11439
|
+
expect.paymentAddress,
|
|
11440
|
+
false
|
|
11441
|
+
);
|
|
11442
|
+
const destination = spl.keys.destination.pubkey;
|
|
11443
|
+
if (!destination.equals(expectedDestination)) {
|
|
11444
|
+
throw new Error(
|
|
11445
|
+
`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`
|
|
11446
|
+
);
|
|
11447
|
+
}
|
|
11448
|
+
const paidAmount = BigInt(spl.data.amount);
|
|
11449
|
+
if (paidAmount !== expect.feeTokenAmount) {
|
|
11450
|
+
throw new Error(
|
|
11451
|
+
`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`
|
|
11452
|
+
);
|
|
11453
|
+
}
|
|
11454
|
+
}
|
|
11455
|
+
async function parseBridgeResponse2(res, what) {
|
|
11456
|
+
let body;
|
|
11457
|
+
try {
|
|
11458
|
+
body = await res.json();
|
|
11459
|
+
} catch {
|
|
11460
|
+
throw new Error(`${what} failed: ${res.status} ${res.statusText} (response body was not JSON)`);
|
|
11461
|
+
}
|
|
11462
|
+
if (!res.ok) {
|
|
11463
|
+
const b = body ?? {};
|
|
11464
|
+
throw new Error(
|
|
11465
|
+
`${what} failed: ${b.message ?? res.statusText} (${b.code ?? res.status}${b.retryable ? ", retryable" : ""})`
|
|
11466
|
+
);
|
|
11467
|
+
}
|
|
11468
|
+
return body;
|
|
11469
|
+
}
|
|
11470
|
+
async function fundReceiverViaPaymaster(relayUrl, receiver, grantLamports) {
|
|
11471
|
+
assertAllowedRelayOrigin(relayUrl);
|
|
11472
|
+
if (grantLamports <= 0n || grantLamports > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
11473
|
+
throw new Error(
|
|
11474
|
+
`grantLamports must be a positive value representable as a JS number, got ${grantLamports}`
|
|
11475
|
+
);
|
|
11476
|
+
}
|
|
11477
|
+
const prepareRes = await relayFetch(`${relayUrl}/bridge/paymaster/prepare`, {
|
|
11478
|
+
method: "POST",
|
|
11479
|
+
headers: { "Content-Type": "application/json" },
|
|
11480
|
+
body: JSON.stringify({
|
|
11481
|
+
recipient: receiver.publicKey.toBase58(),
|
|
11482
|
+
grant_lamports: Number(grantLamports)
|
|
11483
|
+
})
|
|
11484
|
+
});
|
|
11485
|
+
const prepared = await parseBridgeResponse2(prepareRes, "paymaster prepare");
|
|
11486
|
+
if (Math.floor(Date.now() / 1e3) >= prepared.expires_at_unix) {
|
|
11487
|
+
throw new Error(
|
|
11488
|
+
"paymaster prepare returned a voucher that is already expired \u2014 refusing to sign a stale top-up rather than fail confusingly at cosign"
|
|
11489
|
+
);
|
|
11490
|
+
}
|
|
11491
|
+
const feeMint = new PublicKey12(prepared.fee_mint);
|
|
11492
|
+
const paymentAddress = new PublicKey12(prepared.payment_address);
|
|
11493
|
+
const feeTokenAmount = BigInt(prepared.fee_token_amount);
|
|
11494
|
+
const tx = Transaction3.from(Buffer.from(prepared.transaction, "base64"));
|
|
11495
|
+
validatePaymasterTopUpTransaction(tx, {
|
|
11496
|
+
recipient: receiver.publicKey,
|
|
11497
|
+
maxGrantLamports: grantLamports,
|
|
11498
|
+
feeMint,
|
|
11499
|
+
feeTokenAmount,
|
|
11500
|
+
paymentAddress
|
|
11501
|
+
});
|
|
11502
|
+
tx.partialSign(receiver);
|
|
11503
|
+
const cosignRes = await relayFetch(`${relayUrl}/bridge/paymaster/cosign`, {
|
|
11504
|
+
method: "POST",
|
|
11505
|
+
headers: { "Content-Type": "application/json" },
|
|
11506
|
+
body: JSON.stringify({
|
|
11507
|
+
transaction: tx.serialize({ requireAllSignatures: false, verifySignatures: false }).toString("base64"),
|
|
11508
|
+
voucher: prepared.voucher
|
|
11509
|
+
})
|
|
11510
|
+
});
|
|
11511
|
+
const cosigned = await parseBridgeResponse2(cosignRes, "paymaster cosign");
|
|
11512
|
+
return {
|
|
11513
|
+
transaction: Transaction3.from(Buffer.from(cosigned.transaction, "base64")),
|
|
11514
|
+
feeTokenAmount,
|
|
11515
|
+
feeMint: prepared.fee_mint,
|
|
11516
|
+
paymentAddress: prepared.payment_address
|
|
11517
|
+
};
|
|
11518
|
+
}
|
|
11519
|
+
|
|
11520
|
+
// src/bridge/derive.ts
|
|
11521
|
+
import { Keypair as Keypair5 } from "@solana/web3.js";
|
|
11522
|
+
import { hmac } from "@noble/hashes/hmac";
|
|
11523
|
+
import { sha512 } from "@noble/hashes/sha512";
|
|
11524
|
+
var BRIDGE_ESCROW_LABEL = "cloak_bridge_escrow";
|
|
11525
|
+
var MAX_RECEIVER_INDEX = 1e4;
|
|
11526
|
+
function deriveBridgeReceiver(nk, index) {
|
|
11527
|
+
if (!Number.isInteger(index) || index < 0) {
|
|
11528
|
+
throw new Error(`index must be a non-negative integer, got ${index}`);
|
|
11529
|
+
}
|
|
11530
|
+
if (index > MAX_RECEIVER_INDEX) {
|
|
11531
|
+
throw new Error(
|
|
11532
|
+
`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.`
|
|
11533
|
+
);
|
|
11534
|
+
}
|
|
11535
|
+
const h = hmac(sha512, new Uint8Array(nk), new TextEncoder().encode(`${BRIDGE_ESCROW_LABEL}:${index}`));
|
|
11536
|
+
return Keypair5.fromSeed(h.slice(0, 32));
|
|
11537
|
+
}
|
|
11538
|
+
|
|
11539
|
+
// src/bridge/discover.ts
|
|
11540
|
+
import { SolanaJSONRPCError } from "@solana/web3.js";
|
|
11541
|
+
import { getAssociatedTokenAddressSync as getAssociatedTokenAddressSync4 } from "@solana/spl-token";
|
|
11542
|
+
function describeError(err) {
|
|
11543
|
+
return err instanceof Error ? err.message : String(err);
|
|
11544
|
+
}
|
|
11545
|
+
async function listBridgeDeposits(conn, nk, opts) {
|
|
11546
|
+
const scanDepth = Math.min(opts.scanDepth ?? 20, MAX_RECEIVER_INDEX + 1);
|
|
11547
|
+
const stopAfterUnused = opts.stopAfterUnused ?? 5;
|
|
11548
|
+
const out = [];
|
|
11549
|
+
let consecutiveUnused = 0;
|
|
11550
|
+
for (let index = 0; index < scanDepth; index++) {
|
|
11551
|
+
const kp = deriveBridgeReceiver(nk, index);
|
|
11552
|
+
const receiver = kp.publicKey;
|
|
11553
|
+
const tokenAccount = getAssociatedTokenAddressSync4(opts.mint, receiver, false);
|
|
11554
|
+
try {
|
|
11555
|
+
const [recvInfo, ataInfo] = await conn.getMultipleAccountsInfo([receiver, tokenAccount]);
|
|
11556
|
+
const lamports = recvInfo?.lamports ?? 0;
|
|
11557
|
+
let tokenBalance = 0n;
|
|
11558
|
+
if (ataInfo) {
|
|
11559
|
+
try {
|
|
11560
|
+
tokenBalance = BigInt((await conn.getTokenAccountBalance(tokenAccount)).value.amount);
|
|
11561
|
+
} catch (err) {
|
|
11562
|
+
if (!(err instanceof SolanaJSONRPCError && /could not find account/i.test(err.message))) {
|
|
11563
|
+
throw err;
|
|
11564
|
+
}
|
|
11565
|
+
}
|
|
11566
|
+
}
|
|
11567
|
+
if (lamports === 0 && !ataInfo) {
|
|
11568
|
+
consecutiveUnused++;
|
|
11569
|
+
if (consecutiveUnused >= stopAfterUnused) break;
|
|
11570
|
+
continue;
|
|
11571
|
+
}
|
|
11572
|
+
consecutiveUnused = 0;
|
|
11573
|
+
const shieldSignatures = [];
|
|
11574
|
+
const sigs = await conn.getSignaturesForAddress(receiver, { limit: 20 });
|
|
11575
|
+
for (const s of sigs) {
|
|
11576
|
+
if (s.err) continue;
|
|
11577
|
+
const tx = await conn.getTransaction(s.signature, { maxSupportedTransactionVersion: 0 });
|
|
11578
|
+
const keys = tx?.transaction.message.getAccountKeys({
|
|
11579
|
+
accountKeysFromLookups: tx.meta?.loadedAddresses
|
|
11580
|
+
});
|
|
11581
|
+
if (keys && [...Array(keys.length).keys()].some((i) => keys.get(i)?.equals(opts.programId))) {
|
|
11582
|
+
shieldSignatures.push(s.signature);
|
|
11583
|
+
}
|
|
11584
|
+
}
|
|
11585
|
+
const shieldSignature = shieldSignatures[0];
|
|
11586
|
+
const indexReused = shieldSignatures.length > 1;
|
|
11587
|
+
let state;
|
|
11588
|
+
if (tokenBalance > 0n) state = shieldSignature ? "needs-cleanup" : "arrived";
|
|
11589
|
+
else if (shieldSignature) state = ataInfo ? "needs-cleanup" : "complete";
|
|
11590
|
+
else state = "awaiting";
|
|
11591
|
+
out.push({
|
|
11592
|
+
index,
|
|
11593
|
+
receiver,
|
|
11594
|
+
tokenAccount,
|
|
11595
|
+
state,
|
|
11596
|
+
tokenBalance,
|
|
11597
|
+
lamports,
|
|
11598
|
+
shieldSignature,
|
|
11599
|
+
...indexReused ? { indexReused, shieldSignatures } : {}
|
|
11600
|
+
});
|
|
11601
|
+
} catch (err) {
|
|
11602
|
+
out.push({
|
|
11603
|
+
index,
|
|
11604
|
+
receiver,
|
|
11605
|
+
tokenAccount,
|
|
11606
|
+
state: "unknown",
|
|
11607
|
+
tokenBalance: 0n,
|
|
11608
|
+
lamports: 0,
|
|
11609
|
+
error: describeError(err)
|
|
11610
|
+
});
|
|
11611
|
+
}
|
|
11612
|
+
}
|
|
11613
|
+
return out;
|
|
11614
|
+
}
|
|
11615
|
+
|
|
11616
|
+
// src/bridge/deposit-core.ts
|
|
11617
|
+
import {
|
|
11618
|
+
SystemProgram as SystemProgram4,
|
|
11619
|
+
Transaction as Transaction4,
|
|
11620
|
+
VersionedTransaction as VersionedTransaction2,
|
|
11621
|
+
sendAndConfirmTransaction as sendAndConfirmTransaction2
|
|
11622
|
+
} from "@solana/web3.js";
|
|
11623
|
+
import nacl9 from "tweetnacl";
|
|
11624
|
+
|
|
11625
|
+
// src/bridge/funder.ts
|
|
11626
|
+
import { PublicKey as PublicKey14 } from "@solana/web3.js";
|
|
11627
|
+
import { getAssociatedTokenAddressSync as getAssociatedTokenAddressSync5 } from "@solana/spl-token";
|
|
11628
|
+
var MAINNET_RENT_0 = 890880;
|
|
11629
|
+
var MAINNET_RENT_1 = 897840;
|
|
11630
|
+
var FEE_BUDGET = 13e4;
|
|
11631
|
+
var CLEANUP_FEE_BUDGET = 5e3;
|
|
11632
|
+
var pda = (seeds, programId) => PublicKey14.findProgramAddressSync(seeds.map((s) => Buffer.from(s)), programId)[0];
|
|
11633
|
+
function deriveFundingTargets(d) {
|
|
11634
|
+
const pool = pda([Buffer.from("pool"), d.mint.toBuffer()], d.programId);
|
|
11635
|
+
return {
|
|
11636
|
+
pool,
|
|
11637
|
+
// program: NullifierAccount::derive_pda_with_pool -> [b"nullifier", pool, nullifier]
|
|
11638
|
+
nullifier0: pda([Buffer.from("nullifier"), pool.toBuffer(), d.nullifiers[0]], d.programId),
|
|
11639
|
+
nullifier1: pda([Buffer.from("nullifier"), pool.toBuffer(), d.nullifiers[1]], d.programId),
|
|
11640
|
+
// relay: derive_risk_nonce_pda -> [b"risk_nonce", bind0]
|
|
11641
|
+
riskNonce: pda([Buffer.from("risk_nonce"), d.bind0], d.programId),
|
|
11642
|
+
depositorAta: getAssociatedTokenAddressSync5(d.mint, d.depositor, false)
|
|
11643
|
+
};
|
|
11644
|
+
}
|
|
11645
|
+
async function readRent(conn) {
|
|
11646
|
+
const [zero, one] = await Promise.all([
|
|
11647
|
+
conn.getMinimumBalanceForRentExemption(0),
|
|
11648
|
+
conn.getMinimumBalanceForRentExemption(1)
|
|
11649
|
+
]);
|
|
11650
|
+
return { zero, one };
|
|
11651
|
+
}
|
|
11652
|
+
|
|
11653
|
+
// src/bridge/deposit-core.ts
|
|
11654
|
+
var DepositError = class extends Error {
|
|
11655
|
+
constructor(message, fundedAccounts, measuredTxSize) {
|
|
11656
|
+
super(message);
|
|
11657
|
+
this.fundedAccounts = fundedAccounts;
|
|
11658
|
+
this.measuredTxSize = measuredTxSize;
|
|
11659
|
+
this.name = "DepositError";
|
|
11660
|
+
}
|
|
11661
|
+
};
|
|
11662
|
+
async function depositFromDerivedKey(conn, R, funder, amount, log = console.log, grantOverride, opts) {
|
|
11663
|
+
if (!opts.noteSpendKey || opts.noteSpendKey.length !== 32) {
|
|
11664
|
+
throw new Error(
|
|
11665
|
+
"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."
|
|
11666
|
+
);
|
|
11667
|
+
}
|
|
11668
|
+
if (!opts.relayUrl) {
|
|
11669
|
+
throw new Error(
|
|
11670
|
+
"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."
|
|
11671
|
+
);
|
|
11672
|
+
}
|
|
11673
|
+
if (!opts.programId) {
|
|
11674
|
+
throw new Error(
|
|
11675
|
+
"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."
|
|
11676
|
+
);
|
|
11677
|
+
}
|
|
11678
|
+
if (!opts.mint) {
|
|
11679
|
+
throw new Error(
|
|
11680
|
+
"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."
|
|
11681
|
+
);
|
|
11682
|
+
}
|
|
11683
|
+
assertAllowedRpcConnection(conn);
|
|
11684
|
+
const relayUrl = opts.relayUrl;
|
|
11685
|
+
const programId = opts.programId;
|
|
11686
|
+
const mint = opts.mint;
|
|
11687
|
+
setCircuitsPath(resolveCircuitsBase());
|
|
11688
|
+
const grantR = grantOverride ?? MAINNET_RENT_0 + FEE_BUDGET + CLEANUP_FEE_BUDGET;
|
|
11689
|
+
const heldByR = await conn.getBalance(R.publicKey);
|
|
11690
|
+
if (heldByR >= grantR) {
|
|
11691
|
+
log(` R already holds ${heldByR} (>= ${grantR}) \u2014 no top-up needed`);
|
|
11692
|
+
} else {
|
|
11693
|
+
await sendAndConfirmTransaction2(conn, new Transaction4().add(
|
|
11694
|
+
SystemProgram4.transfer({ fromPubkey: funder.publicKey, toPubkey: R.publicKey, lamports: grantR - heldByR })
|
|
11695
|
+
), [funder]);
|
|
11696
|
+
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"}`);
|
|
11697
|
+
}
|
|
11698
|
+
let measuredSize = -1;
|
|
11699
|
+
let fundedAccounts = [];
|
|
11700
|
+
const signTransaction2 = async (tx) => {
|
|
11701
|
+
if (tx instanceof VersionedTransaction2) {
|
|
11702
|
+
measuredSize = tx.serialize().length;
|
|
11703
|
+
const alts = await Promise.all(
|
|
11704
|
+
tx.message.addressTableLookups.map(async (l) => (await conn.getAddressLookupTable(l.accountKey)).value)
|
|
11705
|
+
);
|
|
11706
|
+
const keys = tx.message.getAccountKeys({ addressLookupTableAccounts: alts });
|
|
11707
|
+
const ix = tx.message.compiledInstructions.find((i) => keys.get(i.programIdIndex).equals(programId));
|
|
11708
|
+
const at = (n) => keys.get(ix.accountKeyIndexes[n]);
|
|
11709
|
+
const targets = [
|
|
11710
|
+
{ a: at(12), need: MAINNET_RENT_0, name: "risk_nonce" },
|
|
11711
|
+
{ a: at(4), need: MAINNET_RENT_1, name: "nullifier_0" },
|
|
11712
|
+
{ a: at(5), need: MAINNET_RENT_1, name: "nullifier_1" }
|
|
11713
|
+
];
|
|
11714
|
+
const infos = await conn.getMultipleAccountsInfo(targets.map((t) => t.a));
|
|
11715
|
+
fundedAccounts = targets.map((t, i) => ({
|
|
11716
|
+
name: t.name,
|
|
11717
|
+
address: t.a.toBase58(),
|
|
11718
|
+
lamportsSent: Math.max(0, t.need - (infos[i]?.lamports ?? 0))
|
|
11719
|
+
}));
|
|
11720
|
+
const ixs = targets.flatMap((t, i) => (infos[i]?.lamports ?? 0) >= t.need ? [] : [SystemProgram4.transfer({
|
|
11721
|
+
fromPubkey: funder.publicKey,
|
|
11722
|
+
toPubkey: t.a,
|
|
11723
|
+
lamports: t.need - (infos[i]?.lamports ?? 0)
|
|
11724
|
+
})]);
|
|
11725
|
+
if (ixs.length) {
|
|
11726
|
+
const sig = await sendAndConfirmTransaction2(conn, new Transaction4().add(...ixs), [funder]);
|
|
11727
|
+
log(` seam: funded ${ixs.length} program accounts read off the built tx \u2014 ${sig.slice(0, 16)}\u2026`);
|
|
11728
|
+
}
|
|
11729
|
+
tx.sign([R]);
|
|
11730
|
+
return tx;
|
|
11731
|
+
}
|
|
11732
|
+
tx.partialSign(R);
|
|
11733
|
+
return tx;
|
|
11734
|
+
};
|
|
11735
|
+
const signMessage = async (m) => nacl9.sign.detached(m, R.secretKey);
|
|
11736
|
+
const utxoKeypair = await deriveUtxoKeypairFromSpendKey(opts.noteSpendKey);
|
|
11737
|
+
const nk = getNkFromUtxoPrivateKey(utxoKeypair.privateKey);
|
|
11738
|
+
const { utxo, noteSalt } = await createRecoverableDepositUtxo(amount, nk, mint);
|
|
11739
|
+
const zeroInput = await createZeroUtxo(mint);
|
|
11740
|
+
const rBefore = await conn.getBalance(R.publicKey);
|
|
11741
|
+
let result;
|
|
11742
|
+
try {
|
|
11743
|
+
result = await transact(
|
|
11744
|
+
{ inputUtxos: [zeroInput], outputUtxos: [utxo], externalAmount: amount, depositor: R.publicKey },
|
|
11745
|
+
{
|
|
11746
|
+
connection: conn,
|
|
11747
|
+
programId,
|
|
11748
|
+
relayUrl,
|
|
11749
|
+
// NO depositorKeypair: the SDK branches `if (keypair) … else if (signTransaction)`
|
|
11750
|
+
// (transact.ts:2786, :2936), so passing one silently skips the funding seam.
|
|
11751
|
+
depositorPublicKey: R.publicKey,
|
|
11752
|
+
walletPublicKey: R.publicKey,
|
|
11753
|
+
signTransaction: signTransaction2,
|
|
11754
|
+
signMessage,
|
|
11755
|
+
chainNoteViewingKeyNk: nk,
|
|
11756
|
+
chainNoteSalt: noteSalt,
|
|
11757
|
+
relaySupplementalAlt: true,
|
|
11758
|
+
onProgress: (s) => log(` \xB7 ${s}`)
|
|
11759
|
+
}
|
|
11760
|
+
);
|
|
11761
|
+
} catch (e) {
|
|
11762
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
11763
|
+
throw new DepositError(msg, fundedAccounts, measuredSize);
|
|
11764
|
+
}
|
|
11765
|
+
const rAfter = await conn.getBalance(R.publicKey);
|
|
11766
|
+
const noteIndex = result.outputUtxos[0].index;
|
|
11767
|
+
if (noteIndex === void 0) throw new Error("deposit landed but the note has no index \u2014 it cannot be discovered later");
|
|
11768
|
+
return {
|
|
11769
|
+
signature: result.signature,
|
|
11770
|
+
noteIndex,
|
|
11771
|
+
amount: result.outputUtxos[0].amount,
|
|
11772
|
+
txSize: measuredSize,
|
|
11773
|
+
rBefore,
|
|
11774
|
+
rAfter,
|
|
11775
|
+
rentExempt: rAfter >= MAINNET_RENT_0,
|
|
11776
|
+
inputNullifiers: result.inputNullifiers,
|
|
11777
|
+
fundedAccounts
|
|
11778
|
+
};
|
|
11779
|
+
}
|
|
11780
|
+
|
|
11781
|
+
// src/bridge/cleanup.ts
|
|
11782
|
+
import {
|
|
11783
|
+
PublicKey as PublicKey16,
|
|
11784
|
+
Transaction as Transaction5,
|
|
11785
|
+
sendAndConfirmTransaction as sendAndConfirmTransaction3
|
|
11786
|
+
} from "@solana/web3.js";
|
|
11787
|
+
import {
|
|
11788
|
+
getAssociatedTokenAddressSync as getAssociatedTokenAddressSync6,
|
|
11789
|
+
createCloseAccountInstruction,
|
|
11790
|
+
createTransferInstruction,
|
|
11791
|
+
createAssociatedTokenAccountIdempotentInstruction,
|
|
11792
|
+
getMinimumBalanceForRentExemptAccount
|
|
11793
|
+
} from "@solana/spl-token";
|
|
11794
|
+
var DEFAULT_MINT = new PublicKey16("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
|
|
11795
|
+
async function cleanupReceivingAddress(conn, R, dustDestination, mint = DEFAULT_MINT) {
|
|
11796
|
+
assertAllowedRpcConnection(conn);
|
|
11797
|
+
const rAta = getAssociatedTokenAddressSync6(mint, R.publicKey);
|
|
11798
|
+
const info = await conn.getAccountInfo(rAta);
|
|
11799
|
+
if (!info) {
|
|
11800
|
+
return {
|
|
11801
|
+
closed: false,
|
|
11802
|
+
dustSwept: 0n,
|
|
11803
|
+
rentReturned: 0,
|
|
11804
|
+
destination: null,
|
|
11805
|
+
signature: null,
|
|
11806
|
+
note: "no token account \u2014 nothing to recover"
|
|
11807
|
+
};
|
|
11808
|
+
}
|
|
11809
|
+
const bal = BigInt((await conn.getTokenAccountBalance(rAta)).value.amount);
|
|
11810
|
+
const rentHeld = info.lamports;
|
|
11811
|
+
const before = await conn.getBalance(R.publicKey);
|
|
11812
|
+
const ixs = [];
|
|
11813
|
+
let destination = null;
|
|
11814
|
+
if (bal > 0n && !dustDestination) {
|
|
11815
|
+
return {
|
|
11816
|
+
closed: false,
|
|
11817
|
+
dustSwept: 0n,
|
|
11818
|
+
rentReturned: 0,
|
|
11819
|
+
destination: null,
|
|
11820
|
+
signature: null,
|
|
11821
|
+
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`
|
|
11822
|
+
};
|
|
11823
|
+
}
|
|
11824
|
+
if (bal > 0n && dustDestination) {
|
|
11825
|
+
const destAta = getAssociatedTokenAddressSync6(mint, dustDestination);
|
|
11826
|
+
const destInfo = await conn.getAccountInfo(destAta);
|
|
11827
|
+
if (!destInfo) {
|
|
11828
|
+
const ataRent = await getMinimumBalanceForRentExemptAccount(conn);
|
|
11829
|
+
if (before < ataRent + CLEANUP_FEE_BUDGET) {
|
|
11830
|
+
throw new Error(
|
|
11831
|
+
`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.`
|
|
11832
|
+
);
|
|
11833
|
+
}
|
|
11834
|
+
ixs.push(createAssociatedTokenAccountIdempotentInstruction(R.publicKey, destAta, dustDestination, mint));
|
|
11835
|
+
}
|
|
11836
|
+
ixs.push(createTransferInstruction(rAta, destAta, R.publicKey, bal));
|
|
11837
|
+
destination = destAta.toBase58();
|
|
11838
|
+
}
|
|
11839
|
+
ixs.push(createCloseAccountInstruction(rAta, R.publicKey, R.publicKey));
|
|
11840
|
+
const signature = await sendAndConfirmTransaction3(conn, new Transaction5().add(...ixs), [R], {
|
|
11841
|
+
commitment: "confirmed"
|
|
11842
|
+
});
|
|
11843
|
+
const after = await conn.getBalance(R.publicKey);
|
|
11844
|
+
const gone = await conn.getAccountInfo(rAta) === null;
|
|
11845
|
+
return {
|
|
11846
|
+
closed: gone,
|
|
11847
|
+
dustSwept: bal,
|
|
11848
|
+
rentReturned: after - before,
|
|
11849
|
+
destination,
|
|
11850
|
+
signature,
|
|
11851
|
+
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`
|
|
11852
|
+
};
|
|
11853
|
+
}
|
|
11854
|
+
|
|
11855
|
+
// src/bridge/quote.ts
|
|
11856
|
+
var MIN_DEPOSIT_SPL_BASE_UNITS = 1000000n;
|
|
11857
|
+
var WITHDRAW_FIXED_FEE = 450000n;
|
|
11858
|
+
var WITHDRAW_FEE_BPS = 30n;
|
|
11859
|
+
function withdrawFeeAt(amount, fixedFee, feeBps) {
|
|
11860
|
+
return fixedFee + amount * feeBps / 10000n;
|
|
11861
|
+
}
|
|
11862
|
+
var MAX_FEE_FRACTION = 0.063;
|
|
11863
|
+
var FIXED_ROUND_TRIP = 903000n;
|
|
11864
|
+
var ECONOMIC_MINIMUM = FIXED_ROUND_TRIP * 1000000n / BigInt(Math.round((MAX_FEE_FRACTION - 3e-3) * 1e6));
|
|
11865
|
+
function withdrawFeeFor(amount) {
|
|
11866
|
+
return withdrawFeeAt(amount, WITHDRAW_FIXED_FEE, WITHDRAW_FEE_BPS);
|
|
11867
|
+
}
|
|
11868
|
+
function assessBridgeQuote(q) {
|
|
11869
|
+
if (q.sent < 0n) {
|
|
11870
|
+
throw new Error(`assessBridgeQuote: sent must not be negative (got ${q.sent}).`);
|
|
11871
|
+
}
|
|
11872
|
+
if (q.arrivesMin > q.sent) {
|
|
11873
|
+
throw new Error(
|
|
11874
|
+
`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.`
|
|
11875
|
+
);
|
|
11876
|
+
}
|
|
11877
|
+
const shieldedMinRaw = q.arrivesMin - q.paymasterFee;
|
|
11878
|
+
const shieldedTargetRaw = q.arrivesTarget - q.paymasterFee;
|
|
11879
|
+
const shieldedMin = shieldedMinRaw > 0n ? shieldedMinRaw : 0n;
|
|
11880
|
+
const shieldedTarget = shieldedTargetRaw > 0n ? shieldedTargetRaw : 0n;
|
|
11881
|
+
const viable = shieldedMinRaw >= MIN_DEPOSIT_SPL_BASE_UNITS;
|
|
11882
|
+
const economic = q.sent >= ECONOMIC_MINIMUM;
|
|
11883
|
+
const withdrawFee = shieldedMin > 0n ? withdrawFeeAt(shieldedMin, q.liveWithdrawFixedFee ?? WITHDRAW_FIXED_FEE, q.liveWithdrawFeeBps ?? WITHDRAW_FEE_BPS) : 0n;
|
|
11884
|
+
const roundTripCost = q.sent - (shieldedMin > withdrawFee ? shieldedMin - withdrawFee : 0n);
|
|
11885
|
+
const roundTripFraction = q.sent > 0n ? Number(roundTripCost) / Number(q.sent) : 0;
|
|
11886
|
+
const reasons = [];
|
|
11887
|
+
if (!viable) {
|
|
11888
|
+
const short = MIN_DEPOSIT_SPL_BASE_UNITS - shieldedMinRaw;
|
|
11889
|
+
reasons.push(
|
|
11890
|
+
(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.`
|
|
11891
|
+
);
|
|
11892
|
+
}
|
|
11893
|
+
if (viable && !economic) {
|
|
11894
|
+
reasons.push(
|
|
11895
|
+
`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.`
|
|
11896
|
+
);
|
|
11897
|
+
}
|
|
11898
|
+
return {
|
|
11899
|
+
...q,
|
|
11900
|
+
shieldedMin,
|
|
11901
|
+
shieldedTarget,
|
|
11902
|
+
viable,
|
|
11903
|
+
economic,
|
|
11904
|
+
withdrawFee,
|
|
11905
|
+
roundTripCost,
|
|
11906
|
+
roundTripFraction,
|
|
11907
|
+
reasons
|
|
11908
|
+
};
|
|
11909
|
+
}
|
|
11910
|
+
var fmt = (v) => `${(Number(v) / 1e6).toFixed(6)} USDC`;
|
|
11911
|
+
function renderAssessment(a) {
|
|
11912
|
+
const L = [];
|
|
11913
|
+
L.push(` you send ${fmt(a.sent)}`);
|
|
11914
|
+
L.push(` arrives at least ${fmt(a.arrivesMin)} (target ${fmt(a.arrivesTarget)}, not a promise)`);
|
|
11915
|
+
L.push(` paymaster fee ${fmt(a.paymasterFee)} buys the receiver its SOL, so your wallet stays off chain`);
|
|
11916
|
+
L.push(` SHIELDED ${fmt(a.shieldedMin)} at least \u2014 this is what you end up with`);
|
|
11917
|
+
L.push(` to withdraw later ${fmt(a.withdrawFee)} the program's fee, whenever you take it out`);
|
|
11918
|
+
for (const r of a.reasons) L.push(`
|
|
11919
|
+
${a.viable ? "NOTE" : "REFUSED"}: ${r}`);
|
|
11920
|
+
return L.join("\n");
|
|
11921
|
+
}
|
|
11922
|
+
|
|
11923
|
+
// src/bridge/retry.ts
|
|
11924
|
+
var TERMINAL_FAILURE = /DepositTooSmall|minimum is 1\.000000|insufficient|holds only|below the program|kora wants|over the .* ceiling/i;
|
|
11925
|
+
function isTerminalFailure(message) {
|
|
11926
|
+
return TERMINAL_FAILURE.test(message);
|
|
11927
|
+
}
|
|
11928
|
+
function classifyPostFailure(attempted, after) {
|
|
11929
|
+
if (after === null) return "unknown";
|
|
11930
|
+
if (after === 0n) return "landed";
|
|
11931
|
+
if (after >= attempted) return "did-not-land";
|
|
11932
|
+
return "unknown";
|
|
11933
|
+
}
|
|
11934
|
+
function mayRetry(verdict, message) {
|
|
11935
|
+
return verdict === "did-not-land" && !isTerminalFailure(message);
|
|
11936
|
+
}
|
|
11937
|
+
function retryDelayMs(attempt) {
|
|
11938
|
+
return 15e3 * Math.max(0, attempt - 1);
|
|
11939
|
+
}
|
|
11940
|
+
var DoNotRetry = class extends Error {
|
|
11941
|
+
constructor(message) {
|
|
11942
|
+
super(message);
|
|
11943
|
+
this.name = "DoNotRetry";
|
|
11944
|
+
}
|
|
11945
|
+
};
|
|
11946
|
+
async function withShieldRetries(attempt, hooks = {}) {
|
|
11947
|
+
const attempts = hooks.attempts ?? 3;
|
|
11948
|
+
const sleep4 = hooks.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
11949
|
+
let last = "";
|
|
11950
|
+
for (let n = 1; n <= attempts; n++) {
|
|
11951
|
+
if (n > 1) {
|
|
11952
|
+
const delay = retryDelayMs(n);
|
|
11953
|
+
hooks.onRetry?.(n, delay, last);
|
|
11954
|
+
await sleep4(delay);
|
|
11955
|
+
}
|
|
11956
|
+
try {
|
|
11957
|
+
return await attempt(n);
|
|
11958
|
+
} catch (e) {
|
|
11959
|
+
if (e instanceof DoNotRetry) throw e;
|
|
11960
|
+
last = e instanceof Error ? e.message : String(e);
|
|
11961
|
+
if (isTerminalFailure(last) || n === attempts) throw e;
|
|
11962
|
+
}
|
|
11963
|
+
}
|
|
11964
|
+
throw new Error("unreachable");
|
|
11965
|
+
}
|
|
11966
|
+
|
|
10860
11967
|
// src/index.ts
|
|
10861
|
-
var VERSION = "0.2.
|
|
11968
|
+
var VERSION = "0.2.3";
|
|
10862
11969
|
var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
10863
11970
|
export {
|
|
11971
|
+
BRIDGE_ESCROW_LABEL,
|
|
10864
11972
|
BUILD_ALLOWS_LOCAL_ENDPOINTS,
|
|
10865
11973
|
CHAIN_NOTE_SALT_BITS,
|
|
11974
|
+
CLEANUP_FEE_BUDGET,
|
|
10866
11975
|
CLOAK_PRODUCTION_RELAY_URL,
|
|
10867
11976
|
CLOAK_PROGRAM_ID,
|
|
10868
11977
|
CloakError,
|
|
@@ -10870,25 +11979,38 @@ export {
|
|
|
10870
11979
|
DEFAULT_TRANSACTION_CIRCUITS_URL,
|
|
10871
11980
|
DELIVERY_MEMO_TAG,
|
|
10872
11981
|
DELIVERY_REGISTRY_SEED,
|
|
11982
|
+
DepositError,
|
|
11983
|
+
DoNotRetry,
|
|
11984
|
+
ECONOMIC_MINIMUM,
|
|
10873
11985
|
EXPECTED_CIRCUIT_HASHES,
|
|
11986
|
+
FEE_BUDGET,
|
|
10874
11987
|
FIXED_FEE_LAMPORTS,
|
|
10875
11988
|
InsecureRandomnessError,
|
|
10876
11989
|
LAMPORTS_PER_SOL,
|
|
10877
11990
|
LocalStorageAdapter,
|
|
11991
|
+
MAINNET_RENT_0,
|
|
11992
|
+
MAINNET_RENT_1,
|
|
11993
|
+
MAX_RECEIVER_INDEX,
|
|
10878
11994
|
MERKLE_TREE_HEIGHT2 as MERKLE_TREE_HEIGHT,
|
|
10879
11995
|
MIN_DEPOSIT_LAMPORTS,
|
|
11996
|
+
MIN_DEPOSIT_SPL_BASE_UNITS,
|
|
10880
11997
|
MemoryStorageAdapter,
|
|
10881
11998
|
MerkleTree,
|
|
10882
11999
|
NATIVE_SOL_MINT,
|
|
12000
|
+
ONECLICK_PUBKEY_B58,
|
|
10883
12001
|
RECIPIENT_DELIVERY_CIPHERTEXT_LEN,
|
|
10884
12002
|
RECIPIENT_DELIVERY_EPHEMERAL_PK_LEN,
|
|
10885
12003
|
RECIPIENT_DELIVERY_NONCE_LEN,
|
|
10886
12004
|
RECIPIENT_DELIVERY_NOTE_BYTES,
|
|
10887
12005
|
RECIPIENT_DELIVERY_PLAINTEXT_LEN,
|
|
10888
12006
|
RECIPIENT_DELIVERY_TAG_LEN,
|
|
12007
|
+
RELAY_BATCH_AUTH_MAX_ITEMS,
|
|
10889
12008
|
RELAY_ORIGIN_ALLOWLIST,
|
|
12009
|
+
REQUEST_AUTH_BATCH_DOMAIN,
|
|
12010
|
+
REQUEST_AUTH_BATCH_MAX_AGE_SECONDS,
|
|
10890
12011
|
REQUEST_AUTH_MAX_AGE_SECONDS,
|
|
10891
12012
|
REQUEST_AUTH_MAX_FUTURE_SKEW_SECONDS,
|
|
12013
|
+
RelayBatchAuthCoordinator,
|
|
10892
12014
|
RelayInternalError,
|
|
10893
12015
|
RelayService,
|
|
10894
12016
|
RootNotFoundError,
|
|
@@ -10898,6 +12020,7 @@ export {
|
|
|
10898
12020
|
SettlementVerificationError,
|
|
10899
12021
|
ShieldPoolErrors,
|
|
10900
12022
|
SimpleWallet,
|
|
12023
|
+
TERMINAL_FAILURE,
|
|
10901
12024
|
TRANSACTION_CIRCUITS_VERSION,
|
|
10902
12025
|
TRANSACT_AUTH_FIELDS,
|
|
10903
12026
|
TRANSACT_SWAP_AUTH_FIELDS,
|
|
@@ -10907,8 +12030,12 @@ export {
|
|
|
10907
12030
|
VARIABLE_FEE_NUMERATOR,
|
|
10908
12031
|
VARIABLE_FEE_RATE,
|
|
10909
12032
|
VERSION,
|
|
12033
|
+
WITHDRAW_FEE_BPS,
|
|
12034
|
+
WITHDRAW_FIXED_FEE,
|
|
10910
12035
|
assertDirectSubmissionLanded,
|
|
12036
|
+
assertInputMints,
|
|
10911
12037
|
assertTransactionCircuitIntegrity,
|
|
12038
|
+
assessBridgeQuote,
|
|
10912
12039
|
bigintToBytes32,
|
|
10913
12040
|
bigintToHex,
|
|
10914
12041
|
buildMerkleTree,
|
|
@@ -10916,6 +12043,7 @@ export {
|
|
|
10916
12043
|
buildMerkleTreeFromRelay,
|
|
10917
12044
|
buildRecipientDeliveryNotes,
|
|
10918
12045
|
buildRelayAuthPreimage,
|
|
12046
|
+
buildRelayBatchAuthMessage,
|
|
10919
12047
|
buildTransactRequestBody,
|
|
10920
12048
|
bytesToHex,
|
|
10921
12049
|
calculateFee,
|
|
@@ -10923,12 +12051,16 @@ export {
|
|
|
10923
12051
|
calculateRelayFee,
|
|
10924
12052
|
canRebuildMerkleTreeFromChain,
|
|
10925
12053
|
canonicalJson,
|
|
12054
|
+
canonicalPayloadString,
|
|
10926
12055
|
chainNoteFromBase64,
|
|
10927
12056
|
chainNoteToBase64,
|
|
12057
|
+
classifyPostFailure,
|
|
10928
12058
|
classifyRelayError,
|
|
12059
|
+
cleanupReceivingAddress,
|
|
10929
12060
|
cleanupStalePendingOperations,
|
|
10930
12061
|
clearPendingDeposits,
|
|
10931
12062
|
clearPendingWithdrawals,
|
|
12063
|
+
cloakBridgeRail,
|
|
10932
12064
|
computeChainNoteHash,
|
|
10933
12065
|
computeExtDataHash,
|
|
10934
12066
|
computeMerkleRoot,
|
|
@@ -10945,15 +12077,19 @@ export {
|
|
|
10945
12077
|
createLogger,
|
|
10946
12078
|
createRecoverableChangeUtxo,
|
|
10947
12079
|
createRecoverableDepositUtxo,
|
|
12080
|
+
createRelayBatchAuthCoordinator,
|
|
10948
12081
|
createUtxo,
|
|
10949
12082
|
createZeroUtxo,
|
|
10950
12083
|
decryptCompactChainNote,
|
|
10951
12084
|
decryptComplianceMetadataWithMasterKey,
|
|
10952
12085
|
decryptTransactionMetadata,
|
|
12086
|
+
depositFromDerivedKey,
|
|
12087
|
+
deriveBridgeReceiver,
|
|
10953
12088
|
deriveChangeNoteBlinding,
|
|
10954
12089
|
deriveDepositNoteSecrets,
|
|
10955
12090
|
deriveDiversifiedViewingKey,
|
|
10956
12091
|
deriveDiversifier,
|
|
12092
|
+
deriveFundingTargets,
|
|
10957
12093
|
deriveInputNullifierPdas,
|
|
10958
12094
|
derivePublicKey,
|
|
10959
12095
|
deriveSpendKey,
|
|
@@ -10992,6 +12128,7 @@ export {
|
|
|
10992
12128
|
formatErrorForLogging,
|
|
10993
12129
|
formatSol,
|
|
10994
12130
|
fullWithdraw,
|
|
12131
|
+
fundReceiverViaPaymaster,
|
|
10995
12132
|
generateCloakKeys,
|
|
10996
12133
|
generateCommitmentAsync,
|
|
10997
12134
|
generateMasterSeed,
|
|
@@ -11027,18 +12164,21 @@ export {
|
|
|
11027
12164
|
isReactNative,
|
|
11028
12165
|
isRootNotFoundError,
|
|
11029
12166
|
isSubmissionOutcomeUnknownResponse,
|
|
12167
|
+
isTerminalFailure,
|
|
11030
12168
|
isValidHex,
|
|
11031
12169
|
isValidRpcUrl,
|
|
11032
12170
|
isValidSolanaAddress,
|
|
11033
12171
|
isWithdrawAmountSufficient,
|
|
11034
12172
|
isWithdrawable,
|
|
11035
12173
|
keypairToAdapter,
|
|
12174
|
+
listBridgeDeposits,
|
|
11036
12175
|
loadPendingDeposits,
|
|
11037
12176
|
loadPendingWithdrawals,
|
|
11038
12177
|
loadVerifiedCircuitArtifacts,
|
|
11039
12178
|
matchChangeNote,
|
|
11040
12179
|
matchDepositNote,
|
|
11041
12180
|
matchSwapRefundLeaf,
|
|
12181
|
+
mayRetry,
|
|
11042
12182
|
openRecipientDeliveryNote,
|
|
11043
12183
|
parseAmount,
|
|
11044
12184
|
parseDeliveryCarrierMemo,
|
|
@@ -11061,11 +12201,15 @@ export {
|
|
|
11061
12201
|
randomDepositNoteSalt,
|
|
11062
12202
|
randomFieldElement,
|
|
11063
12203
|
readMerkleTreeState,
|
|
12204
|
+
readRent,
|
|
11064
12205
|
recipientDeliveryNoteToBase64,
|
|
11065
12206
|
registerViewingKey,
|
|
12207
|
+
relayRequestDigestHex,
|
|
11066
12208
|
removePendingDeposit,
|
|
11067
12209
|
removePendingWithdrawal,
|
|
12210
|
+
renderAssessment,
|
|
11068
12211
|
resolveCircuitsBase,
|
|
12212
|
+
retryDelayMs,
|
|
11069
12213
|
savePendingDeposit,
|
|
11070
12214
|
savePendingWithdrawal,
|
|
11071
12215
|
scanNotesForWallet,
|
|
@@ -11086,6 +12230,7 @@ export {
|
|
|
11086
12230
|
swapWithChange,
|
|
11087
12231
|
toComplianceReport,
|
|
11088
12232
|
transact,
|
|
12233
|
+
transactBatch,
|
|
11089
12234
|
transfer,
|
|
11090
12235
|
truncate,
|
|
11091
12236
|
tryDecryptNote,
|
|
@@ -11098,13 +12243,17 @@ export {
|
|
|
11098
12243
|
validateDepositParams,
|
|
11099
12244
|
validateNote,
|
|
11100
12245
|
validateOutputsSum,
|
|
12246
|
+
validatePaymasterTopUpTransaction,
|
|
11101
12247
|
validateRoot,
|
|
11102
12248
|
validateTransfers,
|
|
11103
12249
|
validateWalletConnected,
|
|
11104
12250
|
validateWithdrawableNote,
|
|
11105
12251
|
verifyAllCircuits,
|
|
11106
12252
|
verifyCircuitIntegrity,
|
|
12253
|
+
verifyQuoteSignature,
|
|
11107
12254
|
verifyUtxos,
|
|
11108
12255
|
waitForRoot,
|
|
11109
|
-
|
|
12256
|
+
withShieldRetries,
|
|
12257
|
+
withTiming,
|
|
12258
|
+
withdrawFeeFor
|
|
11110
12259
|
};
|