@cloak.dev/sdk 0.2.1 → 0.2.2-staging.0f03668
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 +378 -31
- package/dist/index.d.cts +160 -1
- package/dist/index.d.ts +160 -1
- package/dist/index.js +372 -31
- 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}$/;
|
|
@@ -4533,6 +4533,91 @@ async function matchDepositNote(params) {
|
|
|
4533
4533
|
return { ...secrets, amount, mintAddress, commitment: candidate, noteSalt };
|
|
4534
4534
|
}
|
|
4535
4535
|
|
|
4536
|
+
// src/notes/change-note.ts
|
|
4537
|
+
import { blake3 as blake35 } from "@noble/hashes/blake3";
|
|
4538
|
+
var CHANGE_NOTE_DOMAIN = new TextEncoder().encode("cloak_change_note_v1");
|
|
4539
|
+
var CHANGE_BLINDING_INFO = new TextEncoder().encode("blinding");
|
|
4540
|
+
var MAX_OUTPUT_INDEX = 1;
|
|
4541
|
+
var MAX_CHAIN_NOTE_SALT2 = (1n << BigInt(CHAIN_NOTE_SALT_BITS)) - 1n;
|
|
4542
|
+
function saltToBytes2(noteSalt) {
|
|
4543
|
+
if (typeof noteSalt !== "bigint" || noteSalt <= 0n || noteSalt > MAX_CHAIN_NOTE_SALT2) {
|
|
4544
|
+
throw new Error(`noteSalt must be a positive ${CHAIN_NOTE_SALT_BITS}-bit value`);
|
|
4545
|
+
}
|
|
4546
|
+
const out = new Uint8Array(32);
|
|
4547
|
+
let v = noteSalt;
|
|
4548
|
+
for (let i = 31; i >= 0; i--) {
|
|
4549
|
+
out[i] = Number(v & 0xffn);
|
|
4550
|
+
v >>= 8n;
|
|
4551
|
+
}
|
|
4552
|
+
return out;
|
|
4553
|
+
}
|
|
4554
|
+
function toFieldSecret3(bytes) {
|
|
4555
|
+
let value = 0n;
|
|
4556
|
+
for (let i = 0; i < 32; i++) value = value << 8n | BigInt(bytes[i]);
|
|
4557
|
+
const reduced = value % (BN254_MODULUS >> 4n);
|
|
4558
|
+
return reduced === 0n ? 1n : reduced;
|
|
4559
|
+
}
|
|
4560
|
+
function randomChangeNoteSalt() {
|
|
4561
|
+
const bytes = randomBytes(12);
|
|
4562
|
+
let v = 0n;
|
|
4563
|
+
for (const b of bytes) v = v << 8n | BigInt(b);
|
|
4564
|
+
return v === 0n ? 1n : v;
|
|
4565
|
+
}
|
|
4566
|
+
function deriveChangeNoteBlinding(viewingKeyNk, noteSalt, outputIndex) {
|
|
4567
|
+
if (!viewingKeyNk || !(viewingKeyNk instanceof Uint8Array) || viewingKeyNk.length !== 32) {
|
|
4568
|
+
throw new Error("viewingKeyNk must be 32 bytes");
|
|
4569
|
+
}
|
|
4570
|
+
if (!Number.isInteger(outputIndex) || outputIndex < 0 || outputIndex > MAX_OUTPUT_INDEX) {
|
|
4571
|
+
throw new Error(`outputIndex must be 0..${MAX_OUTPUT_INDEX}`);
|
|
4572
|
+
}
|
|
4573
|
+
const salt = saltToBytes2(noteSalt);
|
|
4574
|
+
const preimage = new Uint8Array(
|
|
4575
|
+
CHANGE_NOTE_DOMAIN.length + viewingKeyNk.length + salt.length + 1 + CHANGE_BLINDING_INFO.length
|
|
4576
|
+
);
|
|
4577
|
+
let off = 0;
|
|
4578
|
+
preimage.set(CHANGE_NOTE_DOMAIN, off);
|
|
4579
|
+
off += CHANGE_NOTE_DOMAIN.length;
|
|
4580
|
+
preimage.set(viewingKeyNk, off);
|
|
4581
|
+
off += viewingKeyNk.length;
|
|
4582
|
+
preimage.set(salt, off);
|
|
4583
|
+
off += salt.length;
|
|
4584
|
+
preimage[off] = outputIndex;
|
|
4585
|
+
off += 1;
|
|
4586
|
+
preimage.set(CHANGE_BLINDING_INFO, off);
|
|
4587
|
+
return toFieldSecret3(blake35(preimage));
|
|
4588
|
+
}
|
|
4589
|
+
async function createRecoverableChangeUtxo(amount, keypair, viewingKeyNk, mintAddress = NATIVE_SOL_MINT, noteSalt = randomChangeNoteSalt(), outputIndex = 0) {
|
|
4590
|
+
if (typeof amount !== "bigint" || amount <= 0n) {
|
|
4591
|
+
throw new Error("amount must be a positive bigint");
|
|
4592
|
+
}
|
|
4593
|
+
const blinding = deriveChangeNoteBlinding(viewingKeyNk, noteSalt, outputIndex);
|
|
4594
|
+
const utxo = { amount, keypair, blinding, mintAddress };
|
|
4595
|
+
utxo.commitment = await computeCommitment(utxo);
|
|
4596
|
+
return { utxo, noteSalt };
|
|
4597
|
+
}
|
|
4598
|
+
function normalizeCommitment2(value) {
|
|
4599
|
+
if (typeof value === "bigint") return value;
|
|
4600
|
+
if (typeof value !== "string") return null;
|
|
4601
|
+
const clean = value.startsWith("0x") ? value.slice(2) : value;
|
|
4602
|
+
if (!/^[0-9a-fA-F]{1,64}$/.test(clean)) return null;
|
|
4603
|
+
return BigInt("0x" + clean);
|
|
4604
|
+
}
|
|
4605
|
+
async function matchChangeNote(params) {
|
|
4606
|
+
const { viewingKeyNk, noteSalt, amount, keypair, mintAddress, outputIndex, outputCommitments } = params;
|
|
4607
|
+
if (typeof amount !== "bigint" || amount <= 0n) return null;
|
|
4608
|
+
if (!keypair || typeof keypair.publicKey !== "bigint" || keypair.publicKey === 0n) return null;
|
|
4609
|
+
let blinding;
|
|
4610
|
+
try {
|
|
4611
|
+
blinding = deriveChangeNoteBlinding(viewingKeyNk, noteSalt, outputIndex);
|
|
4612
|
+
} catch {
|
|
4613
|
+
return null;
|
|
4614
|
+
}
|
|
4615
|
+
const candidate = await computeCommitment({ amount, keypair, blinding, mintAddress });
|
|
4616
|
+
const published = (outputCommitments ?? []).map(normalizeCommitment2).filter((value) => value !== null);
|
|
4617
|
+
if (!published.some((value) => value === candidate)) return null;
|
|
4618
|
+
return { keypair, blinding, amount, mintAddress, commitment: candidate, noteSalt };
|
|
4619
|
+
}
|
|
4620
|
+
|
|
4536
4621
|
// src/flows/transact.ts
|
|
4537
4622
|
import { buildPoseidon as buildPoseidon2 } from "circomlibjs";
|
|
4538
4623
|
import nacl6 from "tweetnacl";
|
|
@@ -5114,6 +5199,84 @@ async function fetchAltAddressesFromRelayHealth(relayUrl) {
|
|
|
5114
5199
|
return [];
|
|
5115
5200
|
}
|
|
5116
5201
|
}
|
|
5202
|
+
async function fetchSupplementalAltFromRelay(relayUrl, params) {
|
|
5203
|
+
const body = JSON.stringify({
|
|
5204
|
+
mint: params.mint.toBase58(),
|
|
5205
|
+
nullifiers: [
|
|
5206
|
+
Buffer.from(params.nullifiers[0]).toString("hex"),
|
|
5207
|
+
Buffer.from(params.nullifiers[1]).toString("hex")
|
|
5208
|
+
],
|
|
5209
|
+
bind0: Buffer.from(params.bind0).toString("hex"),
|
|
5210
|
+
depositor: params.depositor.toBase58()
|
|
5211
|
+
});
|
|
5212
|
+
let response;
|
|
5213
|
+
try {
|
|
5214
|
+
response = await relayFetch(`${relayUrl.replace(/\/$/, "")}/supplemental-alt`, {
|
|
5215
|
+
method: "POST",
|
|
5216
|
+
headers: { "Content-Type": "application/json" },
|
|
5217
|
+
body
|
|
5218
|
+
});
|
|
5219
|
+
} catch (err) {
|
|
5220
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5221
|
+
throw new Error(`Supplemental ALT request to the relay failed: ${msg}`);
|
|
5222
|
+
}
|
|
5223
|
+
if (!response.ok) {
|
|
5224
|
+
let text = "";
|
|
5225
|
+
try {
|
|
5226
|
+
text = await response.text();
|
|
5227
|
+
} catch {
|
|
5228
|
+
}
|
|
5229
|
+
throw new Error(
|
|
5230
|
+
`Supplemental ALT request failed (${response.status})${text ? `: ${text}` : ""}`
|
|
5231
|
+
);
|
|
5232
|
+
}
|
|
5233
|
+
let json;
|
|
5234
|
+
try {
|
|
5235
|
+
json = await response.json();
|
|
5236
|
+
} catch {
|
|
5237
|
+
throw new Error("Supplemental ALT response was not valid JSON");
|
|
5238
|
+
}
|
|
5239
|
+
const table = json?.table;
|
|
5240
|
+
if (typeof table !== "string" || table.length === 0) {
|
|
5241
|
+
throw new Error("Supplemental ALT response is missing a 'table' address");
|
|
5242
|
+
}
|
|
5243
|
+
try {
|
|
5244
|
+
return new PublicKey8(table);
|
|
5245
|
+
} catch {
|
|
5246
|
+
throw new Error(`Supplemental ALT response 'table' is not a valid public key: ${table}`);
|
|
5247
|
+
}
|
|
5248
|
+
}
|
|
5249
|
+
async function resolveRelaySupplementalAlt(connection, table, expected, pollOpts) {
|
|
5250
|
+
const pollIntervalMs = pollOpts?.pollIntervalMs ?? 150;
|
|
5251
|
+
const timeoutMs = pollOpts?.timeoutMs ?? 5e3;
|
|
5252
|
+
const expectedB58 = expected.map((pk) => pk.toBase58());
|
|
5253
|
+
const deadline = Date.now() + timeoutMs;
|
|
5254
|
+
for (; ; ) {
|
|
5255
|
+
const result = await connection.getAddressLookupTable(table, { commitment: "confirmed" });
|
|
5256
|
+
if (!result.value) {
|
|
5257
|
+
throw new Error(`Supplemental ALT ${table.toBase58()} could not be fetched from the RPC.`);
|
|
5258
|
+
}
|
|
5259
|
+
const account = result.value;
|
|
5260
|
+
const present = new Set(account.state.addresses.map((a) => a.toBase58()));
|
|
5261
|
+
const missing = expectedB58.filter((addr) => !present.has(addr));
|
|
5262
|
+
if (missing.length === 0) {
|
|
5263
|
+
const currentSlot = await connection.getSlot("confirmed");
|
|
5264
|
+
if (account.state.lastExtendedSlot < currentSlot) {
|
|
5265
|
+
return account;
|
|
5266
|
+
}
|
|
5267
|
+
if (Date.now() >= deadline) {
|
|
5268
|
+
throw new Error(
|
|
5269
|
+
`Supplemental ALT ${table.toBase58()} did not become resolvable within ${timeoutMs}ms (lastExtendedSlot=${account.state.lastExtendedSlot} never fell behind current slot=${currentSlot}).`
|
|
5270
|
+
);
|
|
5271
|
+
}
|
|
5272
|
+
} else if (Date.now() >= deadline) {
|
|
5273
|
+
throw new Error(
|
|
5274
|
+
`Supplemental ALT ${table.toBase58()} is still missing expected address(es) after ${timeoutMs}ms: ` + missing.join(", ")
|
|
5275
|
+
);
|
|
5276
|
+
}
|
|
5277
|
+
await sleep2(pollIntervalMs);
|
|
5278
|
+
}
|
|
5279
|
+
}
|
|
5117
5280
|
function deriveRelayUrlFromRiskQuoteUrl(riskQuoteUrl) {
|
|
5118
5281
|
if (!riskQuoteUrl) return void 0;
|
|
5119
5282
|
const trimmed = riskQuoteUrl.replace(/\/$/, "");
|
|
@@ -5784,7 +5947,7 @@ async function planDirectV0Submission(args) {
|
|
|
5784
5947
|
);
|
|
5785
5948
|
return { tier: "minimal", instructions: minimal, supplementalAltRequested, fullSize, minimalSize };
|
|
5786
5949
|
}
|
|
5787
|
-
async function submitViaExternalFeePayer(connection, depositor, externalFeePayer, externalFeePayerPubkey, instructions, addressLookupTableAccounts, onProgress, allowSupplementalAlt = true) {
|
|
5950
|
+
async function submitViaExternalFeePayer(connection, depositor, externalFeePayer, externalFeePayerPubkey, instructions, addressLookupTableAccounts, onProgress, allowSupplementalAlt = true, relayAltAttempt) {
|
|
5788
5951
|
if (!depositor.signTransaction) {
|
|
5789
5952
|
throw new Error(
|
|
5790
5953
|
"externalFeePayer requires wallet-style signTransaction \u2014 keypair-only depositors aren't supported for external fee payment."
|
|
@@ -5813,6 +5976,18 @@ async function submitViaExternalFeePayer(connection, depositor, externalFeePayer
|
|
|
5813
5976
|
};
|
|
5814
5977
|
const compressWithSupplementalAlt = async (instrs, extraAddresses = []) => {
|
|
5815
5978
|
onProgress?.("Transaction exceeds packet limit; creating supplemental ALT...");
|
|
5979
|
+
if (relayAltAttempt) {
|
|
5980
|
+
try {
|
|
5981
|
+
onProgress?.("Requesting supplemental lookup table from relay...");
|
|
5982
|
+
addressLookupTableAccounts = [await relayAltAttempt()];
|
|
5983
|
+
return;
|
|
5984
|
+
} catch (err) {
|
|
5985
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5986
|
+
onProgress?.(
|
|
5987
|
+
`Relay supplemental ALT failed (${msg}); falling back to a depositor-signed table.`
|
|
5988
|
+
);
|
|
5989
|
+
}
|
|
5990
|
+
}
|
|
5816
5991
|
try {
|
|
5817
5992
|
const supplementalAddresses = dedupePubkeys([
|
|
5818
5993
|
...collectLookupCandidatesFromInstructions(instrs, externalFeePayerPubkey, void 0),
|
|
@@ -5880,7 +6055,7 @@ async function submitViaExternalFeePayer(connection, depositor, externalFeePayer
|
|
|
5880
6055
|
}
|
|
5881
6056
|
return signature;
|
|
5882
6057
|
}
|
|
5883
|
-
async function submitTransactionDirect(connection, programId, depositor, proofBytes, publicInputsBytes, nullifiers, mint, recipient, riskOracleQueue, riskQuoteUrl, getRiskQuoteInstruction, onProgress, addressLookupTableAccounts, rangeApiKey, encryptedNoteBytes, relayUrl, altAddresses, relayer, relayerFee, onTransactProofBuilt, externalFeePayer) {
|
|
6058
|
+
async function submitTransactionDirect(connection, programId, depositor, proofBytes, publicInputsBytes, nullifiers, mint, recipient, riskOracleQueue, riskQuoteUrl, getRiskQuoteInstruction, onProgress, addressLookupTableAccounts, rangeApiKey, encryptedNoteBytes, relayUrl, altAddresses, relayer, relayerFee, onTransactProofBuilt, externalFeePayer, relaySupplementalAlt) {
|
|
5884
6059
|
onProgress?.("Building transaction...");
|
|
5885
6060
|
addressLookupTableAccounts = await resolveAddressLookupTableAccounts(
|
|
5886
6061
|
connection,
|
|
@@ -5901,14 +6076,8 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
|
|
|
5901
6076
|
"rangeApiKey direct risk quotes do not support H-01 deposit nonce binding. Use relayUrl/riskQuoteUrl or getRiskQuoteInstruction."
|
|
5902
6077
|
);
|
|
5903
6078
|
}
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
addressLookupTableAccounts = await createEphemeralALT(connection, depositor, onProgress, [], externalFeePayer);
|
|
5907
|
-
}
|
|
5908
|
-
if (isDeposit && isSplPool && (!addressLookupTableAccounts || addressLookupTableAccounts.length === 0)) {
|
|
5909
|
-
onProgress?.("Creating address lookup table (SPL deposit requires v0 tx)...");
|
|
5910
|
-
addressLookupTableAccounts = await createEphemeralALT(connection, depositor, onProgress, [], externalFeePayer);
|
|
5911
|
-
}
|
|
6079
|
+
const needsAltForChainNotes = isDeposit && Boolean(encryptedNoteBytes && encryptedNoteBytes.length > 0);
|
|
6080
|
+
const needsAltForSplDeposit = isDeposit && isSplPool;
|
|
5912
6081
|
const publicAmountBytes = publicInputsBytes.slice(32, 40);
|
|
5913
6082
|
const publicAmountBuffer = Buffer.from(publicAmountBytes);
|
|
5914
6083
|
const publicAmount = readBigInt64LE(publicAmountBuffer, 0);
|
|
@@ -5960,6 +6129,49 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
|
|
|
5960
6129
|
const nullifier1Hex = Buffer.from(nullifiers[1]).toString("hex");
|
|
5961
6130
|
let riskNoncePda;
|
|
5962
6131
|
let depositNonce = null;
|
|
6132
|
+
let relaySupplementalAltPromise;
|
|
6133
|
+
const requestRelaySupplementalAlt = () => {
|
|
6134
|
+
const [nullifierPda0] = deriveNullifierPDA(programId, pdas.pool, nullifiers[0]);
|
|
6135
|
+
const [nullifierPda1] = deriveNullifierPDA(programId, pdas.pool, nullifiers[1]);
|
|
6136
|
+
const [riskNoncePdaForAlt] = deriveRiskNoncePDA(programId, depositNonce);
|
|
6137
|
+
const depositorAta = getAssociatedTokenAddressSync(mint, depositor.publicKey, false, TOKEN_PROGRAM_ID);
|
|
6138
|
+
return fetchSupplementalAltFromRelay(relayUrl, {
|
|
6139
|
+
mint,
|
|
6140
|
+
nullifiers,
|
|
6141
|
+
bind0: depositNonce,
|
|
6142
|
+
depositor: depositor.publicKey
|
|
6143
|
+
}).then(
|
|
6144
|
+
(table) => resolveRelaySupplementalAlt(connection, table, [
|
|
6145
|
+
nullifierPda0,
|
|
6146
|
+
nullifierPda1,
|
|
6147
|
+
riskNoncePdaForAlt,
|
|
6148
|
+
depositorAta
|
|
6149
|
+
])
|
|
6150
|
+
);
|
|
6151
|
+
};
|
|
6152
|
+
const relayAltEligible = () => Boolean(relaySupplementalAlt && relayUrl && isDeposit && isSplPool && depositNonce && nullifiers);
|
|
6153
|
+
const getRelaySupplementalAlt = () => {
|
|
6154
|
+
if (!relaySupplementalAltPromise) {
|
|
6155
|
+
relaySupplementalAltPromise = requestRelaySupplementalAlt();
|
|
6156
|
+
}
|
|
6157
|
+
return relaySupplementalAltPromise;
|
|
6158
|
+
};
|
|
6159
|
+
const acquireDepositAlt = async () => {
|
|
6160
|
+
if (relayAltEligible()) {
|
|
6161
|
+
try {
|
|
6162
|
+
onProgress?.("Requesting lookup table from relay...");
|
|
6163
|
+
const relayAlt = await getRelaySupplementalAlt();
|
|
6164
|
+
return [relayAlt];
|
|
6165
|
+
} catch (err) {
|
|
6166
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
6167
|
+
onProgress?.(
|
|
6168
|
+
`Relay lookup table failed (${msg}); falling back to a depositor-signed table.`
|
|
6169
|
+
);
|
|
6170
|
+
}
|
|
6171
|
+
}
|
|
6172
|
+
onProgress?.("Creating address lookup table for V0 transaction...");
|
|
6173
|
+
return createEphemeralALT(connection, depositor, onProgress, [], externalFeePayer);
|
|
6174
|
+
};
|
|
5963
6175
|
if ((isDeposit || isSend) && riskCheckEnabled && !riskQuoteIx) {
|
|
5964
6176
|
if (getRiskQuoteInstruction) {
|
|
5965
6177
|
onProgress?.("Fetching risk quote (custom)...");
|
|
@@ -5990,10 +6202,13 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
|
|
|
5990
6202
|
}
|
|
5991
6203
|
if (depositNonce) {
|
|
5992
6204
|
[riskNoncePda] = deriveRiskNoncePDA(programId, depositNonce);
|
|
6205
|
+
if (relaySupplementalAlt && relayUrl && isSplPool) {
|
|
6206
|
+
getRelaySupplementalAlt().catch(() => {
|
|
6207
|
+
});
|
|
6208
|
+
}
|
|
5993
6209
|
}
|
|
5994
|
-
if (
|
|
5995
|
-
|
|
5996
|
-
addressLookupTableAccounts = await createEphemeralALT(connection, depositor, onProgress, [], externalFeePayer);
|
|
6210
|
+
if ((!addressLookupTableAccounts || addressLookupTableAccounts.length === 0) && (riskQuoteIx || needsAltForChainNotes || needsAltForSplDeposit)) {
|
|
6211
|
+
addressLookupTableAccounts = await acquireDepositAlt();
|
|
5997
6212
|
}
|
|
5998
6213
|
}
|
|
5999
6214
|
if (!isDeposit && riskCheckEnabled && recipient && riskQuoteUrl && !riskQuoteIx) {
|
|
@@ -6059,7 +6274,14 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
|
|
|
6059
6274
|
externalFeePayerPubkey,
|
|
6060
6275
|
fullInstructions,
|
|
6061
6276
|
addressLookupTableAccounts,
|
|
6062
|
-
onProgress
|
|
6277
|
+
onProgress,
|
|
6278
|
+
void 0,
|
|
6279
|
+
// `addressLookupTableAccounts` above already carries the relay-owned table when eligible
|
|
6280
|
+
// (acquireDepositAlt ran before this branch, same as the non-externalFeePayer path). This is
|
|
6281
|
+
// only for submitViaExternalFeePayer's OWN, separate supplemental round — triggered by the
|
|
6282
|
+
// external payer's extra signature and payment-instruction accounts pushing an already-ALT'd
|
|
6283
|
+
// tx back over the limit — so that round also tries the relay before an ephemeral fallback.
|
|
6284
|
+
relayAltEligible() ? getRelaySupplementalAlt : void 0
|
|
6063
6285
|
);
|
|
6064
6286
|
} else if (useV0) {
|
|
6065
6287
|
const plan = await planDirectV0Submission({
|
|
@@ -6068,6 +6290,23 @@ async function submitTransactionDirect(connection, programId, depositor, proofBy
|
|
|
6068
6290
|
compact: compactInstructions,
|
|
6069
6291
|
minimal: minimalInstructions,
|
|
6070
6292
|
createSupplementalAlt: async () => {
|
|
6293
|
+
if (relayAltEligible()) {
|
|
6294
|
+
try {
|
|
6295
|
+
onProgress?.("Requesting supplemental lookup table from relay...");
|
|
6296
|
+
const relayAlt = await getRelaySupplementalAlt();
|
|
6297
|
+
addressLookupTableAccounts = [
|
|
6298
|
+
...(addressLookupTableAccounts ?? []).filter((t) => !t.key.equals(relayAlt.key)),
|
|
6299
|
+
relayAlt
|
|
6300
|
+
];
|
|
6301
|
+
({ blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash());
|
|
6302
|
+
return;
|
|
6303
|
+
} catch (err) {
|
|
6304
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
6305
|
+
onProgress?.(
|
|
6306
|
+
`Relay supplemental ALT failed (${msg}); falling back to a depositor-signed table.`
|
|
6307
|
+
);
|
|
6308
|
+
}
|
|
6309
|
+
}
|
|
6071
6310
|
const supplementalAddresses = collectLookupCandidatesFromInstructions(
|
|
6072
6311
|
minimalInstructions,
|
|
6073
6312
|
depositor.publicKey,
|
|
@@ -6860,6 +7099,21 @@ async function transact(params, options) {
|
|
|
6860
7099
|
"options.chainNoteSalt requires an explicit chainNoteViewingKeyNk (or getChainNoteViewingKeyNk). The salt anchors a deposit note derived from that nk; inferring nk from the output note's own key would encrypt the chain note under a different key than the note was derived from, and the deposit would be undiscoverable."
|
|
6861
7100
|
);
|
|
6862
7101
|
}
|
|
7102
|
+
const chainNoteSalt = options.chainNoteSalt !== void 0 ? assertChainNoteSalt(options.chainNoteSalt) : randomChainNoteSalt();
|
|
7103
|
+
const chainNoteWillCarrySalt = !options.disableChainNotes && !(options.encryptedNotes && options.encryptedNotes.length > 0);
|
|
7104
|
+
if (explicitChainNoteNk && chainNoteWillCarrySalt) {
|
|
7105
|
+
const inputOwners = new Set(inputUtxos.map((u) => u.keypair.publicKey));
|
|
7106
|
+
for (let i = 0; i < outputUtxos.length; i++) {
|
|
7107
|
+
const out = outputUtxos[i];
|
|
7108
|
+
if (!out || out.amount <= BigInt(0)) continue;
|
|
7109
|
+
if (!inputOwners.has(out.keypair.publicKey)) continue;
|
|
7110
|
+
if (out.blinding !== deriveChangeNoteBlinding(explicitChainNoteNk, chainNoteSalt, i)) {
|
|
7111
|
+
throw new Error(
|
|
7112
|
+
`Output ${i} is change (amount ${out.amount}, owned by an input's keypair) but its blinding is not the one derivable from this wallet's viewing key. It was almost certainly built with \`createUtxo\`, whose blinding is random and is written nowhere on chain \u2014 if the caller ever drops the returned note, the funds are unspendable forever. Build change with \`createRecoverableChangeUtxo(amount, keypair, nk, mint, salt, outputIndex)\` and pass the same salt as \`options.chainNoteSalt\`, or use \`partialWithdraw\` / \`transfer\` / \`swapWithChange\`, which do it for you.`
|
|
7113
|
+
);
|
|
7114
|
+
}
|
|
7115
|
+
}
|
|
7116
|
+
}
|
|
6863
7117
|
assertAllowedRpcConnection(connection);
|
|
6864
7118
|
await ensureViewingKeyRegistered(options, onProgress, fallbackNk);
|
|
6865
7119
|
const resolvedChainNoteNk = explicitChainNoteNk ?? fallbackNk ?? null;
|
|
@@ -7214,7 +7468,6 @@ async function transact(params, options) {
|
|
|
7214
7468
|
const maxFee = isWithdrawal ? protocolFeeSnapshot : BigInt(0);
|
|
7215
7469
|
const extDataHash = await computeExtDataHash(recipient ?? null, relayerFee, relayer ?? null, maxFee);
|
|
7216
7470
|
const chainNoteTimestamp = BigInt(Date.now());
|
|
7217
|
-
const chainNoteSalt = options.chainNoteSalt !== void 0 ? assertChainNoteSalt(options.chainNoteSalt) : randomChainNoteSalt();
|
|
7218
7471
|
const mintField = pubkeyToFieldElement(mint);
|
|
7219
7472
|
let signature = "pending-" + Date.now().toString();
|
|
7220
7473
|
let commitmentIndices = [-1, -1];
|
|
@@ -7489,7 +7742,8 @@ async function transact(params, options) {
|
|
|
7489
7742
|
relayer ?? null,
|
|
7490
7743
|
relayerFee,
|
|
7491
7744
|
options.onTransactProofBuilt,
|
|
7492
|
-
externalFeePayer
|
|
7745
|
+
externalFeePayer,
|
|
7746
|
+
options.relaySupplementalAlt
|
|
7493
7747
|
);
|
|
7494
7748
|
signature = directResult.signature;
|
|
7495
7749
|
commitmentIndices = directResult.commitmentIndices;
|
|
@@ -7881,16 +8135,30 @@ async function transfer(inputUtxos, recipientPubkey, amount, options) {
|
|
|
7881
8135
|
const change = inputSum - amount;
|
|
7882
8136
|
const myKeypair = inputUtxos[0].keypair;
|
|
7883
8137
|
const mint = inputUtxos[0].mintAddress;
|
|
8138
|
+
const transferNk = await resolveChainNoteNk(options);
|
|
8139
|
+
const changeNoteSalt = transferNk ? randomChangeNoteSalt() : void 0;
|
|
8140
|
+
const isSendToSelf = inputUtxos.some((u) => u.keypair.publicKey === recipientPubkey);
|
|
7884
8141
|
const recipientKeypair = {
|
|
7885
8142
|
privateKey: BigInt(0),
|
|
7886
8143
|
// Recipient's private key unknown
|
|
7887
8144
|
publicKey: recipientPubkey
|
|
7888
8145
|
};
|
|
7889
|
-
const recipientUtxo = await createUtxo(amount, recipientKeypair, mint);
|
|
8146
|
+
const recipientUtxo = transferNk && isSendToSelf ? (await createRecoverableChangeUtxo(amount, myKeypair, transferNk, mint, changeNoteSalt, 0)).utxo : await createUtxo(amount, recipientKeypair, mint);
|
|
7890
8147
|
const outputUtxos = [recipientUtxo];
|
|
7891
8148
|
if (change > BigInt(0)) {
|
|
7892
|
-
|
|
7893
|
-
|
|
8149
|
+
if (transferNk) {
|
|
8150
|
+
const { utxo } = await createRecoverableChangeUtxo(
|
|
8151
|
+
change,
|
|
8152
|
+
myKeypair,
|
|
8153
|
+
transferNk,
|
|
8154
|
+
mint,
|
|
8155
|
+
changeNoteSalt,
|
|
8156
|
+
1
|
|
8157
|
+
);
|
|
8158
|
+
outputUtxos.push(utxo);
|
|
8159
|
+
} else {
|
|
8160
|
+
outputUtxos.push(await createUtxo(change, myKeypair, mint));
|
|
8161
|
+
}
|
|
7894
8162
|
}
|
|
7895
8163
|
return transact(
|
|
7896
8164
|
{
|
|
@@ -7899,7 +8167,7 @@ async function transfer(inputUtxos, recipientPubkey, amount, options) {
|
|
|
7899
8167
|
externalAmount: BigInt(0)
|
|
7900
8168
|
// Pure shield-to-shield
|
|
7901
8169
|
},
|
|
7902
|
-
options
|
|
8170
|
+
changeNoteSalt !== void 0 ? { ...options, chainNoteSalt: changeNoteSalt } : options
|
|
7903
8171
|
);
|
|
7904
8172
|
}
|
|
7905
8173
|
async function partialWithdraw(inputUtxos, recipient, withdrawAmount, options) {
|
|
@@ -7916,7 +8184,9 @@ async function partialWithdraw(inputUtxos, recipient, withdrawAmount, options) {
|
|
|
7916
8184
|
utxos.sort((a, b) => a.amount > b.amount ? 1 : a.amount < b.amount ? -1 : 0);
|
|
7917
8185
|
const [small1, small2, ...rest] = utxos;
|
|
7918
8186
|
const mergedAmount = small1.amount + small2.amount;
|
|
7919
|
-
const
|
|
8187
|
+
const mergeNk = await resolveChainNoteNk(options);
|
|
8188
|
+
const mergeSalt = mergeNk ? randomChangeNoteSalt() : void 0;
|
|
8189
|
+
const mergedOutput = mergeNk ? (await createRecoverableChangeUtxo(mergedAmount, myKeypair, mergeNk, mint, mergeSalt, 0)).utxo : await createUtxo(mergedAmount, myKeypair, mint);
|
|
7920
8190
|
const mergeResult = await transact(
|
|
7921
8191
|
{
|
|
7922
8192
|
inputUtxos: [small1, small2],
|
|
@@ -7926,7 +8196,8 @@ async function partialWithdraw(inputUtxos, recipient, withdrawAmount, options) {
|
|
|
7926
8196
|
},
|
|
7927
8197
|
{
|
|
7928
8198
|
...options,
|
|
7929
|
-
cachedMerkleTree: cachedTree
|
|
8199
|
+
cachedMerkleTree: cachedTree,
|
|
8200
|
+
...mergeSalt !== void 0 && { chainNoteSalt: mergeSalt }
|
|
7930
8201
|
}
|
|
7931
8202
|
);
|
|
7932
8203
|
cachedTree = mergeResult.merkleTree;
|
|
@@ -7939,9 +8210,25 @@ async function partialWithdraw(inputUtxos, recipient, withdrawAmount, options) {
|
|
|
7939
8210
|
const finalInputSum = sumUtxoAmounts(utxos);
|
|
7940
8211
|
const change = finalInputSum - withdrawAmount;
|
|
7941
8212
|
const outputUtxos = [];
|
|
8213
|
+
let changeNoteSalt;
|
|
7942
8214
|
if (change > BigInt(0)) {
|
|
7943
|
-
const
|
|
7944
|
-
|
|
8215
|
+
const changeNk = await resolveChainNoteNk(options);
|
|
8216
|
+
if (changeNk) {
|
|
8217
|
+
const salt = randomChangeNoteSalt();
|
|
8218
|
+
const { utxo } = await createRecoverableChangeUtxo(
|
|
8219
|
+
change,
|
|
8220
|
+
myKeypair,
|
|
8221
|
+
changeNk,
|
|
8222
|
+
mint,
|
|
8223
|
+
salt,
|
|
8224
|
+
0
|
|
8225
|
+
// partialWithdraw puts change at output 0
|
|
8226
|
+
);
|
|
8227
|
+
outputUtxos.push(utxo);
|
|
8228
|
+
changeNoteSalt = salt;
|
|
8229
|
+
} else {
|
|
8230
|
+
outputUtxos.push(await createUtxo(change, myKeypair, mint));
|
|
8231
|
+
}
|
|
7945
8232
|
}
|
|
7946
8233
|
return transact(
|
|
7947
8234
|
{
|
|
@@ -7952,7 +8239,8 @@ async function partialWithdraw(inputUtxos, recipient, withdrawAmount, options) {
|
|
|
7952
8239
|
},
|
|
7953
8240
|
{
|
|
7954
8241
|
...options,
|
|
7955
|
-
cachedMerkleTree: cachedTree
|
|
8242
|
+
cachedMerkleTree: cachedTree,
|
|
8243
|
+
...changeNoteSalt !== void 0 && { chainNoteSalt: changeNoteSalt }
|
|
7956
8244
|
}
|
|
7957
8245
|
);
|
|
7958
8246
|
}
|
|
@@ -8086,7 +8374,16 @@ async function swapUtxo(params, options) {
|
|
|
8086
8374
|
onProgress?.("Validating transaction parameters...");
|
|
8087
8375
|
const swapFallbackNk = getNkFromUtxoPrivateKey(inputUtxos[0].keypair.privateKey);
|
|
8088
8376
|
await ensureViewingKeyRegistered(options, onProgress, swapFallbackNk);
|
|
8089
|
-
const
|
|
8377
|
+
const explicitSwapChainNoteNk = await resolveChainNoteNk(options);
|
|
8378
|
+
const resolvedSwapChainNoteNk = explicitSwapChainNoteNk ?? swapFallbackNk;
|
|
8379
|
+
const chainNoteSalt = options.chainNoteSalt !== void 0 ? assertChainNoteSalt(options.chainNoteSalt) : randomChainNoteSalt();
|
|
8380
|
+
if (explicitSwapChainNoteNk && changeUtxo && changeUtxo.amount > BigInt(0) && inputUtxos.some((u) => u.keypair.publicKey === changeUtxo.keypair.publicKey)) {
|
|
8381
|
+
if (changeUtxo.blinding !== deriveChangeNoteBlinding(explicitSwapChainNoteNk, chainNoteSalt, 0)) {
|
|
8382
|
+
throw new Error(
|
|
8383
|
+
`Swap change (amount ${changeUtxo.amount}, owned by an input's keypair) has a blinding that is not derivable from this wallet's viewing key. It was almost certainly built with \`createUtxo\`, whose blinding is random and is written nowhere on chain \u2014 if the caller ever drops the returned note, the funds are unspendable forever. Build it with \`createRecoverableChangeUtxo(amount, keypair, nk, mint, salt, 0)\` and pass the same salt as \`options.chainNoteSalt\`, or use \`swapWithChange\`, which does it for you.`
|
|
8384
|
+
);
|
|
8385
|
+
}
|
|
8386
|
+
}
|
|
8090
8387
|
const swapPoolMint = NATIVE_SOL_MINT;
|
|
8091
8388
|
const pdas = getShieldPoolPDAs(programId, swapPoolMint);
|
|
8092
8389
|
let merkleState = null;
|
|
@@ -8428,7 +8725,6 @@ async function swapUtxo(params, options) {
|
|
|
8428
8725
|
outputCommitments.push(await computeCommitment(utxo));
|
|
8429
8726
|
}
|
|
8430
8727
|
const chainNoteTimestamp = BigInt(Date.now());
|
|
8431
|
-
const chainNoteSalt = randomChainNoteSalt();
|
|
8432
8728
|
onProgress?.("Computing external data hash...");
|
|
8433
8729
|
const mint = swapPoolMint;
|
|
8434
8730
|
const mintField = pubkeyToFieldElement(mint);
|
|
@@ -8646,6 +8942,9 @@ async function swapUtxo(params, options) {
|
|
|
8646
8942
|
url.searchParams.set("wallet", poolPda.toBase58());
|
|
8647
8943
|
url.searchParams.set("recipient", recipientWallet.toBase58());
|
|
8648
8944
|
url.searchParams.set("sender", recipientWallet.toBase58());
|
|
8945
|
+
url.searchParams.set("pool_mint", mint.toBase58());
|
|
8946
|
+
url.searchParams.set("nullifier0", toHex(bigintToBytes322(inputNullifiers[0])));
|
|
8947
|
+
url.searchParams.set("nullifier1", toHex(bigintToBytes322(inputNullifiers[1])));
|
|
8649
8948
|
const quoteRes = await relayFetch(url.toString());
|
|
8650
8949
|
if (!quoteRes.ok) {
|
|
8651
8950
|
const text = await quoteRes.text();
|
|
@@ -8890,10 +9189,18 @@ async function swapWithChange(inputUtxos, swapAmount, outputMint, recipientAta,
|
|
|
8890
9189
|
throw new Error(`Insufficient balance: input(${totalInput}) < swap(${swapAmount})`);
|
|
8891
9190
|
}
|
|
8892
9191
|
let changeUtxo;
|
|
9192
|
+
let changeNoteSalt;
|
|
8893
9193
|
if (change > BigInt(0)) {
|
|
8894
9194
|
const myKeypair = inputUtxos[0].keypair;
|
|
8895
9195
|
const mint = inputUtxos[0].mintAddress;
|
|
8896
|
-
|
|
9196
|
+
const changeNk = await resolveChainNoteNk(options);
|
|
9197
|
+
if (changeNk) {
|
|
9198
|
+
const salt = randomChangeNoteSalt();
|
|
9199
|
+
changeUtxo = (await createRecoverableChangeUtxo(change, myKeypair, changeNk, mint, salt, 0)).utxo;
|
|
9200
|
+
changeNoteSalt = salt;
|
|
9201
|
+
} else {
|
|
9202
|
+
changeUtxo = await createUtxo(change, myKeypair, mint);
|
|
9203
|
+
}
|
|
8897
9204
|
}
|
|
8898
9205
|
return swapUtxo(
|
|
8899
9206
|
{
|
|
@@ -8905,7 +9212,7 @@ async function swapWithChange(inputUtxos, swapAmount, outputMint, recipientAta,
|
|
|
8905
9212
|
recipientWallet,
|
|
8906
9213
|
minOutputAmount
|
|
8907
9214
|
},
|
|
8908
|
-
options
|
|
9215
|
+
changeNoteSalt !== void 0 ? { ...options, chainNoteSalt: changeNoteSalt } : options
|
|
8909
9216
|
);
|
|
8910
9217
|
}
|
|
8911
9218
|
|
|
@@ -9708,6 +10015,7 @@ async function scanTransactions(opts) {
|
|
|
9708
10015
|
const txs = [];
|
|
9709
10016
|
const swapCtxByCommitment = /* @__PURE__ */ new Map();
|
|
9710
10017
|
const recoveredDepositNotes = [];
|
|
10018
|
+
const recoveredChangeNotes = [];
|
|
9711
10019
|
let processed = 0;
|
|
9712
10020
|
let rpcCallsMade = 0;
|
|
9713
10021
|
onStatus?.(`Scanning ${sigInfos.length} transactions...`);
|
|
@@ -10055,6 +10363,34 @@ async function scanTransactions(opts) {
|
|
|
10055
10363
|
if (debug) console.log(`[DEBUG] ${sigInfo.signature}: deposit recovery failed (${e?.message || e})`);
|
|
10056
10364
|
}
|
|
10057
10365
|
}
|
|
10366
|
+
if (isWithdrawal && compactNote.noteSalt !== void 0 && compactNote.outAmount0 !== void 0 && compactNote.outAmount0 > 0n && compactNote.outPubkey0 !== void 0 && compactNote.outPubkey0 !== 0n) {
|
|
10367
|
+
try {
|
|
10368
|
+
const recoveredChange = await matchChangeNote({
|
|
10369
|
+
viewingKeyNk,
|
|
10370
|
+
noteSalt: compactNote.noteSalt,
|
|
10371
|
+
amount: compactNote.outAmount0,
|
|
10372
|
+
keypair: { privateKey: 0n, publicKey: compactNote.outPubkey0 },
|
|
10373
|
+
mintAddress: new PublicKey9(asset.mint),
|
|
10374
|
+
outputIndex: 0,
|
|
10375
|
+
// v4 describes output 0, which is where change lands
|
|
10376
|
+
outputCommitments: ixCtx.outputCommitments ?? []
|
|
10377
|
+
});
|
|
10378
|
+
if (recoveredChange) {
|
|
10379
|
+
recoveredChangeNotes.push({
|
|
10380
|
+
...recoveredChange,
|
|
10381
|
+
signature: sigInfo.signature,
|
|
10382
|
+
timestamp: decoded.timestamp
|
|
10383
|
+
});
|
|
10384
|
+
if (debug) {
|
|
10385
|
+
console.log(
|
|
10386
|
+
`[DEBUG] ${sigInfo.signature}: recovered change note (amount=${recoveredChange.amount})`
|
|
10387
|
+
);
|
|
10388
|
+
}
|
|
10389
|
+
}
|
|
10390
|
+
} catch (e) {
|
|
10391
|
+
if (debug) console.log(`[DEBUG] ${sigInfo.signature}: change recovery failed (${e?.message || e})`);
|
|
10392
|
+
}
|
|
10393
|
+
}
|
|
10058
10394
|
txs.push({
|
|
10059
10395
|
txType: decoded.txType,
|
|
10060
10396
|
amount: grossAmount,
|
|
@@ -10160,7 +10496,8 @@ async function scanTransactions(opts) {
|
|
|
10160
10496
|
lastSignature: newestSignature,
|
|
10161
10497
|
rpcCallsMade,
|
|
10162
10498
|
deliveredNotes,
|
|
10163
|
-
recoveredDepositNotes
|
|
10499
|
+
recoveredDepositNotes,
|
|
10500
|
+
recoveredChangeNotes
|
|
10164
10501
|
};
|
|
10165
10502
|
}
|
|
10166
10503
|
function toComplianceReport(result) {
|
|
@@ -10606,12 +10943,14 @@ export {
|
|
|
10606
10943
|
createCloakError,
|
|
10607
10944
|
createDepositInstruction,
|
|
10608
10945
|
createLogger,
|
|
10946
|
+
createRecoverableChangeUtxo,
|
|
10609
10947
|
createRecoverableDepositUtxo,
|
|
10610
10948
|
createUtxo,
|
|
10611
10949
|
createZeroUtxo,
|
|
10612
10950
|
decryptCompactChainNote,
|
|
10613
10951
|
decryptComplianceMetadataWithMasterKey,
|
|
10614
10952
|
decryptTransactionMetadata,
|
|
10953
|
+
deriveChangeNoteBlinding,
|
|
10615
10954
|
deriveDepositNoteSecrets,
|
|
10616
10955
|
deriveDiversifiedViewingKey,
|
|
10617
10956
|
deriveDiversifier,
|
|
@@ -10697,6 +11036,7 @@ export {
|
|
|
10697
11036
|
loadPendingDeposits,
|
|
10698
11037
|
loadPendingWithdrawals,
|
|
10699
11038
|
loadVerifiedCircuitArtifacts,
|
|
11039
|
+
matchChangeNote,
|
|
10700
11040
|
matchDepositNote,
|
|
10701
11041
|
matchSwapRefundLeaf,
|
|
10702
11042
|
openRecipientDeliveryNote,
|
|
@@ -10717,6 +11057,7 @@ export {
|
|
|
10717
11057
|
pubkeyToFieldElement,
|
|
10718
11058
|
pubkeyToLimbs,
|
|
10719
11059
|
randomBytes,
|
|
11060
|
+
randomChangeNoteSalt,
|
|
10720
11061
|
randomDepositNoteSalt,
|
|
10721
11062
|
randomFieldElement,
|
|
10722
11063
|
readMerkleTreeState,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloak.dev/sdk",
|
|
3
3
|
"description": "Shield, send, and swap on Solana privately — TypeScript SDK with UTXO-based zero-knowledge transactions",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.2-staging.0f03668",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
7
7
|
"module": "dist/index.js",
|