@cloak.dev/sdk 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +196 -22
- package/dist/index.d.cts +1054 -920
- package/dist/index.d.ts +1054 -920
- package/dist/index.js +189 -22
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -877,6 +877,71 @@ function parseRelayErrorResponse(responseText) {
|
|
|
877
877
|
return { isRootNotFound: false };
|
|
878
878
|
}
|
|
879
879
|
}
|
|
880
|
+
var UtxoAlreadySpentError = class extends Error {
|
|
881
|
+
constructor(message, spentUtxoCommitments = []) {
|
|
882
|
+
super(message);
|
|
883
|
+
this.spentUtxoCommitments = spentUtxoCommitments;
|
|
884
|
+
this.name = "UtxoAlreadySpentError";
|
|
885
|
+
}
|
|
886
|
+
};
|
|
887
|
+
var SanctionsQuoteError = class extends Error {
|
|
888
|
+
constructor(message, onChainCode, subKind) {
|
|
889
|
+
super(message);
|
|
890
|
+
this.onChainCode = onChainCode;
|
|
891
|
+
this.subKind = subKind;
|
|
892
|
+
this.name = "SanctionsQuoteError";
|
|
893
|
+
}
|
|
894
|
+
};
|
|
895
|
+
var RelayInternalError = class extends Error {
|
|
896
|
+
constructor(message, relayMessage, httpStatus, cachedTreeFromChain) {
|
|
897
|
+
super(message);
|
|
898
|
+
this.relayMessage = relayMessage;
|
|
899
|
+
this.httpStatus = httpStatus;
|
|
900
|
+
this.cachedTreeFromChain = cachedTreeFromChain;
|
|
901
|
+
this.name = "RelayInternalError";
|
|
902
|
+
}
|
|
903
|
+
};
|
|
904
|
+
function classifyRelayError(responseText, httpStatus) {
|
|
905
|
+
let inner = "";
|
|
906
|
+
try {
|
|
907
|
+
const parsed = JSON.parse(responseText);
|
|
908
|
+
inner = parsed?.message ?? "";
|
|
909
|
+
} catch {
|
|
910
|
+
inner = responseText;
|
|
911
|
+
}
|
|
912
|
+
const haystack = inner.toLowerCase();
|
|
913
|
+
if (haystack.includes("0x1020") || haystack.includes("doublespend")) {
|
|
914
|
+
return new UtxoAlreadySpentError(
|
|
915
|
+
"This shielded balance was already spent. The UTXO's nullifier is registered on-chain \u2014 typically means you spent it via another session or device. Run verifyUtxos() to reconcile local state."
|
|
916
|
+
);
|
|
917
|
+
}
|
|
918
|
+
if (haystack.includes("0x10b0") || haystack.includes("rangequoteexpired")) {
|
|
919
|
+
return new SanctionsQuoteError(
|
|
920
|
+
"Range sanctions quote expired before the on-chain program processed the transaction. The relay's quote-expiry window is too short relative to proof-gen + confirmation latency, or on-chain clock drifted. Retry the send; the relay will issue a fresh quote.",
|
|
921
|
+
4272,
|
|
922
|
+
"expired"
|
|
923
|
+
);
|
|
924
|
+
}
|
|
925
|
+
if (haystack.includes("0x10b2") || haystack.includes("rangequotewalletmismatch")) {
|
|
926
|
+
return new SanctionsQuoteError(
|
|
927
|
+
"Range sanctions quote's wallet doesn't match the on-chain expected wallet. Indicates a relay config drift \u2014 the sender used for quote signing doesn't match the program's pool.range_signer / fee payer. Relay operators must fix; caller retry will not help.",
|
|
928
|
+
4274,
|
|
929
|
+
"wallet_mismatch"
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
if (haystack.includes("0x10b3") || haystack.includes("rangequotemissinged25519")) {
|
|
933
|
+
return new SanctionsQuoteError(
|
|
934
|
+
"Transaction reached the on-chain program without the required Ed25519 sanctions quote instruction. This is a relay bug \u2014 the relay constructed the tx without attaching the signed quote. Caller retry will not help.",
|
|
935
|
+
4275,
|
|
936
|
+
"missing_ix"
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
return new RelayInternalError(
|
|
940
|
+
`Relay returned ${httpStatus ?? "an error"}: ${inner || responseText}`,
|
|
941
|
+
inner || responseText,
|
|
942
|
+
httpStatus
|
|
943
|
+
);
|
|
944
|
+
}
|
|
880
945
|
var ShieldPoolErrors = {
|
|
881
946
|
// Root management errors
|
|
882
947
|
4096: "Invalid Merkle root",
|
|
@@ -4173,6 +4238,63 @@ async function decryptComplianceMetadataWithMasterKey(encrypted, masterComplianc
|
|
|
4173
4238
|
return decryptWithSharedSecret(payload, sharedSecret);
|
|
4174
4239
|
}
|
|
4175
4240
|
|
|
4241
|
+
// src/utils/verify-utxos.ts
|
|
4242
|
+
async function verifyUtxos(utxos, connection, programId, commitment = "confirmed") {
|
|
4243
|
+
const checkable = [];
|
|
4244
|
+
const skipped = [];
|
|
4245
|
+
for (const utxo of utxos) {
|
|
4246
|
+
if (utxo.commitment === void 0 || utxo.mintAddress === void 0 || utxo.keypair === void 0 || utxo.keypair.privateKey === void 0) {
|
|
4247
|
+
skipped.push(utxo);
|
|
4248
|
+
continue;
|
|
4249
|
+
}
|
|
4250
|
+
try {
|
|
4251
|
+
const nullifierBig = await computeNullifier(utxo);
|
|
4252
|
+
const nullifierHex = nullifierBig.toString(16).padStart(64, "0");
|
|
4253
|
+
const nullifierBytes = Buffer.from(nullifierHex, "hex");
|
|
4254
|
+
const { pool } = getShieldPoolPDAs(programId, utxo.mintAddress);
|
|
4255
|
+
const [pda] = getNullifierPDA(pool, nullifierBytes, programId);
|
|
4256
|
+
checkable.push({ utxo, pda });
|
|
4257
|
+
} catch {
|
|
4258
|
+
skipped.push(utxo);
|
|
4259
|
+
}
|
|
4260
|
+
}
|
|
4261
|
+
if (checkable.length === 0) {
|
|
4262
|
+
return { spent: [], unspent: [], skipped };
|
|
4263
|
+
}
|
|
4264
|
+
const CHUNK = 100;
|
|
4265
|
+
const infos = [];
|
|
4266
|
+
for (let i = 0; i < checkable.length; i += CHUNK) {
|
|
4267
|
+
const page = checkable.slice(i, i + CHUNK).map((e) => e.pda);
|
|
4268
|
+
const pageInfos = await connection.getMultipleAccountsInfo(page, commitment);
|
|
4269
|
+
infos.push(...pageInfos);
|
|
4270
|
+
}
|
|
4271
|
+
const spent = [];
|
|
4272
|
+
const unspent = [];
|
|
4273
|
+
for (let i = 0; i < checkable.length; i++) {
|
|
4274
|
+
if (infos[i] !== null) {
|
|
4275
|
+
spent.push(checkable[i].utxo);
|
|
4276
|
+
} else {
|
|
4277
|
+
unspent.push(checkable[i].utxo);
|
|
4278
|
+
}
|
|
4279
|
+
}
|
|
4280
|
+
return { spent, unspent, skipped };
|
|
4281
|
+
}
|
|
4282
|
+
async function preflightNullifiers(utxos, connection, programId, commitment = "confirmed") {
|
|
4283
|
+
if (utxos.length === 0) return;
|
|
4284
|
+
const result = await verifyUtxos(utxos, connection, programId, commitment);
|
|
4285
|
+
if (result.spent.length > 0) {
|
|
4286
|
+
const allCommitments = result.spent.map(
|
|
4287
|
+
(u) => u.commitment !== void 0 ? u.commitment.toString(16).padStart(64, "0") : "<unknown>"
|
|
4288
|
+
);
|
|
4289
|
+
const preview = allCommitments.slice(0, 5);
|
|
4290
|
+
const extra = allCommitments.length > preview.length ? ` (+${allCommitments.length - preview.length} more)` : "";
|
|
4291
|
+
throw new UtxoAlreadySpentError(
|
|
4292
|
+
`Pre-flight detected ${allCommitments.length} already-spent UTXO(s). First spent commitment(s): ${preview.join(", ")}${extra}. Call verifyUtxos() and remove these from local state before retrying.`,
|
|
4293
|
+
allCommitments
|
|
4294
|
+
);
|
|
4295
|
+
}
|
|
4296
|
+
}
|
|
4297
|
+
|
|
4176
4298
|
// src/services/risk-oracle.ts
|
|
4177
4299
|
function buildRangeRiskScoreJob(OracleJob, walletAddress) {
|
|
4178
4300
|
return OracleJob.fromObject({
|
|
@@ -4521,10 +4643,15 @@ import _bs58 from "bs58";
|
|
|
4521
4643
|
var bs58 = _bs58.default || _bs58;
|
|
4522
4644
|
var TRANSACT_TAG = 0;
|
|
4523
4645
|
var TRANSACT_SWAP_TAG = 1;
|
|
4524
|
-
var DEPOSIT_TAG = 1;
|
|
4525
4646
|
var PROOF_LEN = 256;
|
|
4526
4647
|
var PUBLIC_INPUTS_LEN = 264;
|
|
4527
4648
|
var COMMITMENTS_OFFSET = 168;
|
|
4649
|
+
function isAllZero(b) {
|
|
4650
|
+
for (let i = 0; i < b.length; i++) {
|
|
4651
|
+
if (b[i] !== 0) return false;
|
|
4652
|
+
}
|
|
4653
|
+
return true;
|
|
4654
|
+
}
|
|
4528
4655
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
4529
4656
|
var DEFAULT_MAX_RETRIES = 3;
|
|
4530
4657
|
async function fetchWithRetry(url, options = {}) {
|
|
@@ -4662,24 +4789,20 @@ async function buildMerkleTreeFromRelay(relayUrl, options) {
|
|
|
4662
4789
|
function extractCommitmentsFromInstruction(data) {
|
|
4663
4790
|
if (data.length === 0) return [];
|
|
4664
4791
|
const tag = data[0];
|
|
4665
|
-
if (tag
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
if (
|
|
4678
|
-
|
|
4679
|
-
const commitment = data.slice(1 + 8, 1 + 8 + 32);
|
|
4680
|
-
return [commitment];
|
|
4681
|
-
}
|
|
4682
|
-
return [];
|
|
4792
|
+
if (tag !== TRANSACT_TAG && tag !== TRANSACT_SWAP_TAG) return [];
|
|
4793
|
+
const publicInputsStart = 1 + PROOF_LEN;
|
|
4794
|
+
const publicInputsEnd = publicInputsStart + PUBLIC_INPUTS_LEN;
|
|
4795
|
+
if (data.length < publicInputsEnd) return [];
|
|
4796
|
+
const publicInputs = data.slice(publicInputsStart, publicInputsEnd);
|
|
4797
|
+
const c0 = publicInputs.slice(COMMITMENTS_OFFSET, COMMITMENTS_OFFSET + 32);
|
|
4798
|
+
const c1 = publicInputs.slice(
|
|
4799
|
+
COMMITMENTS_OFFSET + 32,
|
|
4800
|
+
COMMITMENTS_OFFSET + 64
|
|
4801
|
+
);
|
|
4802
|
+
const out = [];
|
|
4803
|
+
if (!isAllZero(c0)) out.push(c0);
|
|
4804
|
+
if (!isAllZero(c1)) out.push(c1);
|
|
4805
|
+
return out;
|
|
4683
4806
|
}
|
|
4684
4807
|
function isBrowser() {
|
|
4685
4808
|
return typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
|
|
@@ -6299,6 +6422,7 @@ async function transact(params, options) {
|
|
|
6299
6422
|
if (outputUtxos.length > 2) {
|
|
6300
6423
|
throw new Error("Maximum 2 output UTXOs allowed");
|
|
6301
6424
|
}
|
|
6425
|
+
await preflightNullifiers(inputUtxos, connection, programId);
|
|
6302
6426
|
const mint = inputUtxos.find((u) => u.amount > 0)?.mintAddress ?? outputUtxos.find((u) => u.amount > 0)?.mintAddress ?? NATIVE_SOL_MINT;
|
|
6303
6427
|
const paddedInputs = [...inputUtxos];
|
|
6304
6428
|
while (paddedInputs.length < 2) {
|
|
@@ -7126,10 +7250,46 @@ async function transact(params, options) {
|
|
|
7126
7250
|
}
|
|
7127
7251
|
}
|
|
7128
7252
|
if (lastError) {
|
|
7253
|
+
const classified = classifyRelayError(lastError.message);
|
|
7254
|
+
const isMerkleClass = !(classified instanceof UtxoAlreadySpentError) && !(classified instanceof SanctionsQuoteError) && !isRootNotFoundError2(lastError);
|
|
7255
|
+
if (!IS_BROWSER2 && isMerkleClass && relayUrl) {
|
|
7256
|
+
onProgress?.(
|
|
7257
|
+
"All relay-tree attempts failed. Rebuilding merkle tree from chain as last resort..."
|
|
7258
|
+
);
|
|
7259
|
+
try {
|
|
7260
|
+
const [merkleTreePda] = PublicKey6.findProgramAddressSync(
|
|
7261
|
+
[Buffer.from("merkle_tree"), mint.toBuffer()],
|
|
7262
|
+
programId
|
|
7263
|
+
);
|
|
7264
|
+
const chainTree = await buildMerkleTreeFromChain(
|
|
7265
|
+
connection,
|
|
7266
|
+
programId,
|
|
7267
|
+
merkleTreePda,
|
|
7268
|
+
onProgress
|
|
7269
|
+
);
|
|
7270
|
+
const chainRootHex = chainTree.root().toString(16).padStart(64, "0");
|
|
7271
|
+
onProgress?.(
|
|
7272
|
+
`Chain-rebuilt tree root: ${chainRootHex.substring(0, 16)}... \u2014 attached to error for caller retry`
|
|
7273
|
+
);
|
|
7274
|
+
throw new RelayInternalError(
|
|
7275
|
+
`All relay-tree attempts failed. Chain-rebuilt tree available (root ${chainRootHex.substring(0, 16)}...). Retry this call with options.cachedMerkleTree = err.cachedTreeFromChain. Original error: ${lastError.message}`,
|
|
7276
|
+
lastError.message,
|
|
7277
|
+
void 0,
|
|
7278
|
+
chainTree
|
|
7279
|
+
);
|
|
7280
|
+
} catch (e) {
|
|
7281
|
+
if (e instanceof RelayInternalError) throw e;
|
|
7282
|
+
onProgress?.(
|
|
7283
|
+
`Chain-replay fallback failed: ${e instanceof Error ? e.message : String(e)}`
|
|
7284
|
+
);
|
|
7285
|
+
}
|
|
7286
|
+
}
|
|
7129
7287
|
if (isRootNotFoundError2(lastError)) {
|
|
7130
|
-
throw new Error(
|
|
7288
|
+
throw new Error(
|
|
7289
|
+
`Transaction failed after ${maxRootRetries + 1} retries: ${lastError.message}`
|
|
7290
|
+
);
|
|
7131
7291
|
}
|
|
7132
|
-
throw
|
|
7292
|
+
throw classified;
|
|
7133
7293
|
}
|
|
7134
7294
|
if (relayUrl && outputCommitments.length >= 2) {
|
|
7135
7295
|
const hasOnChainIndices = commitmentIndices[0] >= 0 && commitmentIndices[1] >= 0;
|
|
@@ -7433,6 +7593,7 @@ async function swapUtxo(params, options) {
|
|
|
7433
7593
|
if (inputUtxos.length > 2) {
|
|
7434
7594
|
throw new Error("Maximum 2 input UTXOs allowed");
|
|
7435
7595
|
}
|
|
7596
|
+
await preflightNullifiers(inputUtxos, connection, programId);
|
|
7436
7597
|
const outputMintAccount = await connection.getAccountInfo(outputMint, {
|
|
7437
7598
|
commitment: "confirmed"
|
|
7438
7599
|
});
|
|
@@ -9257,7 +9418,7 @@ var SimpleWallet = class {
|
|
|
9257
9418
|
};
|
|
9258
9419
|
|
|
9259
9420
|
// src/index.ts
|
|
9260
|
-
var VERSION = "1.
|
|
9421
|
+
var VERSION = "0.1.5";
|
|
9261
9422
|
var SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
9262
9423
|
export {
|
|
9263
9424
|
CLOAK_PROGRAM_ID,
|
|
@@ -9274,12 +9435,15 @@ export {
|
|
|
9274
9435
|
MemoryStorageAdapter,
|
|
9275
9436
|
MerkleTree,
|
|
9276
9437
|
NATIVE_SOL_MINT,
|
|
9438
|
+
RelayInternalError,
|
|
9277
9439
|
RelayService,
|
|
9278
9440
|
RootNotFoundError,
|
|
9279
9441
|
SCANNER_SUPPORTS_TRANSACT_SWAP,
|
|
9280
9442
|
SIGN_IN_MESSAGE,
|
|
9443
|
+
SanctionsQuoteError,
|
|
9281
9444
|
ShieldPoolErrors,
|
|
9282
9445
|
SimpleWallet,
|
|
9446
|
+
UtxoAlreadySpentError,
|
|
9283
9447
|
UtxoWallet,
|
|
9284
9448
|
VARIABLE_FEE_DENOMINATOR,
|
|
9285
9449
|
VARIABLE_FEE_NUMERATOR,
|
|
@@ -9298,6 +9462,7 @@ export {
|
|
|
9298
9462
|
calculateRelayFee,
|
|
9299
9463
|
chainNoteFromBase64,
|
|
9300
9464
|
chainNoteToBase64,
|
|
9465
|
+
classifyRelayError,
|
|
9301
9466
|
cleanupStalePendingOperations,
|
|
9302
9467
|
clearPendingDeposits,
|
|
9303
9468
|
clearPendingWithdrawals,
|
|
@@ -9410,6 +9575,7 @@ export {
|
|
|
9410
9575
|
partialWithdraw,
|
|
9411
9576
|
poseidonHash,
|
|
9412
9577
|
preflightCheck,
|
|
9578
|
+
preflightNullifiers,
|
|
9413
9579
|
prepareEncryptedOutput,
|
|
9414
9580
|
prepareEncryptedOutputForRecipient,
|
|
9415
9581
|
proofToBytes,
|
|
@@ -9457,6 +9623,7 @@ export {
|
|
|
9457
9623
|
validateWithdrawableNote,
|
|
9458
9624
|
verifyAllCircuits,
|
|
9459
9625
|
verifyCircuitIntegrity,
|
|
9626
|
+
verifyUtxos,
|
|
9460
9627
|
waitForRoot,
|
|
9461
9628
|
withTiming
|
|
9462
9629
|
};
|
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.1.
|
|
4
|
+
"version": "0.1.5",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
7
7
|
"module": "dist/index.js",
|