@cloak.dev/sdk 0.1.8 → 0.2.0
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 +435 -205
- package/dist/chunk-YX5SCAMR.js +594 -0
- package/dist/index.cjs +4336 -1788
- package/dist/index.d.cts +1460 -271
- package/dist/index.d.ts +1460 -271
- package/dist/index.js +3750 -1580
- package/dist/{utxo-LSTVI4HH.js → utxo-PFJT3ETR.js} +7 -5
- package/package.json +4 -3
- package/dist/chunk-2SOX3JNO.js +0 -255
package/dist/index.d.cts
CHANGED
|
@@ -1394,10 +1394,27 @@ declare function hexToBytes(hex: string): Uint8Array;
|
|
|
1394
1394
|
*/
|
|
1395
1395
|
declare function bytesToHex(bytes: Uint8Array, prefix?: boolean): string;
|
|
1396
1396
|
/**
|
|
1397
|
-
*
|
|
1397
|
+
* Thrown when no cryptographically secure randomness source is reachable.
|
|
1398
|
+
*
|
|
1399
|
+
* `randomBytes` backs note spend keys, blindings, salts and nonces. A predictable
|
|
1400
|
+
* value there is unrecoverable — it deanonymizes and can drain the note — so the
|
|
1401
|
+
* SDK fails closed instead of degrading to a non-cryptographic generator.
|
|
1402
|
+
*/
|
|
1403
|
+
declare class InsecureRandomnessError extends CloakError {
|
|
1404
|
+
constructor(message: string, originalError?: Error);
|
|
1405
|
+
}
|
|
1406
|
+
/**
|
|
1407
|
+
* Generate cryptographically secure random bytes.
|
|
1408
|
+
*
|
|
1409
|
+
* Source order: `globalThis.crypto.getRandomValues` first (works in every runtime
|
|
1410
|
+
* this SDK supports, under both ESM and CJS), then `node:crypto.randomFillSync`.
|
|
1411
|
+
* There is deliberately **no** insecure fallback: if neither source is usable this
|
|
1412
|
+
* throws {@link InsecureRandomnessError} naming every source that was tried and why
|
|
1413
|
+
* it failed.
|
|
1398
1414
|
*
|
|
1399
1415
|
* @param length - Number of bytes to generate
|
|
1400
1416
|
* @returns Random bytes
|
|
1417
|
+
* @throws {InsecureRandomnessError} If no cryptographically secure source is available
|
|
1401
1418
|
*/
|
|
1402
1419
|
declare function randomBytes(length: number): Uint8Array;
|
|
1403
1420
|
/**
|
|
@@ -1672,6 +1689,67 @@ declare class RelayInternalError extends Error {
|
|
|
1672
1689
|
*/
|
|
1673
1690
|
cachedTreeFromChain?: MerkleTree | undefined);
|
|
1674
1691
|
}
|
|
1692
|
+
/**
|
|
1693
|
+
* Raised when a submission's outcome could NOT be established as "landed" from chain state.
|
|
1694
|
+
*
|
|
1695
|
+
* This is the failure side of settlement verification (see `core/settlement.ts`). It exists
|
|
1696
|
+
* because the campaign's worst outcome was not a failed transaction — it was an AMBIGUOUS one
|
|
1697
|
+
* whose error text carried no signature (REL-A-10: seven POSTs, the transaction landed, the SDK
|
|
1698
|
+
* threw `RelayInternalError`, and the user had nothing to look up). Losing the signature is what
|
|
1699
|
+
* turns a landed transaction into a support ticket.
|
|
1700
|
+
*
|
|
1701
|
+
* `outcome` is deliberately unambiguous for the caller:
|
|
1702
|
+
*
|
|
1703
|
+
* "landed" the transaction DID land — the input nullifier PDAs exist — but the relay never
|
|
1704
|
+
* returned a usable response, so the SDK cannot hand back commitment indices.
|
|
1705
|
+
* DO NOT RETRY: a retry spends nothing and fails 0x1020. Rescan to recover notes.
|
|
1706
|
+
* "not-landed" provably nothing landed. The inputs are unspent; retrying is safe.
|
|
1707
|
+
* "unknown" the evidence is contradictory or unavailable. Check `signature` on an
|
|
1708
|
+
* independent RPC before doing anything else.
|
|
1709
|
+
* "failed" the transaction is on chain and failed during execution. Inputs are unspent.
|
|
1710
|
+
*/
|
|
1711
|
+
declare class SettlementVerificationError extends Error {
|
|
1712
|
+
/** What the chain evidence supports. See the class doc — each value implies a different action. */
|
|
1713
|
+
readonly outcome: "landed" | "not-landed" | "unknown" | "failed";
|
|
1714
|
+
/**
|
|
1715
|
+
* The signature to look up, when one is known. `null` means no counterparty ever gave the SDK
|
|
1716
|
+
* one — which is itself part of the report, not something to paper over.
|
|
1717
|
+
*/
|
|
1718
|
+
readonly signature: string | null;
|
|
1719
|
+
/** The verifier's own statement of what was and was not proven. */
|
|
1720
|
+
readonly reason: string;
|
|
1721
|
+
/** True when the input nullifier PDAs were observed on chain. */
|
|
1722
|
+
readonly nullifiersSpent: boolean;
|
|
1723
|
+
/** The underlying relay/RPC error, when the failure started as one. */
|
|
1724
|
+
readonly cause?: unknown | undefined;
|
|
1725
|
+
constructor(message: string,
|
|
1726
|
+
/** What the chain evidence supports. See the class doc — each value implies a different action. */
|
|
1727
|
+
outcome: "landed" | "not-landed" | "unknown" | "failed",
|
|
1728
|
+
/**
|
|
1729
|
+
* The signature to look up, when one is known. `null` means no counterparty ever gave the SDK
|
|
1730
|
+
* one — which is itself part of the report, not something to paper over.
|
|
1731
|
+
*/
|
|
1732
|
+
signature: string | null,
|
|
1733
|
+
/** The verifier's own statement of what was and was not proven. */
|
|
1734
|
+
reason: string,
|
|
1735
|
+
/** True when the input nullifier PDAs were observed on chain. */
|
|
1736
|
+
nullifiersSpent?: boolean,
|
|
1737
|
+
/** The underlying relay/RPC error, when the failure started as one. */
|
|
1738
|
+
cause?: unknown | undefined);
|
|
1739
|
+
/** True when retrying this exact spend is safe (nothing was consumed on chain). */
|
|
1740
|
+
get safeToRetry(): boolean;
|
|
1741
|
+
}
|
|
1742
|
+
/**
|
|
1743
|
+
* Pull the signature out of a relay error body.
|
|
1744
|
+
*
|
|
1745
|
+
* The relay's `SubmissionOutcomeUnknown` response is a 503 whose JSON carries
|
|
1746
|
+
* `{"code":"submission_outcome_unknown","retryable":true,"signature":"<sig>"}` (relay
|
|
1747
|
+
* `src/error.rs`). The SDK used to funnel that body into a generic retry and then throw an error
|
|
1748
|
+
* built from a LATER attempt's message, dropping the one field the user needs.
|
|
1749
|
+
*/
|
|
1750
|
+
declare function parseRelayErrorSignature(responseText: string): string | null;
|
|
1751
|
+
/** True when a relay error body is the relay's own "I do not know if this landed" report. */
|
|
1752
|
+
declare function isSubmissionOutcomeUnknownResponse(responseText: string): boolean;
|
|
1675
1753
|
/**
|
|
1676
1754
|
* Classify a relay error (response body text + HTTP status) into one of the
|
|
1677
1755
|
* structured error types above, or fall back to RelayInternalError.
|
|
@@ -2052,6 +2130,141 @@ declare function verifyUtxos(utxos: Utxo[], connection: Connection, programId: P
|
|
|
2052
2130
|
*/
|
|
2053
2131
|
declare function preflightNullifiers(utxos: Utxo[], connection: Connection, programId: PublicKey, commitment?: "processed" | "confirmed" | "finalized"): Promise<void>;
|
|
2054
2132
|
|
|
2133
|
+
/**
|
|
2134
|
+
* Settlement verification — decide from CHAIN STATE whether a submission landed.
|
|
2135
|
+
*
|
|
2136
|
+
* Why this module exists (adversarial campaign, tier 2):
|
|
2137
|
+
*
|
|
2138
|
+
* X-S-01B A hostile relay answered `/transact` with a well-formed success body carrying a
|
|
2139
|
+
* phantom signature and commitment indices [4242, 4243]. The SDK returned SUCCESS.
|
|
2140
|
+
* Real-RPC `getSignatureStatuses(searchTransactionHistory)` -> null, and BOTH input
|
|
2141
|
+
* nullifier PDAs read `exists=false`. Nothing had landed.
|
|
2142
|
+
* X-S-03a A hostile RPC swallowed `sendTransaction` and fabricated
|
|
2143
|
+
* `{err: null, confirmationStatus: "finalized"}`. `confirmTransaction` was satisfied and
|
|
2144
|
+
* the SDK returned a signature for a transaction that was never forwarded. (A control
|
|
2145
|
+
* deposit landed honestly through the same proxy, so account reads were NOT tampered
|
|
2146
|
+
* with — only the submission and its status were.)
|
|
2147
|
+
*
|
|
2148
|
+
* Both defects have the same shape: the SDK reported success on the strength of what its
|
|
2149
|
+
* counterparty SAID, never on the strength of what the chain SHOWS.
|
|
2150
|
+
*
|
|
2151
|
+
* The ground truth used here is the one the campaign itself used to prove nothing landed: the
|
|
2152
|
+
* input NULLIFIER PDAs. Their addresses are derived locally from the proof's own public inputs
|
|
2153
|
+
* (`["nullifier", pool, nullifier]`), and the program creates one per non-zero input nullifier on
|
|
2154
|
+
* every landed transact — including deposits, whose padding slots still emit real nullifiers
|
|
2155
|
+
* (transaction.circom: "the slot still emits a real nullifier ... and consumed on-chain";
|
|
2156
|
+
* shield-pool/src/instructions/transact.rs skips only all-zero nullifiers).
|
|
2157
|
+
*
|
|
2158
|
+
* Nullifier presence is strictly stronger evidence than a signature status:
|
|
2159
|
+
*
|
|
2160
|
+
* - it is ACCOUNT STATE, so it survives an RPC that has no signature history for the slot
|
|
2161
|
+
* (Surfpool forks, pruned nodes, a load-balanced endpoint that missed the write); and
|
|
2162
|
+
* - the nullifier and the output commitments are fields of the SAME proof's public inputs, so
|
|
2163
|
+
* the only transaction that can create these PDAs is one that also appended exactly our
|
|
2164
|
+
* output commitments. "Nullifiers exist" therefore means "our outputs are in the tree".
|
|
2165
|
+
*
|
|
2166
|
+
* The signature status is still checked, because it is the only signal that can prove a
|
|
2167
|
+
* DEFINITIVE FAILURE (`err != null`) and because a signature the RPC has never heard of is the
|
|
2168
|
+
* fingerprint of a phantom. It is corroboration, never the sole basis for success.
|
|
2169
|
+
*
|
|
2170
|
+
* LIMIT, stated plainly: a single RPC endpoint that lies about ACCOUNT READS as well cannot be
|
|
2171
|
+
* caught by any single-endpoint client, and this module does not pretend to. It closes the two
|
|
2172
|
+
* observed attacks — a lying relay, and an RPC that lies only about submission/status — and it
|
|
2173
|
+
* downgrades every unproven outcome to an explicit, signature-carrying "unknown" instead of
|
|
2174
|
+
* silently reporting success.
|
|
2175
|
+
*/
|
|
2176
|
+
|
|
2177
|
+
/**
|
|
2178
|
+
* The RPC surface settlement verification needs, as a structural type rather than a hard
|
|
2179
|
+
* dependency on `Connection`. A real `web3.js` Connection satisfies it; so does a test stub, which
|
|
2180
|
+
* is what lets the phantom-relay / lying-RPC regressions be reproduced without a validator.
|
|
2181
|
+
*/
|
|
2182
|
+
interface SettlementConnection {
|
|
2183
|
+
getSignatureStatuses(signatures: string[], config?: {
|
|
2184
|
+
searchTransactionHistory?: boolean;
|
|
2185
|
+
}): Promise<{
|
|
2186
|
+
value: Array<{
|
|
2187
|
+
err: unknown | null;
|
|
2188
|
+
confirmationStatus?: string | null;
|
|
2189
|
+
} | null>;
|
|
2190
|
+
}>;
|
|
2191
|
+
getMultipleAccountsInfo(publicKeys: PublicKey[], commitmentOrConfig?: any): Promise<Array<unknown | null>>;
|
|
2192
|
+
}
|
|
2193
|
+
/**
|
|
2194
|
+
* What the chain says about a submission.
|
|
2195
|
+
*
|
|
2196
|
+
* - `landed` the input nullifier PDAs exist: the proof was consumed, so its output
|
|
2197
|
+
* commitments are in the tree. This is the ONLY status that may be reported as
|
|
2198
|
+
* success.
|
|
2199
|
+
* - `failed` the signature is on chain and carries an execution error. Nothing was applied;
|
|
2200
|
+
* the inputs are still spendable.
|
|
2201
|
+
* - `not-landed` no nullifier PDA exists and the signature (if any) is unknown to the RPC even
|
|
2202
|
+
* with `searchTransactionHistory`. The phantom-signature fingerprint.
|
|
2203
|
+
* - `unknown` the evidence is contradictory or incomplete — most importantly the case where
|
|
2204
|
+
* a status claims confirmation while no nullifier PDA exists. NEVER report this
|
|
2205
|
+
* as success and NEVER silently retry it: the caller must surface the signature.
|
|
2206
|
+
*/
|
|
2207
|
+
type SettlementStatus = "landed" | "failed" | "not-landed" | "unknown";
|
|
2208
|
+
interface SettlementVerdict {
|
|
2209
|
+
status: SettlementStatus;
|
|
2210
|
+
/** The signature that was checked, when one was supplied and syntactically usable. */
|
|
2211
|
+
signature: string | null;
|
|
2212
|
+
/** Human-readable statement of what was and was not proven. Safe to put in an error message. */
|
|
2213
|
+
reason: string;
|
|
2214
|
+
/** True when every checkable input nullifier PDA was present on chain. */
|
|
2215
|
+
nullifiersSpent: boolean;
|
|
2216
|
+
/** What `getSignatureStatuses` reported for `signature`. */
|
|
2217
|
+
signatureStatus: "finalized" | "confirmed" | "processed" | "absent" | "error" | "unchecked";
|
|
2218
|
+
}
|
|
2219
|
+
interface ConfirmSettlementParams {
|
|
2220
|
+
connection: SettlementConnection;
|
|
2221
|
+
programId: PublicKey;
|
|
2222
|
+
mint: PublicKey;
|
|
2223
|
+
/** The proof's public input nullifiers. All-zero entries are padding and are skipped. */
|
|
2224
|
+
inputNullifiers: bigint[];
|
|
2225
|
+
/** The signature the counterparty reported, if it reported one. */
|
|
2226
|
+
signature?: string | null;
|
|
2227
|
+
/** Total time to wait for evidence to appear before giving a verdict. */
|
|
2228
|
+
timeoutMs?: number;
|
|
2229
|
+
pollIntervalMs?: number;
|
|
2230
|
+
onProgress?: (status: string) => void;
|
|
2231
|
+
}
|
|
2232
|
+
declare function isPlausibleSignature(value: string | null | undefined): value is string;
|
|
2233
|
+
/**
|
|
2234
|
+
* Derive the nullifier PDA for each non-zero input nullifier. Zero entries are the program's own
|
|
2235
|
+
* padding sentinel (`transact.rs`: "Skip zero nullifiers (padding inputs)") and create no account.
|
|
2236
|
+
*/
|
|
2237
|
+
declare function deriveInputNullifierPdas(programId: PublicKey, mint: PublicKey, inputNullifiers: bigint[]): PublicKey[];
|
|
2238
|
+
/**
|
|
2239
|
+
* Establish, from chain state, whether a submission landed.
|
|
2240
|
+
*
|
|
2241
|
+
* Polls until either side of the question is answered or `timeoutMs` elapses. Every RPC error is
|
|
2242
|
+
* absorbed into the verdict rather than thrown: "I could not check" is `unknown`, which the caller
|
|
2243
|
+
* must surface — it is never success.
|
|
2244
|
+
*/
|
|
2245
|
+
declare function confirmTransactSettlement(params: ConfirmSettlementParams): Promise<SettlementVerdict>;
|
|
2246
|
+
/**
|
|
2247
|
+
* Gate a DIRECT (self-signed) submission on chain state — X-S-03a.
|
|
2248
|
+
*
|
|
2249
|
+
* `connection.confirmTransaction` is not proof of anything: the campaign's proxy swallowed
|
|
2250
|
+
* `sendTransaction` and answered the follow-up status poll with a fabricated
|
|
2251
|
+
* `{err: null, confirmationStatus: "finalized"}`, and the SDK handed the caller a signature for a
|
|
2252
|
+
* transaction that was never forwarded. A control deposit landed honestly through the same proxy,
|
|
2253
|
+
* so this was not broken plumbing — it was the client believing a status field.
|
|
2254
|
+
*
|
|
2255
|
+
* Throws `SettlementVerificationError` unless the input nullifier PDAs prove the transaction was
|
|
2256
|
+
* applied. The signature is always carried on the error so the caller can look it up.
|
|
2257
|
+
*/
|
|
2258
|
+
declare function assertDirectSubmissionLanded(params: {
|
|
2259
|
+
connection: SettlementConnection;
|
|
2260
|
+
programId: PublicKey;
|
|
2261
|
+
mint: PublicKey;
|
|
2262
|
+
nullifiers: Array<Uint8Array | bigint>;
|
|
2263
|
+
signature: string;
|
|
2264
|
+
timeoutMs?: number;
|
|
2265
|
+
onProgress?: (status: string) => void;
|
|
2266
|
+
}): Promise<SettlementVerdict>;
|
|
2267
|
+
|
|
2055
2268
|
/**
|
|
2056
2269
|
* Structured Logger for Cloak SDK
|
|
2057
2270
|
*
|
|
@@ -2510,6 +2723,111 @@ declare function getNullifierPDA(poolPubkey: PublicKey, nullifier: Uint8Array |
|
|
|
2510
2723
|
* @returns [PublicKey, bump] - The swap state PDA and its bump seed
|
|
2511
2724
|
*/
|
|
2512
2725
|
declare function getSwapStatePDA(poolPubkey: PublicKey, nullifier: Uint8Array | Buffer, programId?: PublicKey): [PublicKey, number];
|
|
2726
|
+
/**
|
|
2727
|
+
* H-04: derive the per-pool RefundLedger PDA (conservation counter).
|
|
2728
|
+
*
|
|
2729
|
+
* Seeds: ["refund_ledger", pool_mint]
|
|
2730
|
+
*/
|
|
2731
|
+
declare function getRefundLedgerPDA(mint?: PublicKey, programId?: PublicKey): [PublicKey, number];
|
|
2732
|
+
/**
|
|
2733
|
+
* H-04: derive the one-time RefundClaim PDA (replay guard for a voucher).
|
|
2734
|
+
*
|
|
2735
|
+
* Seeds: ["refund_claim", claim_id]
|
|
2736
|
+
*/
|
|
2737
|
+
declare function getRefundClaimPDA(claimId: Uint8Array | Buffer, programId?: PublicKey): [PublicKey, number];
|
|
2738
|
+
/**
|
|
2739
|
+
* M-07/H-04: derive the per-pool PoolAuthorityConfig PDA (holds the
|
|
2740
|
+
* withdraw-authorizer the refund voucher must be signed by).
|
|
2741
|
+
*
|
|
2742
|
+
* Seeds: ["pool_authority", pool_mint]
|
|
2743
|
+
*/
|
|
2744
|
+
declare function getPoolAuthorityConfigPDA(mint?: PublicKey, programId?: PublicKey): [PublicKey, number];
|
|
2745
|
+
/**
|
|
2746
|
+
* Registry PDA the relay's recipient-delivery carrier (CLKD1) touches so the carrier transaction is
|
|
2747
|
+
* enumerable via `getSignaturesForAddress`.
|
|
2748
|
+
*
|
|
2749
|
+
* Seed: ["cloak_delivery_registry"] — `DELIVERY_REGISTRY_SEED` in
|
|
2750
|
+
* `services/relay/src/solana/mod.rs::emit_recipient_delivery_carrier`. Mint-independent, exactly
|
|
2751
|
+
* like the chain-note registry: one registry per program, not per pool.
|
|
2752
|
+
*/
|
|
2753
|
+
declare function getDeliveryRegistryPDA(programId?: PublicKey): PublicKey;
|
|
2754
|
+
/**
|
|
2755
|
+
* Registry PDA the relay's compliance chain-note carrier (CLK1) touches.
|
|
2756
|
+
*
|
|
2757
|
+
* Seed: ["cloak_chain_note_registry"] — `CHAIN_NOTE_REGISTRY_SEED` in
|
|
2758
|
+
* `services/relay/src/solana/mod.rs::emit_chain_note_carrier`.
|
|
2759
|
+
*/
|
|
2760
|
+
declare function getChainNoteRegistryPDA(programId?: PublicKey): PublicKey;
|
|
2761
|
+
|
|
2762
|
+
/**
|
|
2763
|
+
* H-04 — claim-based timeout refund (SDK).
|
|
2764
|
+
*
|
|
2765
|
+
* When a private swap times out, `CloseSwapState` returns the parked principal
|
|
2766
|
+
* to the pool and credits the on-chain `RefundLedger`. The user later reclaims
|
|
2767
|
+
* that principal as a FRESH shielded note via `ClaimRefund`, gated by a
|
|
2768
|
+
* withdraw-authorizer voucher (signed by the relay) and a deposit-shape Groth16
|
|
2769
|
+
* proof — with no on-chain link back to the original swap.
|
|
2770
|
+
*
|
|
2771
|
+
* This module:
|
|
2772
|
+
* 1. asks the relay to sign the 81-byte refund voucher for `claim_id`,
|
|
2773
|
+
* 2. generates a deposit-shape proof (publicAmount = +refund_amount, zero
|
|
2774
|
+
* inputs, one fresh output note `C_r`) bound to the live merkle root,
|
|
2775
|
+
* 3. builds and submits the `[ed25519 voucher][CU][ClaimRefund]` transaction,
|
|
2776
|
+
* 4. returns the fresh refund UTXO for the caller to store/spend later.
|
|
2777
|
+
*
|
|
2778
|
+
* The proof construction is byte-for-byte the same as the standalone
|
|
2779
|
+
* `audit-tests/h-04/gen-claim-proof.cjs` generator that the program-side soak
|
|
2780
|
+
* verified, so the proof verifies against the deployed transaction vkey.
|
|
2781
|
+
*/
|
|
2782
|
+
|
|
2783
|
+
/** A relay-signed refund voucher, as returned by `POST /refund-voucher`. */
|
|
2784
|
+
interface RefundVoucher {
|
|
2785
|
+
/** base64 Ed25519 signature over the 81-byte message. */
|
|
2786
|
+
signature: string;
|
|
2787
|
+
/** hex 81-byte voucher message. */
|
|
2788
|
+
message: string;
|
|
2789
|
+
/** base58 signer (pool withdraw-authorizer). */
|
|
2790
|
+
signer_pubkey: string;
|
|
2791
|
+
/** expiry slot bound into the voucher. */
|
|
2792
|
+
expiry_slot?: number;
|
|
2793
|
+
}
|
|
2794
|
+
interface ClaimTimeoutRefundParams {
|
|
2795
|
+
/** Refund principal in lamports (must equal the closed swap's parked amount). */
|
|
2796
|
+
refundAmount: bigint;
|
|
2797
|
+
/** Pool mint; ClaimRefund is SOL-pool only (defaults to WSOL sentinel). */
|
|
2798
|
+
poolMint?: PublicKey;
|
|
2799
|
+
/** 32-byte claim id = H(user secret). Random if omitted. */
|
|
2800
|
+
claimId?: Uint8Array;
|
|
2801
|
+
/** Supply a pre-fetched voucher to skip the relay round-trip. */
|
|
2802
|
+
voucher?: RefundVoucher;
|
|
2803
|
+
}
|
|
2804
|
+
interface ClaimTimeoutRefundOptions {
|
|
2805
|
+
connection: Connection;
|
|
2806
|
+
programId: PublicKey;
|
|
2807
|
+
/** Relay base URL (used to fetch the voucher when not supplied). */
|
|
2808
|
+
relayUrl?: string;
|
|
2809
|
+
/** Pays fees + signs the claim tx (relay-submitted is a documented follow-up). */
|
|
2810
|
+
payerKeypair: Keypair;
|
|
2811
|
+
onProgress?: (message: string) => void;
|
|
2812
|
+
}
|
|
2813
|
+
interface ClaimTimeoutRefundResult {
|
|
2814
|
+
/** Submitted ClaimRefund transaction signature. */
|
|
2815
|
+
signature: string;
|
|
2816
|
+
/** The fresh refund note created by the claim — store this to spend later. */
|
|
2817
|
+
refundUtxo: Utxo;
|
|
2818
|
+
/** The claim id consumed (hex). */
|
|
2819
|
+
claimId: string;
|
|
2820
|
+
/** The merkle leaf index where the refund note `C_r` landed. */
|
|
2821
|
+
leafIndex: number;
|
|
2822
|
+
}
|
|
2823
|
+
/**
|
|
2824
|
+
* Reclaim a timed-out swap's principal as a fresh shielded note via ClaimRefund.
|
|
2825
|
+
*
|
|
2826
|
+
* Front-running note: this self-submits the claim. The production default is
|
|
2827
|
+
* relay submission (decision #3) to avoid mempool exposure of the voucher; a
|
|
2828
|
+
* relay `/claim-refund` endpoint is the documented follow-up.
|
|
2829
|
+
*/
|
|
2830
|
+
declare function claimTimeoutRefund(params: ClaimTimeoutRefundParams, options: ClaimTimeoutRefundOptions): Promise<ClaimTimeoutRefundResult>;
|
|
2513
2831
|
|
|
2514
2832
|
/**
|
|
2515
2833
|
* On-chain Merkle proof computation
|
|
@@ -2625,7 +2943,12 @@ declare function buildMerkleTreeFromRelay(relayUrl: string, options?: {
|
|
|
2625
2943
|
maxRetries?: number;
|
|
2626
2944
|
waitForIndex?: number;
|
|
2627
2945
|
}): Promise<MerkleTree>;
|
|
2628
|
-
declare function buildMerkleTreeFromChain(connection: Connection, programId: PublicKey, merkleTree: PublicKey, onProgress?: (message: string) => void
|
|
2946
|
+
declare function buildMerkleTreeFromChain(connection: Connection, programId: PublicKey, merkleTree: PublicKey, onProgress?: (message: string) => void,
|
|
2947
|
+
/**
|
|
2948
|
+
* The pool mint this tree belongs to. Required in practice: without it a `CloseSwapState` refund
|
|
2949
|
+
* leaf cannot be attributed to a tree, and reconstruction fails closed rather than guess.
|
|
2950
|
+
*/
|
|
2951
|
+
mint?: PublicKey): Promise<MerkleTree>;
|
|
2629
2952
|
/**
|
|
2630
2953
|
* Pre-flight root validation
|
|
2631
2954
|
*
|
|
@@ -2679,288 +3002,186 @@ declare function preflightCheck(relayUrl: string, rootHex: string): Promise<{
|
|
|
2679
3002
|
}>;
|
|
2680
3003
|
|
|
2681
3004
|
/**
|
|
2682
|
-
*
|
|
3005
|
+
* The SDK's host-environment predicates, in one place (X-S-02C).
|
|
2683
3006
|
*
|
|
2684
|
-
*
|
|
2685
|
-
*
|
|
3007
|
+
* ── The defect this closes ────────────────────────────────────────────────────────────────────
|
|
3008
|
+
* The merkle-from-chain rebuild was guarded by TWO independently written predicates that disagreed
|
|
3009
|
+
* about React Native:
|
|
2686
3010
|
*
|
|
2687
|
-
*
|
|
2688
|
-
*
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
*
|
|
2693
|
-
*
|
|
2694
|
-
*/
|
|
2695
|
-
declare const DEFAULT_CIRCUITS_URL = "https://storage.googleapis.com/cloak-circuits/circuits/0.1.0";
|
|
2696
|
-
interface WithdrawRegularInputs {
|
|
2697
|
-
root: bigint;
|
|
2698
|
-
nullifier: bigint;
|
|
2699
|
-
outputs_hash: bigint;
|
|
2700
|
-
public_amount: bigint;
|
|
2701
|
-
amount: bigint;
|
|
2702
|
-
leaf_index: bigint;
|
|
2703
|
-
sk: [bigint, bigint];
|
|
2704
|
-
r: [bigint, bigint];
|
|
2705
|
-
pathElements: bigint[];
|
|
2706
|
-
pathIndices: number[];
|
|
2707
|
-
num_outputs: number;
|
|
2708
|
-
out_addr: bigint[][];
|
|
2709
|
-
out_amount: bigint[];
|
|
2710
|
-
out_flags: number[];
|
|
2711
|
-
var_fee: bigint;
|
|
2712
|
-
rem: bigint;
|
|
2713
|
-
}
|
|
2714
|
-
interface WithdrawSwapInputs {
|
|
2715
|
-
sk_spend: bigint;
|
|
2716
|
-
r: bigint;
|
|
2717
|
-
amount: bigint;
|
|
2718
|
-
leaf_index: bigint;
|
|
2719
|
-
path_elements: bigint[];
|
|
2720
|
-
path_indices: number[];
|
|
2721
|
-
root: bigint;
|
|
2722
|
-
nullifier: bigint;
|
|
2723
|
-
outputs_hash: bigint;
|
|
2724
|
-
public_amount: bigint;
|
|
2725
|
-
input_mint: bigint[];
|
|
2726
|
-
output_mint: bigint[];
|
|
2727
|
-
recipient_ata: bigint[];
|
|
2728
|
-
min_output_amount: bigint;
|
|
2729
|
-
var_fee: bigint;
|
|
2730
|
-
rem: bigint;
|
|
2731
|
-
}
|
|
2732
|
-
interface ProofResult {
|
|
2733
|
-
proof: Groth16Proof;
|
|
2734
|
-
publicSignals: string[];
|
|
2735
|
-
proofBytes: Uint8Array;
|
|
2736
|
-
publicInputsBytes: Uint8Array;
|
|
2737
|
-
}
|
|
2738
|
-
/**
|
|
2739
|
-
* Generate Groth16 proof for regular withdrawal using Circom WASM
|
|
3011
|
+
* - the GATE in `core/transact.ts` — `!IS_BROWSER && isMerkleClass && relayUrl`, where
|
|
3012
|
+
* `IS_BROWSER` was `typeof window !== "undefined" || typeof globalThis.document !== "undefined"`,
|
|
3013
|
+
* with NO React-Native carve-out. React Native defines `window`, so RN read as a browser and the
|
|
3014
|
+
* last-resort chain replay was silently skipped there.
|
|
3015
|
+
* - the BUILDER it guards — `utils/relay-client.ts::buildMerkleTreeFromChain`, whose own
|
|
3016
|
+
* `isBrowser()` short-circuits on `navigator.product === "ReactNative"` and therefore explicitly
|
|
3017
|
+
* PERMITS React Native to rebuild from chain.
|
|
2740
3018
|
*
|
|
2741
|
-
*
|
|
3019
|
+
* So one half of the same mechanism classified RN as a browser and the other half classified it as
|
|
3020
|
+
* not-a-browser.
|
|
2742
3021
|
*
|
|
2743
|
-
*
|
|
2744
|
-
*
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
*
|
|
3022
|
+
* ── The decision: the BUILDER is right, and the gate was wrong ────────────────────────────────
|
|
3023
|
+
* The browser fails closed for a stated reason — a full signature-history scan is too slow and too
|
|
3024
|
+
* unreliable on a browser main thread, and a browser always has a reachable relay whose tree it can
|
|
3025
|
+
* use instead. Neither half of that reasoning holds for React Native: it is not a browser, it has no
|
|
3026
|
+
* DOM, it is not running on a page's main thread, and on a local node it has no relay-tree fallback
|
|
3027
|
+
* at all. Closing the gate against RN removed its ONLY recovery path from a drifted relay tree —
|
|
3028
|
+
* precisely the class of failure the fallback exists for — while leaving the builder it calls happy
|
|
3029
|
+
* to serve it.
|
|
2749
3030
|
*
|
|
2750
|
-
*
|
|
3031
|
+
* The alternative reading (make the builder refuse RN too, so the two agree by failing closed
|
|
3032
|
+
* everywhere) was rejected: it agrees by breaking the Cloak mobile wallet, whose merkle path depends
|
|
3033
|
+
* on the carve-out, and it discards a documented, deliberate decision in favour of an undocumented
|
|
3034
|
+
* accident. The gate's own comment never mentions React Native — it argues about SSR — which is what
|
|
3035
|
+
* an accident looks like.
|
|
2751
3036
|
*
|
|
2752
|
-
*
|
|
2753
|
-
*
|
|
3037
|
+
* ── Two predicates, deliberately, and the difference is not RN ────────────────────────────────
|
|
3038
|
+
* `isBrowser()` is a HARD REFUSAL — "this host cannot do it at all" — so it demands a real DOM
|
|
3039
|
+
* (`window` AND `document`).
|
|
3040
|
+
* `isBrowserLike()` is a CONSERVATIVE OPT-OUT — "don't start something slow here" — so `window` OR
|
|
3041
|
+
* `document` is enough, which keeps an SSR host that polyfills only `document` from being mistaken
|
|
3042
|
+
* for Node and made to attempt a chain replay.
|
|
3043
|
+
*
|
|
3044
|
+
* They differ on SSR on purpose. They must NEVER differ on React Native again, which is why both are
|
|
3045
|
+
* built from the single `isReactNative()` below.
|
|
3046
|
+
*
|
|
3047
|
+
* All three are functions, not module-scope constants: a constant is frozen at import time, so any
|
|
3048
|
+
* host that installs its globals after the bundle loads — and any test that wants to pin the three
|
|
3049
|
+
* environments — reads a stale answer.
|
|
2754
3050
|
*/
|
|
2755
|
-
declare function generateWithdrawSwapProof(inputs: WithdrawSwapInputs, circuitsPath: string): Promise<ProofResult>;
|
|
2756
3051
|
/**
|
|
2757
|
-
*
|
|
3052
|
+
* True on React Native. `navigator.product === "ReactNative"` is the canonical flag and is what
|
|
3053
|
+
* `utils/proof-generation.ts` has always used.
|
|
2758
3054
|
*/
|
|
2759
|
-
declare function
|
|
3055
|
+
declare function isReactNative(): boolean;
|
|
2760
3056
|
/**
|
|
2761
|
-
*
|
|
3057
|
+
* True only on a real browser: a DOM host with both `window` and `document`, and not React Native.
|
|
3058
|
+
*
|
|
3059
|
+
* Use for HARD refusals — work this host genuinely cannot perform.
|
|
2762
3060
|
*/
|
|
2763
|
-
declare function
|
|
3061
|
+
declare function isBrowser(): boolean;
|
|
2764
3062
|
/**
|
|
2765
|
-
*
|
|
3063
|
+
* True on a browser OR on a DOM-ish host such as SSR that defines only one of `window`/`document`,
|
|
3064
|
+
* and false on React Native and on Node.
|
|
2766
3065
|
*
|
|
2767
|
-
*
|
|
3066
|
+
* Use for CONSERVATIVE opt-outs — expensive work that should not be started speculatively.
|
|
2768
3067
|
*/
|
|
2769
|
-
declare
|
|
2770
|
-
|
|
2771
|
-
withdraw_regular_zkey: string;
|
|
2772
|
-
withdraw_swap_wasm: string;
|
|
2773
|
-
withdraw_swap_zkey: string;
|
|
2774
|
-
};
|
|
3068
|
+
declare function isBrowserLike(): boolean;
|
|
3069
|
+
|
|
2775
3070
|
/**
|
|
2776
|
-
* Circuit
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
3071
|
+
* Circuit artifact releases — the single source of truth for
|
|
3072
|
+
* "which bundle version" and "which bytes are that bundle".
|
|
3073
|
+
*
|
|
3074
|
+
* Why this file exists
|
|
3075
|
+
* -------------------
|
|
3076
|
+
* The version segment of the artifact base URL and the pinned SHA-256 digests
|
|
3077
|
+
* used to be written out by hand in two unrelated places
|
|
3078
|
+
* (`utils/proof-generation.ts` held `.../circuits/0.1.0` plus a digest table,
|
|
3079
|
+
* `core/transact.ts` held its own copy of the same URL literal). They drifted:
|
|
3080
|
+
* the default base pointed at the `0.1.0` bundle while the pinned `transaction`
|
|
3081
|
+
* digests were the `0.2.0` trusted-setup ceremony output, so every default-config
|
|
3082
|
+
* proof died on a digest mismatch several megabytes into proof generation.
|
|
3083
|
+
*
|
|
3084
|
+
* Here a bundle is declared once, as a version plus the digests of the artifacts
|
|
3085
|
+
* that version contains, and the base URL is *derived* from that version by
|
|
3086
|
+
* {@link defineCircuitBundle}. There is no way to write a URL whose version
|
|
3087
|
+
* segment disagrees with the digests next to it:
|
|
3088
|
+
*
|
|
3089
|
+
* - **compile time** — `baseUrl` is typed `` `${string}/circuits/${V}` ``, where
|
|
3090
|
+
* `V` is the bundle's own `version` literal, and {@link BUNDLE_BY_CIRCUIT} is
|
|
3091
|
+
* an exhaustive `Record<CircuitName, …>`, so a new circuit with no bundle, or
|
|
3092
|
+
* an assertion about a version the bundle does not carry, fails `tsc`.
|
|
3093
|
+
* - **startup** — {@link assertCircuitReleaseConsistency} runs at module load
|
|
3094
|
+
* and throws if a bundle's digests are malformed, if a bundle does not pin the
|
|
3095
|
+
* circuit that maps to it, or if a hand-edited base URL stops ending in its own
|
|
3096
|
+
* version. That is an import-time failure, not a failure deep inside
|
|
3097
|
+
* `groth16.fullProve`.
|
|
3098
|
+
*
|
|
3099
|
+
* Publication is deliberately *not* assumed. A bundle whose bytes this SDK has
|
|
3100
|
+
* not verified at a public location carries `baseUrl: null`; callers must point
|
|
3101
|
+
* the SDK at a location themselves (`setCircuitsPath`). Nothing here silently
|
|
3102
|
+
* defaults to a URL that has not been checked against the digests below.
|
|
3103
|
+
*/
|
|
3104
|
+
/** Circuits whose artifacts carry a pinned SHA-256 digest in this SDK. */
|
|
3105
|
+
type CircuitName = 'withdraw_regular' | 'withdraw_swap' | 'transaction';
|
|
3106
|
+
/** Version of the trusted-setup ceremony bundle that froze the `transaction` circuit. */
|
|
3107
|
+
declare const TRANSACTION_CIRCUITS_VERSION = "0.2.0";
|
|
3108
|
+
|
|
2794
3109
|
/**
|
|
2795
|
-
*
|
|
3110
|
+
* UTXO Transaction Methods
|
|
2796
3111
|
*
|
|
2797
|
-
* This
|
|
2798
|
-
*
|
|
3112
|
+
* This module provides high-level methods for UTXO transactions:
|
|
3113
|
+
* - transact(): Core UTXO transaction
|
|
3114
|
+
* - transfer(): Shield-to-shield transfer
|
|
3115
|
+
* - partialWithdraw(): Withdraw with change
|
|
3116
|
+
*/
|
|
3117
|
+
|
|
3118
|
+
/**
|
|
3119
|
+
* Can this host attempt the last-resort "rebuild the merkle tree from chain" recovery?
|
|
2799
3120
|
*
|
|
2800
|
-
*
|
|
2801
|
-
*
|
|
3121
|
+
* X-S-02C: this gate and the builder it guards (`buildMerkleTreeFromChain`, which refuses only on a
|
|
3122
|
+
* real browser and EXPLICITLY permits React Native) used to be written separately and disagreed —
|
|
3123
|
+
* RN defines `window`, so the old inline `typeof window !== "undefined" || typeof document !==
|
|
3124
|
+
* "undefined"` classified RN as a browser and skipped the rebuild, removing RN's only recovery path
|
|
3125
|
+
* from a drifted relay tree while the builder was perfectly willing to serve it. Both now come from
|
|
3126
|
+
* `utils/environment`, so they cannot disagree about React Native again.
|
|
2802
3127
|
*
|
|
2803
|
-
*
|
|
2804
|
-
*
|
|
2805
|
-
*
|
|
3128
|
+
* The SSR conservatism is kept: `isBrowserLike()` is true when EITHER `window` or `document` exists,
|
|
3129
|
+
* because an SSR host that polyfills only `document` must not be mistaken for Node and made to run a
|
|
3130
|
+
* full signature-history scan.
|
|
2806
3131
|
*
|
|
2807
|
-
*
|
|
2808
|
-
*
|
|
2809
|
-
* const result = await verifyCircuitIntegrity('https://storage.googleapis.com/cloak-circuits/circuits/0.1.0', 'withdraw_regular');
|
|
2810
|
-
* if (!result.valid) {
|
|
2811
|
-
* console.error('Circuit verification failed:', result.error);
|
|
2812
|
-
* // Don't proceed with proof generation!
|
|
2813
|
-
* }
|
|
2814
|
-
* ```
|
|
3132
|
+
* A function, not a module-scope constant: the constant was frozen at import time, so a host that
|
|
3133
|
+
* installs its globals after the bundle loads read a stale answer.
|
|
2815
3134
|
*/
|
|
2816
|
-
declare function
|
|
3135
|
+
declare function canRebuildMerkleTreeFromChain(): boolean;
|
|
3136
|
+
|
|
2817
3137
|
/**
|
|
2818
|
-
*
|
|
2819
|
-
*
|
|
2820
|
-
* Call this at SDK initialization to ensure circuits are valid.
|
|
3138
|
+
* Base the ceremony-frozen `transaction` artifacts are fetched from by default.
|
|
2821
3139
|
*
|
|
2822
|
-
* @
|
|
2823
|
-
*
|
|
3140
|
+
* Derived from {@link TRANSACTION_CIRCUIT_BUNDLE} — the same record that pins the
|
|
3141
|
+
* digests — so the version in the URL and the digests checked against it cannot
|
|
3142
|
+
* drift apart. It is `null` while this SDK build pins no location whose bytes
|
|
3143
|
+
* were verified to hash to those digests; that makes an unconfigured SDK a
|
|
3144
|
+
* `tsc` error at the call site (`setCircuitsPath(DEFAULT_TRANSACTION_CIRCUITS_URL)`
|
|
3145
|
+
* does not type-check against `string`) and an immediate, explanatory throw at
|
|
3146
|
+
* runtime, instead of a digest mismatch ~22 MB into proof generation.
|
|
2824
3147
|
*/
|
|
2825
|
-
declare
|
|
3148
|
+
declare const DEFAULT_TRANSACTION_CIRCUITS_URL: string | null;
|
|
2826
3149
|
|
|
2827
3150
|
/**
|
|
2828
|
-
*
|
|
2829
|
-
*
|
|
2830
|
-
* Utility for persisting pending deposit/withdrawal operations in browser storage.
|
|
2831
|
-
* This enables recovery if the browser crashes or user navigates away mid-operation.
|
|
3151
|
+
* Set circuits base path: local directory containing `transaction_js/` and `transaction_final.zkey`,
|
|
3152
|
+
* or an `http(s)` base URL to those artifacts (loaded into memory once per process).
|
|
2832
3153
|
*
|
|
2833
|
-
*
|
|
2834
|
-
*
|
|
2835
|
-
*
|
|
3154
|
+
* No cache invalidation is needed here: verified artifact buffers are memoised
|
|
3155
|
+
* per base inside `proof-generation.ts`, so a new base loads and re-verifies its
|
|
3156
|
+
* own bytes.
|
|
2836
3157
|
*/
|
|
2837
|
-
|
|
3158
|
+
declare function setCircuitsPath(next: string): void;
|
|
2838
3159
|
/**
|
|
2839
|
-
*
|
|
3160
|
+
* Get the current circuits path, or `null` when none is configured and this SDK
|
|
3161
|
+
* build pins no verified default (see `DEFAULT_TRANSACTION_CIRCUITS_URL`).
|
|
2840
3162
|
*/
|
|
2841
|
-
|
|
2842
|
-
/** The note (contains spending secrets!) */
|
|
2843
|
-
note: CloakNote;
|
|
2844
|
-
/** When the deposit was initiated */
|
|
2845
|
-
startedAt: number;
|
|
2846
|
-
/** Transaction signature if available */
|
|
2847
|
-
txSignature?: string;
|
|
2848
|
-
/** Status of the deposit */
|
|
2849
|
-
status: "pending" | "tx_sent" | "confirmed" | "failed";
|
|
2850
|
-
/** Error message if failed */
|
|
2851
|
-
error?: string;
|
|
2852
|
-
}
|
|
3163
|
+
declare function getCircuitsPath(): string | null;
|
|
2853
3164
|
/**
|
|
2854
|
-
*
|
|
3165
|
+
* Resolve a base to prove from: an explicit argument, else `CLOAK_CIRCUITS_PATH`
|
|
3166
|
+
* / `CLOAK_CIRCUITS` from the environment, else this build's pinned default.
|
|
3167
|
+
*
|
|
3168
|
+
* Use this instead of writing an artifact URL out by hand — a hand-written URL
|
|
3169
|
+
* is exactly how the base's version segment came to disagree with the digests
|
|
3170
|
+
* this SDK checks against. Throws an explanatory error (naming the expected
|
|
3171
|
+
* bundle version and both expected digests) when nothing resolves.
|
|
2855
3172
|
*/
|
|
2856
|
-
|
|
2857
|
-
/** The relay request ID (for resumption) */
|
|
2858
|
-
requestId: string;
|
|
2859
|
-
/** The note commitment being withdrawn */
|
|
2860
|
-
commitment: string;
|
|
2861
|
-
/** The nullifier being used */
|
|
2862
|
-
nullifier: string;
|
|
2863
|
-
/** When the withdrawal was initiated */
|
|
2864
|
-
startedAt: number;
|
|
2865
|
-
/** Status of the withdrawal */
|
|
2866
|
-
status: "pending" | "processing" | "completed" | "failed";
|
|
2867
|
-
/** Transaction signature if completed */
|
|
2868
|
-
txSignature?: string;
|
|
2869
|
-
/** Error message if failed */
|
|
2870
|
-
error?: string;
|
|
2871
|
-
}
|
|
3173
|
+
declare function resolveCircuitsBase(explicit?: string): string;
|
|
2872
3174
|
/**
|
|
2873
|
-
*
|
|
2874
|
-
*
|
|
3175
|
+
* Chain note v4.
|
|
3176
|
+
*
|
|
3177
|
+
* v4 binds noteSemantics = Poseidon(outAmount[0], outPubkey[0], noteIsSendToSelfKey0) into the note
|
|
3178
|
+
* tail, matching the ceremony circuit. The tail is therefore a 4-input hash, not 3.
|
|
3179
|
+
*
|
|
3180
|
+
* noteIsSendToSelfKey0 is 1 only when publicAmount == 0 AND output 0 went to the spender's own key,
|
|
3181
|
+
* exactly as the circuit computes it.
|
|
2875
3182
|
*/
|
|
2876
|
-
declare function
|
|
2877
|
-
|
|
2878
|
-
* Load all pending deposits
|
|
2879
|
-
*/
|
|
2880
|
-
declare function loadPendingDeposits(): PendingDeposit[];
|
|
2881
|
-
/**
|
|
2882
|
-
* Update a pending deposit status
|
|
2883
|
-
*/
|
|
2884
|
-
declare function updatePendingDeposit(commitment: string, updates: Partial<PendingDeposit>): void;
|
|
2885
|
-
/**
|
|
2886
|
-
* Remove a pending deposit (e.g., after successful confirmation)
|
|
2887
|
-
*/
|
|
2888
|
-
declare function removePendingDeposit(commitment: string): void;
|
|
2889
|
-
/**
|
|
2890
|
-
* Clear all pending deposits
|
|
2891
|
-
*/
|
|
2892
|
-
declare function clearPendingDeposits(): void;
|
|
2893
|
-
/**
|
|
2894
|
-
* Save a pending withdrawal
|
|
2895
|
-
* Call this when you receive the request_id from the relay
|
|
2896
|
-
*/
|
|
2897
|
-
declare function savePendingWithdrawal(withdrawal: PendingWithdrawal): void;
|
|
2898
|
-
/**
|
|
2899
|
-
* Load all pending withdrawals
|
|
2900
|
-
*/
|
|
2901
|
-
declare function loadPendingWithdrawals(): PendingWithdrawal[];
|
|
2902
|
-
/**
|
|
2903
|
-
* Update a pending withdrawal status
|
|
2904
|
-
*/
|
|
2905
|
-
declare function updatePendingWithdrawal(requestId: string, updates: Partial<PendingWithdrawal>): void;
|
|
2906
|
-
/**
|
|
2907
|
-
* Remove a pending withdrawal (e.g., after successful completion)
|
|
2908
|
-
*/
|
|
2909
|
-
declare function removePendingWithdrawal(requestId: string): void;
|
|
2910
|
-
/**
|
|
2911
|
-
* Clear all pending withdrawals
|
|
2912
|
-
*/
|
|
2913
|
-
declare function clearPendingWithdrawals(): void;
|
|
2914
|
-
/**
|
|
2915
|
-
* Check if there are any pending operations that need recovery
|
|
2916
|
-
* Call this on page load to determine if recovery UI should be shown
|
|
2917
|
-
*/
|
|
2918
|
-
declare function hasPendingOperations(): boolean;
|
|
2919
|
-
/**
|
|
2920
|
-
* Get summary of pending operations for recovery UI
|
|
2921
|
-
*/
|
|
2922
|
-
declare function getPendingOperationsSummary(): {
|
|
2923
|
-
deposits: PendingDeposit[];
|
|
2924
|
-
withdrawals: PendingWithdrawal[];
|
|
2925
|
-
totalPending: number;
|
|
2926
|
-
};
|
|
2927
|
-
/**
|
|
2928
|
-
* Clean up stale pending operations
|
|
2929
|
-
* Call this periodically to remove old failed/completed operations
|
|
2930
|
-
*
|
|
2931
|
-
* @param maxAgeMs Maximum age in milliseconds before an operation is removed (default: 24 hours)
|
|
2932
|
-
*/
|
|
2933
|
-
declare function cleanupStalePendingOperations(maxAgeMs?: number): {
|
|
2934
|
-
removedDeposits: number;
|
|
2935
|
-
removedWithdrawals: number;
|
|
2936
|
-
};
|
|
2937
|
-
|
|
2938
|
-
/**
|
|
2939
|
-
* UTXO Transaction Methods
|
|
2940
|
-
*
|
|
2941
|
-
* This module provides high-level methods for UTXO transactions:
|
|
2942
|
-
* - transact(): Core UTXO transaction
|
|
2943
|
-
* - transfer(): Shield-to-shield transfer
|
|
2944
|
-
* - partialWithdraw(): Withdraw with change
|
|
2945
|
-
*/
|
|
2946
|
-
|
|
2947
|
-
declare const DEFAULT_TRANSACTION_CIRCUITS_URL = "https://storage.googleapis.com/cloak-circuits/circuits/0.1.0";
|
|
2948
|
-
/**
|
|
2949
|
-
* Set circuits base path: local directory containing `transaction_js/` and `transaction_final.zkey`,
|
|
2950
|
-
* or an `http(s)` base URL to those artifacts (Node will download once per process to a temp dir).
|
|
2951
|
-
*/
|
|
2952
|
-
declare function setCircuitsPath(next: string): void;
|
|
2953
|
-
/**
|
|
2954
|
-
* Get the current circuits path
|
|
2955
|
-
*/
|
|
2956
|
-
declare function getCircuitsPath(): string;
|
|
2957
|
-
declare function computeChainNoteHash(publicAmount: bigint, extDataHash: bigint, noteTimestamp: bigint, noteCommitment: bigint): Promise<bigint>;
|
|
2958
|
-
/**
|
|
2959
|
-
* Compute extDataHash for binding proof to external parameters
|
|
2960
|
-
*
|
|
2961
|
-
* extDataHash = Poseidon(recipient, relayerFee, relayer)
|
|
2962
|
-
*/
|
|
2963
|
-
declare function computeExtDataHash(recipient: PublicKey | null, relayerFee: bigint, relayer: PublicKey | null): Promise<bigint>;
|
|
3183
|
+
declare function computeChainNoteHash(publicAmount: bigint, extDataHash: bigint, noteTimestamp: bigint, noteCommitment: bigint, noteSalt: bigint, outAmount0: bigint, outPubkey0: bigint, noteIsSendToSelfKey0: bigint): Promise<bigint>;
|
|
3184
|
+
declare function computeExtDataHash(recipient: PublicKey | null, relayerFee: bigint, relayer: PublicKey | null, maxFee?: bigint): Promise<bigint>;
|
|
2964
3185
|
/**
|
|
2965
3186
|
* Options for transact operation
|
|
2966
3187
|
*/
|
|
@@ -2978,8 +3199,8 @@ interface TransactOptions {
|
|
|
2978
3199
|
/** Relayer address for fee payment */
|
|
2979
3200
|
relayer?: PublicKey;
|
|
2980
3201
|
/**
|
|
2981
|
-
* Relay URL
|
|
2982
|
-
*
|
|
3202
|
+
* Relay URL. Never defaulted: resolved from this option, then from `CLOAK_RELAY_URL`.
|
|
3203
|
+
* Name production explicitly (`https://api.cloak.ag`).
|
|
2983
3204
|
*/
|
|
2984
3205
|
relayUrl?: string;
|
|
2985
3206
|
/** Keypair of the depositor (signs the deposit transaction) - for programmatic use */
|
|
@@ -3065,6 +3286,13 @@ interface TransactOptions {
|
|
|
3065
3286
|
* Each entry must be base64-encoded note bytes.
|
|
3066
3287
|
*/
|
|
3067
3288
|
encryptedNotes?: string[];
|
|
3289
|
+
/**
|
|
3290
|
+
* Omit the on-chain encrypted chain-note envelope entirely. The chainNoteHash public input
|
|
3291
|
+
* is unaffected (proof still binds it); only the optional ciphertext blob the program emits
|
|
3292
|
+
* for wallet auto-scan is dropped. Use when the caller tracks UTXOs out-of-band and needs the
|
|
3293
|
+
* smaller packet — e.g. V3 + SPL deposits that would otherwise overflow the 1232-byte limit.
|
|
3294
|
+
*/
|
|
3295
|
+
disableChainNotes?: boolean;
|
|
3068
3296
|
/**
|
|
3069
3297
|
* Optional nk (32 bytes, hex or bytes) for diversified chain note encryption (Phase 3).
|
|
3070
3298
|
* When provided, 2 per-output encrypted notes are embedded on-chain.
|
|
@@ -3076,6 +3304,32 @@ interface TransactOptions {
|
|
|
3076
3304
|
* passing nk at every call site — e.g. pass a getter from your key manager.
|
|
3077
3305
|
*/
|
|
3078
3306
|
getChainNoteViewingKeyNk?: () => Promise<string | Uint8Array | null>;
|
|
3307
|
+
/**
|
|
3308
|
+
* Pin the chain note's 96-bit `noteSalt` instead of drawing a fresh one (VK-01).
|
|
3309
|
+
*
|
|
3310
|
+
* Pass the `noteSalt` returned by {@link createRecoverableDepositUtxo}. That helper derives the
|
|
3311
|
+
* deposit note's keypair and blinding as `PRF(nk, noteSalt)`, and the chain note is the ONLY place
|
|
3312
|
+
* the salt is published — so a cold `(rpc, programId, nk)` scan can only rebuild the note if the
|
|
3313
|
+
* salt that anchored it is the salt that reaches the note. Leaving this unset draws a random salt,
|
|
3314
|
+
* which is correct for every non-derived flow.
|
|
3315
|
+
*
|
|
3316
|
+
* Requires an explicit `chainNoteViewingKeyNk` / `getChainNoteViewingKeyNk`; `transact` refuses
|
|
3317
|
+
* otherwise rather than pairing the salt with an nk inferred from the note's own key.
|
|
3318
|
+
*/
|
|
3319
|
+
chainNoteSalt?: bigint;
|
|
3320
|
+
/**
|
|
3321
|
+
* The RECIPIENT's 32-byte X25519 public viewing key, for a shield-to-shield send.
|
|
3322
|
+
*
|
|
3323
|
+
* Supply `deriveViewingKeyFromNk(recipientNk).publicKey` (hex or bytes). When present, the SDK
|
|
3324
|
+
* seals `{amount, blinding}` of output 0 to this key and ships it as `recipient_delivery_notes`,
|
|
3325
|
+
* which the relay publishes as a CLKD1 carrier the recipient can find with their `nk` alone.
|
|
3326
|
+
*
|
|
3327
|
+
* When absent, the send still succeeds but the recipient CANNOT discover the note from chain —
|
|
3328
|
+
* it must be handed over out of band. That was the shipped behaviour and is what campaign rows
|
|
3329
|
+
* S2S-08 / VK-01 measured. Ignored on deposits and withdrawals: the relay rejects the field
|
|
3330
|
+
* outright on anything that is not a send.
|
|
3331
|
+
*/
|
|
3332
|
+
recipientViewingPublicKey?: Uint8Array | string;
|
|
3079
3333
|
/**
|
|
3080
3334
|
* Cached Merkle tree from a previous transaction.
|
|
3081
3335
|
* When provided, the SDK skips fetching commitments from the relay and uses this
|
|
@@ -3151,17 +3405,119 @@ interface RiskQuoteInstructionResponse {
|
|
|
3151
3405
|
data: string;
|
|
3152
3406
|
};
|
|
3153
3407
|
}
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
* The backend should call Switchboard's fetchQuoteIx for the given wallet
|
|
3157
|
-
* (Range Risk API) and return the serialized instruction.
|
|
3158
|
-
* See: https://www.range.org/blog/integrate-range-onchain-risk-verifier-into-your-solana-program
|
|
3159
|
-
*/
|
|
3160
|
-
declare function fetchRiskQuoteInstruction(riskQuoteUrl: string, wallet: PublicKey, options?: {
|
|
3408
|
+
declare function fetchRiskQuoteInstruction(riskQuoteUrl: string, wallet: PublicKey, options: {
|
|
3409
|
+
poolMint: PublicKey;
|
|
3161
3410
|
recipient?: PublicKey;
|
|
3162
3411
|
amount?: bigint;
|
|
3163
3412
|
token?: PublicKey;
|
|
3413
|
+
commitment?: string | Uint8Array;
|
|
3414
|
+
context?: "deposit" | "send" | "withdraw";
|
|
3415
|
+
nullifier0?: string;
|
|
3416
|
+
nullifier1?: string;
|
|
3164
3417
|
}): Promise<TransactionInstruction>;
|
|
3418
|
+
/** Everything the `/transact` relay body is built from. */
|
|
3419
|
+
interface TransactRequestBodyParams {
|
|
3420
|
+
proofB64: string;
|
|
3421
|
+
publicInputsB64: string;
|
|
3422
|
+
mint: PublicKey;
|
|
3423
|
+
recipient?: PublicKey | null;
|
|
3424
|
+
relayer?: PublicKey | null;
|
|
3425
|
+
relayerFee: bigint;
|
|
3426
|
+
maxFee: bigint;
|
|
3427
|
+
encryptedNotes?: string[];
|
|
3428
|
+
riskQuote?: {
|
|
3429
|
+
signature: string;
|
|
3430
|
+
message: string;
|
|
3431
|
+
signer_pubkey: string;
|
|
3432
|
+
};
|
|
3433
|
+
/** Present only on shield-to-shield sends, where the relay enforces sanctions on the sender. */
|
|
3434
|
+
sender?: PublicKey | null;
|
|
3435
|
+
/** Signed public amount; zero is a shield-to-shield send. */
|
|
3436
|
+
externalAmount: bigint;
|
|
3437
|
+
/** Base64 CLKD1 envelopes, from `buildRecipientDeliveryNotes`. Omitted when the rail is off. */
|
|
3438
|
+
recipientDeliveryNotes?: string[];
|
|
3439
|
+
}
|
|
3440
|
+
/**
|
|
3441
|
+
* Assemble the `/transact` body.
|
|
3442
|
+
*
|
|
3443
|
+
* Extracted so the wire shape is testable without a proof. The field set is a contract with
|
|
3444
|
+
* `services/relay/src/api/transact.rs`, and `recipient_delivery_notes` in particular is part of the
|
|
3445
|
+
* relay's signed field view (`TRANSACT_AUTH_FIELDS`) — so a body that carries it must be signed
|
|
3446
|
+
* with it present, and a body that omits it must be signed with an explicit null. `buildAuthRequest`
|
|
3447
|
+
* handles that, but only if the field is genuinely absent rather than set to `undefined`-ish values.
|
|
3448
|
+
*/
|
|
3449
|
+
declare function buildTransactRequestBody(params: TransactRequestBodyParams): Record<string, unknown>;
|
|
3450
|
+
/** Everything settlement verification needs to check THIS proof against chain state. */
|
|
3451
|
+
interface SettlementContext {
|
|
3452
|
+
connection: SettlementConnection;
|
|
3453
|
+
programId: PublicKey;
|
|
3454
|
+
mint: PublicKey;
|
|
3455
|
+
/** The proof's public input nullifiers — the ground truth for "did this land". */
|
|
3456
|
+
inputNullifiers: bigint[];
|
|
3457
|
+
}
|
|
3458
|
+
type RelaySubmissionResult = {
|
|
3459
|
+
kind: "submitted";
|
|
3460
|
+
signature: string;
|
|
3461
|
+
commitmentIndices?: [number, number];
|
|
3462
|
+
viewingKeyRegistered?: boolean;
|
|
3463
|
+
settlement: SettlementVerdict;
|
|
3464
|
+
}
|
|
3465
|
+
/** The relay rejected the proof's root; the caller must rebuild the tree and re-prove. */
|
|
3466
|
+
| {
|
|
3467
|
+
kind: "stale-root";
|
|
3468
|
+
error: Error;
|
|
3469
|
+
} | {
|
|
3470
|
+
kind: "failed";
|
|
3471
|
+
error: Error;
|
|
3472
|
+
};
|
|
3473
|
+
interface SubmitTransactToRelayArgs {
|
|
3474
|
+
relayUrl: string;
|
|
3475
|
+
/** The exact body to POST. Auth fields are added ONCE, in place, and then never changed. */
|
|
3476
|
+
requestBody: Record<string, unknown>;
|
|
3477
|
+
programId: PublicKey;
|
|
3478
|
+
depositorKeypair?: Keypair;
|
|
3479
|
+
settlement: SettlementContext;
|
|
3480
|
+
/** True while the caller still has a re-prove budget for a stale root. */
|
|
3481
|
+
canRetryStaleRoot: boolean;
|
|
3482
|
+
maxNetworkRetries?: number;
|
|
3483
|
+
requestTimeoutMs?: number;
|
|
3484
|
+
/** Injection seam for tests; defaults to global fetch. */
|
|
3485
|
+
fetchImpl?: typeof fetch;
|
|
3486
|
+
onProgress?: (status: string) => void;
|
|
3487
|
+
/** Total window allowed for the on-chain settlement check after a reported success. */
|
|
3488
|
+
settlementTimeoutMs?: number;
|
|
3489
|
+
/** Shorter window used to ask "did it land anyway?" after a terminal relay failure. */
|
|
3490
|
+
failureProbeTimeoutMs?: number;
|
|
3491
|
+
}
|
|
3492
|
+
/**
|
|
3493
|
+
* POST one logical `/transact` request and REPORT ONLY WHAT THE CHAIN CONFIRMS.
|
|
3494
|
+
*
|
|
3495
|
+
* Extracted from `transact()` so the two campaign defects it fixes are reproducible without a
|
|
3496
|
+
* validator: a stub `fetchImpl` plays the hostile relay, a stub `SettlementConnection` plays the
|
|
3497
|
+
* RPC. Its control flow is the caller's old inline network-retry loop, unchanged except where
|
|
3498
|
+
* noted below.
|
|
3499
|
+
*
|
|
3500
|
+
* THREE fixes live here, all of them the same root cause — trusting the counterparty:
|
|
3501
|
+
*
|
|
3502
|
+
* 1. REL-A-1 (root cause of REL-A-10): the request is signed ONCE, before the retry loop, and
|
|
3503
|
+
* every retry re-POSTs the byte-identical body. The relay's recovery contract is keyed on
|
|
3504
|
+
* `(auth_nonce, endpoint, sender, request_digest)` — `request_auth.rs::authenticate_relay_request`
|
|
3505
|
+
* — and `handle_transact` answers an exact replay from its durable row: `Completed` replays the
|
|
3506
|
+
* stored response, `Prepared` reconciles the stored signature against chain, `Processing`
|
|
3507
|
+
* returns a retryable 503. The shipped client re-signed with a FRESH `randomUUID()` nonce inside
|
|
3508
|
+
* the loop, so every retry was a NEW request that could only collide with its own predecessor's
|
|
3509
|
+
* nullifier reservation (503) — measured: 7 POSTs, 7 nonces, byte-identical business fields, and
|
|
3510
|
+
* the recovery path unreachable. Re-signing here is not a fallback: a nonce that has aged past
|
|
3511
|
+
* the relay's 300s freshness window is still accepted for an EXACT replay whose row exists, and
|
|
3512
|
+
* when no row exists nothing was mutated, so the 401 is both correct and safe.
|
|
3513
|
+
* 2. X-S-01B: a reported success is verified against chain before it is returned. The relay's word
|
|
3514
|
+
* plus its commitment indices are not evidence; the input nullifier PDAs are.
|
|
3515
|
+
* 3. REL-A-10 / (b): the signature the relay puts in a `submission_outcome_unknown` body is
|
|
3516
|
+
* captured across attempts, and every terminal failure that could plausibly have been submitted
|
|
3517
|
+
* is resolved against chain into a `SettlementVerificationError` that states the outcome and
|
|
3518
|
+
* carries the signature.
|
|
3519
|
+
*/
|
|
3520
|
+
declare function submitTransactToRelay(args: SubmitTransactToRelayArgs): Promise<RelaySubmissionResult>;
|
|
3165
3521
|
declare function transact(params: TransactParams, options: TransactOptions): Promise<TransactResult>;
|
|
3166
3522
|
/**
|
|
3167
3523
|
* Execute a shield-to-shield transfer
|
|
@@ -3226,6 +3582,25 @@ interface UtxoSwapResult extends TransactResult {
|
|
|
3226
3582
|
nullifier: string;
|
|
3227
3583
|
/** Relay request ID for swap execution status */
|
|
3228
3584
|
requestId?: string;
|
|
3585
|
+
/**
|
|
3586
|
+
* V3-04 refund-fallback secret. **Persist this** to recover the swap principal if the swap times
|
|
3587
|
+
* out before TX2. On a timeout close the program builds the amount-bound note
|
|
3588
|
+
* R_fb = Poseidon(amountAfterFee, publicKey, blinding, WSOL)
|
|
3589
|
+
* where `amountAfterFee` is the principal it locked (read authoritatively from the on-chain
|
|
3590
|
+
* `SwapState.sol_amount`, = gross swap amount minus the on-chain swap fee). `ClaimRefund` then spends
|
|
3591
|
+
* `R_fb` with `privateKey` (a plain ZK membership proof of the refund tree). All values are hex.
|
|
3592
|
+
*
|
|
3593
|
+
* VK-02: when `derivedFromNk` is true this secret is ALSO recoverable from the wallet's `nk` plus
|
|
3594
|
+
* the swap's public first input nullifier — see `matchSwapRefundLeaf`. Persisting it is still the
|
|
3595
|
+
* fast path; losing it is no longer terminal. When false (no `nk` was supplied to the swap) this
|
|
3596
|
+
* object is the only copy that will ever exist.
|
|
3597
|
+
*/
|
|
3598
|
+
refund: {
|
|
3599
|
+
privateKey: string;
|
|
3600
|
+
publicKey: string;
|
|
3601
|
+
blinding: string;
|
|
3602
|
+
derivedFromNk: boolean;
|
|
3603
|
+
};
|
|
3229
3604
|
}
|
|
3230
3605
|
/**
|
|
3231
3606
|
* Execute a UTXO swap withdrawal
|
|
@@ -3255,24 +3630,755 @@ declare function swapUtxo(params: UtxoSwapParams, options: TransactOptions): Pro
|
|
|
3255
3630
|
declare function swapWithChange(inputUtxos: Utxo[], swapAmount: bigint, outputMint: PublicKey, recipientAta: PublicKey, minOutputAmount: bigint, options: TransactOptions, recipientWallet?: PublicKey): Promise<UtxoSwapResult>;
|
|
3256
3631
|
|
|
3257
3632
|
/**
|
|
3258
|
-
*
|
|
3633
|
+
* Direct Circom WASM Proof Generation
|
|
3634
|
+
*
|
|
3635
|
+
* This module provides direct proof generation using snarkjs and Circom WASM,
|
|
3636
|
+
* matching the approach used in services-new/tests/src/proof.ts
|
|
3637
|
+
*
|
|
3638
|
+
* Artifacts come from the pinned per-bundle hosts in `config/circuit-release`,
|
|
3639
|
+
* verified by digest; no backend prover service is required.
|
|
3640
|
+
*/
|
|
3641
|
+
|
|
3642
|
+
/**
|
|
3643
|
+
* Default base URL for the legacy `withdraw_regular` / `withdraw_swap` artifacts.
|
|
3644
|
+
*
|
|
3645
|
+
* Derived from {@link LEGACY_WITHDRAW_CIRCUIT_BUNDLE}, so the version segment is
|
|
3646
|
+
* the same one the pinned digests were declared under. Do not write this URL out
|
|
3647
|
+
* by hand anywhere — change the bundle instead.
|
|
3648
|
+
*
|
|
3649
|
+
* This is NOT the base for the ceremony-frozen `transaction` circuit: that
|
|
3650
|
+
* circuit lives in a different bundle ({@link TRANSACTION_CIRCUIT_BUNDLE}) with
|
|
3651
|
+
* different digests, and is configured through `setCircuitsPath()`.
|
|
3652
|
+
*
|
|
3653
|
+
* `string | null` — null when the bundle has no published host, which is the
|
|
3654
|
+
* case for the legacy withdraw artifacts.
|
|
3655
|
+
*
|
|
3656
|
+
* RESOLVED LAZILY ON PURPOSE. This was previously
|
|
3657
|
+
* `requirePinnedBaseUrl('withdraw_regular')` evaluated at module scope, so an
|
|
3658
|
+
* unpinned bundle made merely IMPORTING the SDK throw — breaking every consumer,
|
|
3659
|
+
* including those that never touch a legacy withdraw path. The diagnostic is
|
|
3660
|
+
* still raised, by `resolveCircuitsUrl` and `getDefaultCircuitsPath` at the
|
|
3661
|
+
* point of use, where a caller can actually act on it.
|
|
3662
|
+
*/
|
|
3663
|
+
declare const DEFAULT_CIRCUITS_URL: string | null;
|
|
3664
|
+
interface WithdrawRegularInputs {
|
|
3665
|
+
root: bigint;
|
|
3666
|
+
nullifier: bigint;
|
|
3667
|
+
outputs_hash: bigint;
|
|
3668
|
+
public_amount: bigint;
|
|
3669
|
+
amount: bigint;
|
|
3670
|
+
leaf_index: bigint;
|
|
3671
|
+
sk: [bigint, bigint];
|
|
3672
|
+
r: [bigint, bigint];
|
|
3673
|
+
pathElements: bigint[];
|
|
3674
|
+
pathIndices: number[];
|
|
3675
|
+
num_outputs: number;
|
|
3676
|
+
out_addr: bigint[][];
|
|
3677
|
+
out_amount: bigint[];
|
|
3678
|
+
out_flags: number[];
|
|
3679
|
+
var_fee: bigint;
|
|
3680
|
+
rem: bigint;
|
|
3681
|
+
}
|
|
3682
|
+
interface WithdrawSwapInputs {
|
|
3683
|
+
sk_spend: bigint;
|
|
3684
|
+
r: bigint;
|
|
3685
|
+
amount: bigint;
|
|
3686
|
+
leaf_index: bigint;
|
|
3687
|
+
path_elements: bigint[];
|
|
3688
|
+
path_indices: number[];
|
|
3689
|
+
root: bigint;
|
|
3690
|
+
nullifier: bigint;
|
|
3691
|
+
outputs_hash: bigint;
|
|
3692
|
+
public_amount: bigint;
|
|
3693
|
+
input_mint: bigint[];
|
|
3694
|
+
output_mint: bigint[];
|
|
3695
|
+
recipient_ata: bigint[];
|
|
3696
|
+
min_output_amount: bigint;
|
|
3697
|
+
var_fee: bigint;
|
|
3698
|
+
rem: bigint;
|
|
3699
|
+
}
|
|
3700
|
+
interface ProofResult {
|
|
3701
|
+
proof: Groth16Proof;
|
|
3702
|
+
publicSignals: string[];
|
|
3703
|
+
proofBytes: Uint8Array;
|
|
3704
|
+
publicInputsBytes: Uint8Array;
|
|
3705
|
+
}
|
|
3706
|
+
/**
|
|
3707
|
+
* Generate Groth16 proof for regular withdrawal using Circom WASM
|
|
3708
|
+
*
|
|
3709
|
+
* This matches the approach in services-new/tests/src/proof.ts
|
|
3710
|
+
*
|
|
3711
|
+
* @param inputs - Circuit inputs
|
|
3712
|
+
* @param circuitsPath - Ignored. Proof generation always uses pinned S3 circuits.
|
|
3713
|
+
*/
|
|
3714
|
+
declare function generateWithdrawRegularProof(inputs: WithdrawRegularInputs, circuitsPath: string): Promise<ProofResult>;
|
|
3715
|
+
/**
|
|
3716
|
+
* Generate Groth16 proof for swap withdrawal using Circom WASM
|
|
3717
|
+
*
|
|
3718
|
+
* This matches the approach in services-new/tests/src/proof.ts
|
|
3719
|
+
*
|
|
3720
|
+
* @param inputs - Circuit inputs
|
|
3721
|
+
* @param circuitsPath - Ignored. Proof generation always uses pinned S3 circuits.
|
|
3722
|
+
*/
|
|
3723
|
+
declare function generateWithdrawSwapProof(inputs: WithdrawSwapInputs, circuitsPath: string): Promise<ProofResult>;
|
|
3724
|
+
/**
|
|
3725
|
+
* Check if circuits are available from the pinned S3 source.
|
|
3726
|
+
*/
|
|
3727
|
+
declare function areCircuitsAvailable(circuitsPath: string): Promise<boolean>;
|
|
3728
|
+
/**
|
|
3729
|
+
* Get default circuits URL.
|
|
3730
|
+
*/
|
|
3731
|
+
declare function getDefaultCircuitsPath(): Promise<string>;
|
|
3732
|
+
/**
|
|
3733
|
+
* Pinned circuit artifact hashes (SHA-256), flattened from the release table in
|
|
3734
|
+
* `config/circuit-release.ts`.
|
|
3259
3735
|
*
|
|
3260
|
-
*
|
|
3736
|
+
* This is a *view*, not the source of truth: edit the bundle, not this object.
|
|
3737
|
+
* It stays writable because the artifact TOCTOU tests re-pin it to synthetic
|
|
3738
|
+
* fixtures; production code must not mutate it.
|
|
3739
|
+
*/
|
|
3740
|
+
declare const EXPECTED_CIRCUIT_HASHES: {
|
|
3741
|
+
withdraw_regular_wasm: string;
|
|
3742
|
+
withdraw_regular_zkey: string;
|
|
3743
|
+
withdraw_swap_wasm: string;
|
|
3744
|
+
withdraw_swap_zkey: string;
|
|
3745
|
+
transaction_wasm: string;
|
|
3746
|
+
transaction_zkey: string;
|
|
3747
|
+
};
|
|
3748
|
+
/**
|
|
3749
|
+
* Circuit verification result
|
|
3750
|
+
*/
|
|
3751
|
+
interface CircuitVerificationResult {
|
|
3752
|
+
/** Whether verification passed */
|
|
3753
|
+
valid: boolean;
|
|
3754
|
+
/** Which circuit was checked */
|
|
3755
|
+
circuit: CircuitName;
|
|
3756
|
+
/** Error message if verification failed */
|
|
3757
|
+
error?: string;
|
|
3758
|
+
computed?: {
|
|
3759
|
+
wasm: string;
|
|
3760
|
+
zkey: string;
|
|
3761
|
+
};
|
|
3762
|
+
expected?: {
|
|
3763
|
+
wasm: string;
|
|
3764
|
+
zkey: string;
|
|
3765
|
+
};
|
|
3766
|
+
}
|
|
3767
|
+
/** Circuit artifact bytes together with the digests computed over those bytes. */
|
|
3768
|
+
interface VerifiedCircuitArtifacts {
|
|
3769
|
+
/** `<circuit>_js/<circuit>.wasm` bytes. */
|
|
3770
|
+
wasm: Uint8Array;
|
|
3771
|
+
/** `<circuit>_final.zkey` bytes. */
|
|
3772
|
+
zkey: Uint8Array;
|
|
3773
|
+
/** SHA-256 (lowercase hex) of the buffers in this object. */
|
|
3774
|
+
digests: {
|
|
3775
|
+
wasm: string;
|
|
3776
|
+
zkey: string;
|
|
3777
|
+
};
|
|
3778
|
+
}
|
|
3779
|
+
/**
|
|
3780
|
+
* Load circuit artifacts and return the very bytes whose digest was checked.
|
|
3781
|
+
*
|
|
3782
|
+
* This is the only supported way to obtain proving artifacts: the caller passes
|
|
3783
|
+
* the returned buffers straight to `snarkjs.groth16.fullProve`, which accepts a
|
|
3784
|
+
* `Uint8Array` for both the wasm and the zkey. Passing snarkjs a URL or a file
|
|
3785
|
+
* path instead would re-read the artifact independently of the digest check, so
|
|
3786
|
+
* a CDN (or a concurrent writer on disk) could serve good bytes to the check and
|
|
3787
|
+
* different bytes to the prover.
|
|
3788
|
+
*
|
|
3789
|
+
* Fails closed: any digest mismatch, unreachable artifact, or environment that
|
|
3790
|
+
* cannot produce bytes (a browser pointed at a local directory) throws rather
|
|
3791
|
+
* than falling back to an unverified source.
|
|
3792
|
+
*/
|
|
3793
|
+
declare function loadVerifiedCircuitArtifacts(circuitsPath: string | null, circuit: CircuitName): Promise<VerifiedCircuitArtifacts>;
|
|
3794
|
+
/**
|
|
3795
|
+
* Report whether a circuit's artifacts match the digests pinned in this SDK.
|
|
3796
|
+
*
|
|
3797
|
+
* This is a *reporting* helper (used by `verifyAllCircuits` for start-up checks
|
|
3798
|
+
* and diagnostics). It is NOT what gates proving: a check that only inspects the
|
|
3799
|
+
* source cannot say anything about the bytes a later, independent read returns.
|
|
3800
|
+
* Proof paths must call {@link loadVerifiedCircuitArtifacts} and hand the bytes
|
|
3801
|
+
* it returns to snarkjs.
|
|
3802
|
+
*
|
|
3803
|
+
* IMPORTANT: If the hashes don't match, the circuit may produce proofs
|
|
3804
|
+
* that will be rejected by the on-chain verifier!
|
|
3805
|
+
*
|
|
3806
|
+
* @param circuitsPath - Ignored for the legacy withdraw circuits (they always use
|
|
3807
|
+
* their own pinned bundle); honoured for `transaction`.
|
|
3808
|
+
* @param circuit - Which circuit to verify
|
|
3809
|
+
* @returns Verification result
|
|
3810
|
+
*
|
|
3811
|
+
* @example
|
|
3812
|
+
* ```typescript
|
|
3813
|
+
* const result = await verifyCircuitIntegrity(DEFAULT_CIRCUITS_URL, 'withdraw_regular');
|
|
3814
|
+
* if (!result.valid) {
|
|
3815
|
+
* console.error('Circuit verification failed:', result.error);
|
|
3816
|
+
* // Don't proceed with proof generation!
|
|
3817
|
+
* }
|
|
3818
|
+
* ```
|
|
3819
|
+
*/
|
|
3820
|
+
declare function verifyCircuitIntegrity(circuitsPath: string | null, circuit: CircuitName, prefetched?: {
|
|
3821
|
+
wasm: Uint8Array;
|
|
3822
|
+
zkey: Uint8Array;
|
|
3823
|
+
}): Promise<CircuitVerificationResult>;
|
|
3824
|
+
/**
|
|
3825
|
+
* Assert the ceremony-frozen `transaction` circuit artifacts are the pinned ones.
|
|
3826
|
+
*
|
|
3827
|
+
* Throws (fail-closed) when the digests do not match, mirroring how
|
|
3828
|
+
* `generateWithdrawRegularProof` / `generateWithdrawSwapProof` gate the legacy
|
|
3829
|
+
* circuits. Proving against an unpinned zkey silently produces proofs the
|
|
3830
|
+
* on-chain verifying key rejects, so failing here is strictly better than
|
|
3831
|
+
* failing on-chain.
|
|
3832
|
+
*
|
|
3833
|
+
* @param circuitsPath - Base directory or URL holding `transaction_js/transaction.wasm`
|
|
3834
|
+
* and `transaction_final.zkey`.
|
|
3835
|
+
* @param prefetched - Already-downloaded artifact bytes, to avoid a second fetch.
|
|
3836
|
+
*/
|
|
3837
|
+
declare function assertTransactionCircuitIntegrity(circuitsPath: string | null, prefetched?: {
|
|
3838
|
+
wasm: Uint8Array;
|
|
3839
|
+
zkey: Uint8Array;
|
|
3840
|
+
}): Promise<void>;
|
|
3841
|
+
/**
|
|
3842
|
+
* Verify all circuits before use
|
|
3843
|
+
*
|
|
3844
|
+
* Call this at SDK initialization to ensure circuits are valid.
|
|
3845
|
+
*
|
|
3846
|
+
* @param circuitsPath - For the legacy withdraw circuits this is ignored; verification
|
|
3847
|
+
* always uses their own pinned bundle.
|
|
3848
|
+
* @param transactionCircuitsPath - Base for the ceremony-frozen `transaction` circuit.
|
|
3849
|
+
* Pass `getCircuitsPath()` when the caller has reconfigured it;
|
|
3850
|
+
* `null` reports the "no base configured" state rather than throwing.
|
|
3851
|
+
* @returns Array of verification results (one per circuit)
|
|
3852
|
+
*/
|
|
3853
|
+
declare function verifyAllCircuits(circuitsPath: string, transactionCircuitsPath?: string | null): Promise<CircuitVerificationResult[]>;
|
|
3854
|
+
|
|
3855
|
+
/**
|
|
3856
|
+
* Pending Operations Manager
|
|
3857
|
+
*
|
|
3858
|
+
* Utility for persisting pending deposit/withdrawal operations in browser storage.
|
|
3859
|
+
* This enables recovery if the browser crashes or user navigates away mid-operation.
|
|
3860
|
+
*
|
|
3861
|
+
* IMPORTANT: This uses localStorage by default which has security implications.
|
|
3862
|
+
* Notes contain sensitive spending keys - consider using more secure storage
|
|
3863
|
+
* in production (e.g., encrypted IndexedDB, secure enclave).
|
|
3864
|
+
*/
|
|
3865
|
+
|
|
3866
|
+
/**
|
|
3867
|
+
* Pending deposit record
|
|
3868
|
+
*/
|
|
3869
|
+
interface PendingDeposit {
|
|
3870
|
+
/** The note (contains spending secrets!) */
|
|
3871
|
+
note: CloakNote;
|
|
3872
|
+
/** When the deposit was initiated */
|
|
3873
|
+
startedAt: number;
|
|
3874
|
+
/** Transaction signature if available */
|
|
3875
|
+
txSignature?: string;
|
|
3876
|
+
/** Status of the deposit */
|
|
3877
|
+
status: "pending" | "tx_sent" | "confirmed" | "failed";
|
|
3878
|
+
/** Error message if failed */
|
|
3879
|
+
error?: string;
|
|
3880
|
+
}
|
|
3881
|
+
/**
|
|
3882
|
+
* Pending withdrawal record
|
|
3883
|
+
*/
|
|
3884
|
+
interface PendingWithdrawal {
|
|
3885
|
+
/** The relay request ID (for resumption) */
|
|
3886
|
+
requestId: string;
|
|
3887
|
+
/** The note commitment being withdrawn */
|
|
3888
|
+
commitment: string;
|
|
3889
|
+
/** The nullifier being used */
|
|
3890
|
+
nullifier: string;
|
|
3891
|
+
/** When the withdrawal was initiated */
|
|
3892
|
+
startedAt: number;
|
|
3893
|
+
/** Status of the withdrawal */
|
|
3894
|
+
status: "pending" | "processing" | "completed" | "failed";
|
|
3895
|
+
/** Transaction signature if completed */
|
|
3896
|
+
txSignature?: string;
|
|
3897
|
+
/** Error message if failed */
|
|
3898
|
+
error?: string;
|
|
3899
|
+
}
|
|
3900
|
+
/**
|
|
3901
|
+
* Save a pending deposit
|
|
3902
|
+
* Call this BEFORE sending the on-chain transaction to ensure note is persisted
|
|
3903
|
+
*/
|
|
3904
|
+
declare function savePendingDeposit(deposit: PendingDeposit): void;
|
|
3905
|
+
/**
|
|
3906
|
+
* Load all pending deposits
|
|
3907
|
+
*/
|
|
3908
|
+
declare function loadPendingDeposits(): PendingDeposit[];
|
|
3909
|
+
/**
|
|
3910
|
+
* Update a pending deposit status
|
|
3911
|
+
*/
|
|
3912
|
+
declare function updatePendingDeposit(commitment: string, updates: Partial<PendingDeposit>): void;
|
|
3913
|
+
/**
|
|
3914
|
+
* Remove a pending deposit (e.g., after successful confirmation)
|
|
3915
|
+
*/
|
|
3916
|
+
declare function removePendingDeposit(commitment: string): void;
|
|
3917
|
+
/**
|
|
3918
|
+
* Clear all pending deposits
|
|
3919
|
+
*/
|
|
3920
|
+
declare function clearPendingDeposits(): void;
|
|
3921
|
+
/**
|
|
3922
|
+
* Save a pending withdrawal
|
|
3923
|
+
* Call this when you receive the request_id from the relay
|
|
3924
|
+
*/
|
|
3925
|
+
declare function savePendingWithdrawal(withdrawal: PendingWithdrawal): void;
|
|
3926
|
+
/**
|
|
3927
|
+
* Load all pending withdrawals
|
|
3928
|
+
*/
|
|
3929
|
+
declare function loadPendingWithdrawals(): PendingWithdrawal[];
|
|
3930
|
+
/**
|
|
3931
|
+
* Update a pending withdrawal status
|
|
3932
|
+
*/
|
|
3933
|
+
declare function updatePendingWithdrawal(requestId: string, updates: Partial<PendingWithdrawal>): void;
|
|
3934
|
+
/**
|
|
3935
|
+
* Remove a pending withdrawal (e.g., after successful completion)
|
|
3936
|
+
*/
|
|
3937
|
+
declare function removePendingWithdrawal(requestId: string): void;
|
|
3938
|
+
/**
|
|
3939
|
+
* Clear all pending withdrawals
|
|
3940
|
+
*/
|
|
3941
|
+
declare function clearPendingWithdrawals(): void;
|
|
3942
|
+
/**
|
|
3943
|
+
* Check if there are any pending operations that need recovery
|
|
3944
|
+
* Call this on page load to determine if recovery UI should be shown
|
|
3945
|
+
*/
|
|
3946
|
+
declare function hasPendingOperations(): boolean;
|
|
3947
|
+
/**
|
|
3948
|
+
* Get summary of pending operations for recovery UI
|
|
3949
|
+
*/
|
|
3950
|
+
declare function getPendingOperationsSummary(): {
|
|
3951
|
+
deposits: PendingDeposit[];
|
|
3952
|
+
withdrawals: PendingWithdrawal[];
|
|
3953
|
+
totalPending: number;
|
|
3954
|
+
};
|
|
3955
|
+
/**
|
|
3956
|
+
* Clean up stale pending operations
|
|
3957
|
+
* Call this periodically to remove old failed/completed operations
|
|
3958
|
+
*
|
|
3959
|
+
* @param maxAgeMs Maximum age in milliseconds before an operation is removed (default: 24 hours)
|
|
3960
|
+
*/
|
|
3961
|
+
declare function cleanupStalePendingOperations(maxAgeMs?: number): {
|
|
3962
|
+
removedDeposits: number;
|
|
3963
|
+
removedWithdrawals: number;
|
|
3964
|
+
};
|
|
3965
|
+
|
|
3966
|
+
/**
|
|
3967
|
+
* Recipient-addressed delivery envelope (CLKD1).
|
|
3968
|
+
*
|
|
3969
|
+
* ── The defect this closes ────────────────────────────────────────────────────────────────────
|
|
3970
|
+
* A shield-to-shield send creates an output note OWNED BY THE RECIPIENT. The only discovery
|
|
3971
|
+
* artefact the SDK used to publish for it was the CLK1 compliance chain note, which is encrypted
|
|
3972
|
+
* under the SENDER's `nk` and keyed (HKDF salt) by the output commitment. That note is openable by
|
|
3973
|
+
* the sender and by nobody else — so the recipient's money sat on chain, valid and spendable, with
|
|
3974
|
+
* its owner unable to see it (campaign rows S2S-08 / VK-01).
|
|
3975
|
+
*
|
|
3976
|
+
* This envelope is the recipient's half. It is encrypted to the RECIPIENT's X25519 public viewing
|
|
3977
|
+
* key — the one derived by `deriveViewingKeyFromNk(nk)` — so a cold scan holding nothing but
|
|
3978
|
+
* `(rpc, programId, nk)` can open it. That triple is exactly the cold-scan contract VK-01 tested.
|
|
3979
|
+
*
|
|
3980
|
+
* ── Wire format — dictated by the relay, not by us ────────────────────────────────────────────
|
|
3981
|
+
* `services/relay/src/api/transact.rs:176-205` accepts EXACTLY ONE base64 envelope of EXACTLY
|
|
3982
|
+
* 112 bytes and only on a shield-to-shield send (`public_amount == 0`); anything else is a 400 and
|
|
3983
|
+
* no carrier is written. `services/relay/src/solana/mod.rs:2338` then publishes
|
|
3984
|
+
*
|
|
3985
|
+
* "CLKD1" || hex(output_commitment[0]) || hex(envelope)
|
|
3986
|
+
*
|
|
3987
|
+
* as an SPL Memo in a transaction that touches the PDA at seed `b"cloak_delivery_registry"`, which
|
|
3988
|
+
* is what makes it enumerable via `getSignaturesForAddress`.
|
|
3989
|
+
*
|
|
3990
|
+
* The 112 bytes are:
|
|
3991
|
+
*
|
|
3992
|
+
* [0 ..32) ephemeral X25519 public key
|
|
3993
|
+
* [32 ..56) 24-byte XSalsa20-Poly1305 nonce
|
|
3994
|
+
* [56..112) 56-byte ciphertext = 40-byte plaintext + 16-byte Poly1305 tag
|
|
3995
|
+
*
|
|
3996
|
+
* and the 40-byte plaintext is `amount(u64 LE) || blinding(u256 BE)` — precisely what a recipient
|
|
3997
|
+
* needs, alongside their own keypair and the pool mint, to recompute the commitment and spend it.
|
|
3998
|
+
* Amount is LE to match every other u64 the SDK writes (`chain-note.ts`, public inputs); blinding
|
|
3999
|
+
* is BE to match `bigintToBytes32` and the on-chain field-element convention.
|
|
4000
|
+
*
|
|
4001
|
+
* ── Crypto ────────────────────────────────────────────────────────────────────────────────────
|
|
4002
|
+
* X25519 ECDH + XSalsa20-Poly1305, i.e. `nacl.box`, reusing the exact construction already in
|
|
4003
|
+
* `core/keys.ts` (`nacl.box.before` + `nacl.secretbox`). No new primitive is introduced here.
|
|
4004
|
+
*/
|
|
4005
|
+
|
|
4006
|
+
/** Ephemeral X25519 public key, at offset 0. */
|
|
4007
|
+
declare const RECIPIENT_DELIVERY_EPHEMERAL_PK_LEN = 32;
|
|
4008
|
+
/** XSalsa20-Poly1305 nonce, immediately after the ephemeral key. */
|
|
4009
|
+
declare const RECIPIENT_DELIVERY_NONCE_LEN = 24;
|
|
4010
|
+
/** amount(8) + blinding(32) — everything the owner needs to rebuild and spend the note. */
|
|
4011
|
+
declare const RECIPIENT_DELIVERY_PLAINTEXT_LEN = 40;
|
|
4012
|
+
/** Poly1305 authentication tag. */
|
|
4013
|
+
declare const RECIPIENT_DELIVERY_TAG_LEN = 16;
|
|
4014
|
+
/** Sealed payload = plaintext + tag. */
|
|
4015
|
+
declare const RECIPIENT_DELIVERY_CIPHERTEXT_LEN: number;
|
|
4016
|
+
/**
|
|
4017
|
+
* Total envelope size. The relay rejects any other length outright
|
|
4018
|
+
* (`RECIPIENT_DELIVERY_NOTE_BYTES` in `services/relay/src/api/transact.rs`), so this constant is a
|
|
4019
|
+
* contract with a shipped binary, not a preference.
|
|
4020
|
+
*/
|
|
4021
|
+
declare const RECIPIENT_DELIVERY_NOTE_BYTES: number;
|
|
4022
|
+
/** PDA seed the relay derives the carrier's registry account from. */
|
|
4023
|
+
declare const DELIVERY_REGISTRY_SEED = "cloak_delivery_registry";
|
|
4024
|
+
/** ASCII tag prefixing the carrier memo payload. */
|
|
4025
|
+
declare const DELIVERY_MEMO_TAG = "CLKD1";
|
|
4026
|
+
/** The spendable contents of a delivery envelope. */
|
|
4027
|
+
interface RecipientDeliveryNote {
|
|
4028
|
+
/** Note amount in the pool mint's smallest unit. */
|
|
4029
|
+
amount: bigint;
|
|
4030
|
+
/** Note blinding factor (BN254 field element). */
|
|
4031
|
+
blinding: bigint;
|
|
4032
|
+
}
|
|
4033
|
+
/**
|
|
4034
|
+
* Seal `{amount, blinding}` to a recipient's X25519 public viewing key.
|
|
4035
|
+
*
|
|
4036
|
+
* Every input is length-checked here rather than at the relay: a malformed envelope costs a
|
|
4037
|
+
* confirmed transaction's worth of latency before the 400 comes back, and the send has already
|
|
4038
|
+
* generated its proof by then.
|
|
4039
|
+
*/
|
|
4040
|
+
declare function encodeRecipientDeliveryNote(note: RecipientDeliveryNote, recipientViewingPublicKey: Uint8Array): Uint8Array;
|
|
4041
|
+
/**
|
|
4042
|
+
* Trial-open an envelope with a viewing secret. Returns `null` — never throws — when the envelope
|
|
4043
|
+
* is not ours, because a scanner runs this against every carrier in the registry.
|
|
4044
|
+
*/
|
|
4045
|
+
declare function openRecipientDeliveryNote(envelope: Uint8Array, viewingSecretKey: Uint8Array): RecipientDeliveryNote | null;
|
|
4046
|
+
declare function recipientDeliveryNoteToBase64(envelope: Uint8Array): string;
|
|
4047
|
+
/** Parameters the send path already has in hand when it assembles the relay body. */
|
|
4048
|
+
interface BuildRecipientDeliveryNotesParams {
|
|
4049
|
+
/** Signed public amount. Zero — and only zero — is a shield-to-shield send. */
|
|
4050
|
+
externalAmount: bigint;
|
|
4051
|
+
/** External withdrawal recipient, if any. A send has none. */
|
|
4052
|
+
recipient?: PublicKey | null;
|
|
4053
|
+
/** The recipient's 32-byte X25519 public viewing key (`deriveViewingKeyFromNk(nk).publicKey`). */
|
|
4054
|
+
recipientViewingPublicKey?: Uint8Array | string | null;
|
|
4055
|
+
/**
|
|
4056
|
+
* The output note being delivered. MUST be output 0: the relay binds the carrier to
|
|
4057
|
+
* `public_inputs.output_commitments[0]` (`services/relay/src/api/transact.rs:1015`).
|
|
4058
|
+
*/
|
|
4059
|
+
note?: Pick<Utxo, "amount" | "blinding"> | null;
|
|
4060
|
+
/** UTXO public key that owns `note` (i.e. `paddedOutputs[0].keypair.publicKey`). */
|
|
4061
|
+
noteOwnerPublicKey?: bigint;
|
|
4062
|
+
/** UTXO public key doing the spending (i.e. `paddedInputs[0].keypair.publicKey`). */
|
|
4063
|
+
spenderPublicKey?: bigint;
|
|
4064
|
+
}
|
|
4065
|
+
/**
|
|
4066
|
+
* Build the `recipient_delivery_notes` field for `/transact`, or `undefined` when the rail does not
|
|
4067
|
+
* apply. Returning `undefined` (rather than an empty array) matters: the relay's signed field view
|
|
4068
|
+
* treats omitted and null identically, and an empty array on a withdrawal would still be a 400.
|
|
4069
|
+
*/
|
|
4070
|
+
declare function buildRecipientDeliveryNotes(params: BuildRecipientDeliveryNotesParams): string[] | undefined;
|
|
4071
|
+
/** Render the carrier memo byte-for-byte as `emit_recipient_delivery_carrier` does. */
|
|
4072
|
+
declare function encodeDeliveryCarrierMemo(outputCommitment: bigint | Uint8Array, envelope: Uint8Array): Uint8Array;
|
|
4073
|
+
interface ParsedDeliveryCarrier {
|
|
4074
|
+
/** Lowercase 64-char hex of the output commitment the carrier declares. */
|
|
4075
|
+
commitment: string;
|
|
4076
|
+
/** The raw 112-byte envelope. */
|
|
4077
|
+
note: Uint8Array;
|
|
4078
|
+
}
|
|
4079
|
+
/**
|
|
4080
|
+
* Parse one SPL Memo payload. Returns `null` for anything that is not a well-formed CLKD1 carrier —
|
|
4081
|
+
* the memo program accepts arbitrary UTF-8 from anyone, so this must fail closed on exact lengths
|
|
4082
|
+
* (I-12: exact `!==` gates, never `<`).
|
|
4083
|
+
*/
|
|
4084
|
+
declare function parseDeliveryCarrierMemo(data: Uint8Array): ParsedDeliveryCarrier | null;
|
|
4085
|
+
|
|
4086
|
+
/**
|
|
4087
|
+
* Recoverable deposit notes (VK-01, deposit shape).
|
|
4088
|
+
*
|
|
4089
|
+
* ── The defect this closes ────────────────────────────────────────────────────────────────────
|
|
4090
|
+
* A cold scan holding exactly `(rpc, programId, nk)` — the contract the SDK advertises for viewing
|
|
4091
|
+
* keys — found the WITHDRAWAL change note and did NOT find the DEPOSIT. The control that makes the
|
|
4092
|
+
* miss attributable is the withdrawal: the same scanner, the same 300-signature window, the same
|
|
4093
|
+
* key, one shape found and one not.
|
|
4094
|
+
*
|
|
4095
|
+
* Two things were missing, and only one of them was visible from the failing arm:
|
|
4096
|
+
*
|
|
4097
|
+
* 1. `outPubkey0`. The deposit's chain note is v3 (timestamp + noteSalt), and `chainNoteHash` binds
|
|
4098
|
+
* `noteSemantics = Poseidon(outAmount0, outPubkey0, isSendToSelfKey0)`. `outPubkey0` is the
|
|
4099
|
+
* output note's OWNER key, which is not derivable from `nk` — the key hierarchy runs
|
|
4100
|
+
* `skSpend → nk` through BLAKE3 and does not run backwards. So the scanner could decrypt the
|
|
4101
|
+
* note and then had to drop it, because the hash it recomputed could never match. That is why
|
|
4102
|
+
* the widened arm needed an undocumented `ownUtxoPublicKey`: it was feeding the scanner the one
|
|
4103
|
+
* value the advertised contract does not carry.
|
|
4104
|
+
* 2. The BLINDING. Even with `ownUtxoPublicKey` the deposit is only VISIBLE, not RECOVERABLE — the
|
|
4105
|
+
* blinding came from `randomFieldElement()` inside `createUtxo` and is written nowhere. A note
|
|
4106
|
+
* you can see and cannot spend is not a recovered note.
|
|
4107
|
+
*
|
|
4108
|
+
* ── Why derivation, and not another envelope ──────────────────────────────────────────────────
|
|
4109
|
+
* The shielded-send shape was fixed with a CLKD1 delivery envelope because a send's output is owned
|
|
4110
|
+
* by SOMEONE ELSE: the only way to reach them is to encrypt to their key. A deposit's output is
|
|
4111
|
+
* SELF-owned. There is nobody to deliver to, and an envelope would cost 112 bytes on the one
|
|
4112
|
+
* transaction in the protocol that is wallet-signed and already tight against the 1232-byte packet
|
|
4113
|
+
* limit (a v4 chain note's extra 41 bytes is measured at 1282 and is exactly why deposits stay v3).
|
|
4114
|
+
*
|
|
4115
|
+
* So make the existing chain-note path recoverable instead. Both missing values become a PRF of the
|
|
4116
|
+
* wallet's own `nk` and the note salt the chain note ALREADY carries:
|
|
4117
|
+
*
|
|
4118
|
+
* seed = BLAKE3("cloak_deposit_note_v1" || nk || noteSalt(32B BE) || info)
|
|
4119
|
+
* privateKey = seed("sk") reduced into the field
|
|
4120
|
+
* blinding = seed("blinding") reduced into the field
|
|
4121
|
+
* publicKey = PoseidonEx(privateKey, KEYPAIR_) [L-02, matches keypair.circom]
|
|
4122
|
+
*
|
|
4123
|
+
* A cold scanner decrypts the chain note with `nk` (AES-GCM, HKDF-salted by the output commitment),
|
|
4124
|
+
* reads `noteSalt` out of the plaintext, replays those three lines, recomputes
|
|
4125
|
+
* `Poseidon(amount, publicKey, blinding, mint)` and requires it to equal a commitment the
|
|
4126
|
+
* transaction actually published. That equality is the authentication: it is not a heuristic, and a
|
|
4127
|
+
* wrong `nk` cannot produce it. Zero extra bytes on chain, no new envelope, no relay change.
|
|
4128
|
+
*
|
|
4129
|
+
* This is the same shape already blessed for swap timeout refunds in `core/swap-refund.ts`
|
|
4130
|
+
* (PRF(nk, nullifier0)), for the same reason: unrecoverable randomness becomes recoverable
|
|
4131
|
+
* randomness without changing anything an observer can see.
|
|
4132
|
+
*
|
|
4133
|
+
* ── The consequence to be explicit about ──────────────────────────────────────────────────────
|
|
4134
|
+
* The deposit note is owned by a PER-DEPOSIT key rather than by the wallet's single long-lived UTXO
|
|
4135
|
+
* keypair. It is still fully spendable — the wallet re-derives `privateKey` from `nk` whenever it
|
|
4136
|
+
* needs it — and it is strictly better for privacy, because `outPubkey0` no longer links a wallet's
|
|
4137
|
+
* deposits to each other. But it means the caller MUST persist (or be able to re-derive) `nk`, which
|
|
4138
|
+
* a Cloak wallet already does, and it means `transact` must be given an explicit `nk` rather than
|
|
4139
|
+
* inferring one from the output note's own key. `transact` enforces that rather than trusting it.
|
|
4140
|
+
*
|
|
4141
|
+
* ── Guardrails preserved ──────────────────────────────────────────────────────────────────────
|
|
4142
|
+
* [L-02] the public key comes from the capacity-tagged `derivePublicKey`. [H-04-shaped] a zero
|
|
4143
|
+
* public key or blinding would be an unspendable note, so the reduction forces non-zero and the
|
|
4144
|
+
* derivation refuses to return a degenerate pair. [M-04] `noteSalt` stays a private input to
|
|
4145
|
+
* `chainNoteHash`; it is only ever published inside the note's own authenticated ciphertext.
|
|
4146
|
+
*/
|
|
4147
|
+
|
|
4148
|
+
/**
|
|
4149
|
+
* The chain note salt is 96 bits — the circuit constrains it with `Num2Bits(96)` and `transact`
|
|
4150
|
+
* generates exactly 12 bytes. A salt outside that range would produce a proof the circuit rejects,
|
|
4151
|
+
* so it is refused here rather than at proof time.
|
|
4152
|
+
*/
|
|
4153
|
+
declare const CHAIN_NOTE_SALT_BITS = 96;
|
|
4154
|
+
/** The secrets a deposit note is built from — and the ones a cold scan re-derives. */
|
|
4155
|
+
interface DepositNoteSecrets {
|
|
4156
|
+
keypair: UtxoKeypair;
|
|
4157
|
+
blinding: bigint;
|
|
4158
|
+
}
|
|
4159
|
+
/** A deposit note recovered from `nk` alone, in spendable form. */
|
|
4160
|
+
interface RecoveredDepositNote extends DepositNoteSecrets {
|
|
4161
|
+
amount: bigint;
|
|
4162
|
+
mintAddress: PublicKey;
|
|
4163
|
+
/** The commitment the transaction published, reproduced from the derived secrets. */
|
|
4164
|
+
commitment: bigint;
|
|
4165
|
+
/** The salt the chain note carried, which anchored the derivation. */
|
|
4166
|
+
noteSalt: bigint;
|
|
4167
|
+
}
|
|
4168
|
+
/** A fresh 96-bit chain-note salt, from the same fail-closed source `transact` uses. */
|
|
4169
|
+
declare function randomDepositNoteSalt(): bigint;
|
|
4170
|
+
/**
|
|
4171
|
+
* Derive a deposit note's keypair and blinding from `(nk, noteSalt)`.
|
|
4172
|
+
*
|
|
4173
|
+
* Deterministic by design: this is the whole reason a cold scan can rebuild the note. Both the
|
|
4174
|
+
* builder and the scanner call it, so there is exactly one definition of what a deposit note is.
|
|
4175
|
+
*/
|
|
4176
|
+
declare function deriveDepositNoteSecrets(viewingKeyNk: Uint8Array, noteSalt: bigint): Promise<DepositNoteSecrets>;
|
|
4177
|
+
/**
|
|
4178
|
+
* Build a deposit output note that a cold `(rpc, programId, nk)` scan can recover.
|
|
4179
|
+
*
|
|
4180
|
+
* Returns the UTXO to pass as `outputUtxos[0]` AND the salt that anchored it. The SAME salt must be
|
|
4181
|
+
* handed to `transact` as `options.chainNoteSalt`, because the chain note is what publishes it — a
|
|
4182
|
+
* salt that does not reach the note leaves the deposit exactly as undiscoverable as before.
|
|
4183
|
+
*
|
|
4184
|
+
* ```ts
|
|
4185
|
+
* const { utxo, noteSalt } = await createRecoverableDepositUtxo(amount, nk, mint);
|
|
4186
|
+
* await transact({ ..., outputUtxos: [utxo], externalAmount: amount },
|
|
4187
|
+
* { chainNoteViewingKeyNk: nk, chainNoteSalt: noteSalt, ... });
|
|
4188
|
+
* ```
|
|
4189
|
+
*/
|
|
4190
|
+
declare function createRecoverableDepositUtxo(amount: bigint, viewingKeyNk: Uint8Array, mintAddress?: PublicKey, noteSalt?: bigint): Promise<{
|
|
4191
|
+
utxo: Utxo;
|
|
4192
|
+
noteSalt: bigint;
|
|
4193
|
+
}>;
|
|
4194
|
+
interface MatchDepositNoteParams {
|
|
4195
|
+
/** The scanning wallet's incoming viewing base. */
|
|
4196
|
+
viewingKeyNk: Uint8Array;
|
|
4197
|
+
/** `noteSalt`, read out of the decrypted chain note. */
|
|
4198
|
+
noteSalt: bigint;
|
|
4199
|
+
/** Candidate note amount — for a shield with no inputs this is the public deposit amount. */
|
|
4200
|
+
amount: bigint;
|
|
4201
|
+
/** Pool mint the commitment was computed under. */
|
|
4202
|
+
mintAddress: PublicKey;
|
|
4203
|
+
/** Output commitments the transaction actually published, hex or field elements. */
|
|
4204
|
+
outputCommitments: Array<string | bigint>;
|
|
4205
|
+
}
|
|
4206
|
+
/**
|
|
4207
|
+
* Decide whether a deposit's published commitment is one this `nk` can rebuild, and if so return
|
|
4208
|
+
* the note in spendable form. Returns `null` for everything that is not ours.
|
|
4209
|
+
*
|
|
4210
|
+
* The commitment equality is the authentication. Nothing here trusts the chain note's own claim
|
|
4211
|
+
* about what it describes; the note only supplies `noteSalt`, and the derived secrets have to
|
|
4212
|
+
* reproduce a value the transaction published or the candidate is discarded.
|
|
4213
|
+
*/
|
|
4214
|
+
declare function matchDepositNote(params: MatchDepositNoteParams): Promise<RecoveredDepositNote | null>;
|
|
4215
|
+
|
|
4216
|
+
/**
|
|
4217
|
+
* Swap timeout-refund discovery (VK-02).
|
|
4218
|
+
*
|
|
4219
|
+
* ── The defect this closes ────────────────────────────────────────────────────────────────────
|
|
4220
|
+
* When a private swap exhausts its retry budget, `CloseSwapState` appends
|
|
4221
|
+
*
|
|
4222
|
+
* R_fb = Poseidon(amount_after_fee, refund_pubkey, refund_blinding, field(WSOL))
|
|
4223
|
+
*
|
|
4224
|
+
* to the main pool tree. `swapUtxo` generated `refund_pubkey` / `refund_blinding` from raw
|
|
4225
|
+
* randomness and returned them only in the in-memory `UtxoSwapResult.refund`. Lose that object —
|
|
4226
|
+
* a crashed tab, a different device, a scan from cold key material — and the leaf is real, funded,
|
|
4227
|
+
* and permanently unrecoverable: campaign row VK-02 observed R_fb under BOTH the refund note's own
|
|
4228
|
+
* viewing key and the swap creator's, and found nothing under either.
|
|
4229
|
+
*
|
|
4230
|
+
* ── The fix, and why it is SDK-only ───────────────────────────────────────────────────────────
|
|
4231
|
+
* Derive the refund authorization as a PRF of the owner's `nk` and the swap's own first input
|
|
4232
|
+
* nullifier:
|
|
4233
|
+
*
|
|
4234
|
+
* seed = BLAKE3("cloak_swap_refund_v1" || nk || nullifier0)
|
|
4235
|
+
*
|
|
4236
|
+
* `nullifier0` is published in the TransactSwap public inputs, so a cold scanner enumerating
|
|
4237
|
+
* program transactions can replay this derivation for every swap it sees, recompute R_fb, and
|
|
4238
|
+
* match it against the commitment `CloseSwapState` declared. `nk` is secret, so no third party can
|
|
4239
|
+
* predict, front-run or link the refund key — the on-chain artefacts are unchanged in shape and
|
|
4240
|
+
* every existing binding still holds.
|
|
4241
|
+
*
|
|
4242
|
+
* This changes NOTHING on chain or in the relay. `refund_pubkey` / `refund_blinding` remain
|
|
4243
|
+
* caller-supplied values bound into `computeSwapExtDataHash`; only their provenance changes, from
|
|
4244
|
+
* "unrecoverable randomness" to "recoverable randomness".
|
|
4245
|
+
*
|
|
4246
|
+
* ── Guardrails preserved ──────────────────────────────────────────────────────────────────────
|
|
4247
|
+
* [H-04 / DD-01] the derivation is rejected unless publicKey and blinding are both non-zero, and
|
|
4248
|
+
* it is unique per swap because `nullifier0` is unique per spend. [L-02] the public key comes from
|
|
4249
|
+
* the capacity-tagged `derivePublicKey`, matching `keypair.circom`.
|
|
4250
|
+
*
|
|
4251
|
+
* ── FORWARD-LOOKING ONLY ──────────────────────────────────────────────────────────────────────
|
|
4252
|
+
* Everything here — including `discoverSwapRefunds`, the RPC walker at the bottom of this file —
|
|
4253
|
+
* recovers only refunds whose authorization was DERIVED. Swaps already on chain whose refund keypair
|
|
4254
|
+
* and blinding came from raw randomness stay unrecoverable, permanently, by any key-only scan. There
|
|
4255
|
+
* is nothing to replay: those secrets existed only in the caller's in-memory `UtxoSwapResult.refund`.
|
|
4256
|
+
* Do not let this note get softened — a wallet that reports "no stranded refunds" on the strength of
|
|
4257
|
+
* an empty scan would be making a claim this code cannot support.
|
|
4258
|
+
*/
|
|
4259
|
+
|
|
4260
|
+
/** A refund authorization: what `swapUtxo` binds into the swap ext-data hash. */
|
|
4261
|
+
interface SwapRefundAuthorization {
|
|
4262
|
+
privateKey: bigint;
|
|
4263
|
+
publicKey: bigint;
|
|
4264
|
+
blinding: bigint;
|
|
4265
|
+
}
|
|
4266
|
+
/** A refund leaf matched back to its owner, ready to spend. */
|
|
4267
|
+
interface RecoveredSwapRefund {
|
|
4268
|
+
keypair: UtxoKeypair;
|
|
4269
|
+
blinding: bigint;
|
|
4270
|
+
amount: bigint;
|
|
4271
|
+
/** R_fb as a field element, identical to the commitment `CloseSwapState` appended. */
|
|
4272
|
+
commitment: bigint;
|
|
4273
|
+
}
|
|
4274
|
+
/**
|
|
4275
|
+
* Derive a swap's refund authorization from the owner's `nk` and the swap's first input nullifier.
|
|
4276
|
+
*
|
|
4277
|
+
* Deterministic by design: this is what makes the refund leaf recoverable from key material alone.
|
|
4278
|
+
*/
|
|
4279
|
+
declare function deriveSwapRefundAuthorization(viewingKeyNk: Uint8Array, inputNullifier: Uint8Array | bigint): Promise<SwapRefundAuthorization>;
|
|
4280
|
+
/**
|
|
4281
|
+
* R_fb, exactly as `CloseSwapState` computes it. The swap principal is always WSOL-locked, so the
|
|
4282
|
+
* mint term is the native-SOL sentinel.
|
|
4283
|
+
*/
|
|
4284
|
+
declare function computeSwapRefundCommitment(amountAfterFee: bigint, refundPublicKey: bigint, refundBlinding: bigint): Promise<bigint>;
|
|
4285
|
+
interface MatchSwapRefundLeafParams {
|
|
4286
|
+
/** The scanning wallet's incoming viewing base. */
|
|
4287
|
+
viewingKeyNk: Uint8Array;
|
|
4288
|
+
/** First input nullifier of the candidate swap, read from its TransactSwap public inputs. */
|
|
4289
|
+
inputNullifier: Uint8Array | bigint;
|
|
4290
|
+
/** Principal returned to the pool, from the `cloak/refund_leaf/v1` event. */
|
|
4291
|
+
amountAfterFee: bigint;
|
|
4292
|
+
/** R_fb as appended on chain. */
|
|
4293
|
+
commitment: bigint | Uint8Array;
|
|
4294
|
+
}
|
|
4295
|
+
/**
|
|
4296
|
+
* Decide whether a public refund leaf belongs to the holder of `viewingKeyNk`, and if so return it
|
|
4297
|
+
* in spendable form. Returns `null` for every leaf that is not ours — a scanner runs this against
|
|
4298
|
+
* every close it can see.
|
|
4299
|
+
*/
|
|
4300
|
+
declare function matchSwapRefundLeaf(params: MatchSwapRefundLeafParams): Promise<RecoveredSwapRefund | null>;
|
|
4301
|
+
/** One recovered refund leaf, with the chain coordinates that produced it. */
|
|
4302
|
+
interface DiscoveredSwapRefund extends RecoveredSwapRefund {
|
|
4303
|
+
/** Signature of the `CloseSwapState` transaction that appended the leaf. */
|
|
4304
|
+
signature: string;
|
|
4305
|
+
/** Leaf index, straight from the `cloak/refund_leaf/v1` event. */
|
|
4306
|
+
leafIndex: bigint;
|
|
4307
|
+
/** First input nullifier of the swap this refund belongs to (the PRF's second input). */
|
|
4308
|
+
inputNullifier: Uint8Array;
|
|
4309
|
+
/** Signature of the `TransactSwap` that opened the swap, when it was inside the scanned window. */
|
|
4310
|
+
swapSignature?: string;
|
|
4311
|
+
}
|
|
4312
|
+
interface DiscoverSwapRefundsOptions {
|
|
4313
|
+
/** Maximum program signatures to walk. Omit or 0 to walk the whole history. */
|
|
4314
|
+
limit?: number;
|
|
4315
|
+
/** Stop when this signature is reached (exclusive) — the cursor from a previous run. */
|
|
4316
|
+
untilSignature?: string;
|
|
4317
|
+
/** `getTransaction` concurrency (default 50). */
|
|
4318
|
+
batchSize?: number;
|
|
4319
|
+
/** Progress/status callback. */
|
|
4320
|
+
onStatus?: (status: string) => void;
|
|
4321
|
+
/**
|
|
4322
|
+
* Pool mint whose `swap_state` PDAs to derive. Swap input is wSOL-locked, so the default is the
|
|
4323
|
+
* native-SOL sentinel and there is no reason to change it outside a test.
|
|
4324
|
+
*/
|
|
4325
|
+
poolMint?: PublicKey;
|
|
4326
|
+
}
|
|
4327
|
+
/**
|
|
4328
|
+
* Find every swap timeout-refund leaf on chain that belongs to the holder of `viewingKeyNk`, and
|
|
4329
|
+
* return each in spendable form (`keypair`, `blinding`, `amount`, `commitment`).
|
|
4330
|
+
*
|
|
4331
|
+
* Key material only — no relay, no local note store, no prior knowledge of the swap. This is the
|
|
4332
|
+
* recovery path for the exact situation VK-02 described: `CloseSwapState` appended a funded leaf and
|
|
4333
|
+
* its owner could not see it under any key they held.
|
|
4334
|
+
*
|
|
4335
|
+
* FORWARD-LOOKING ONLY. This finds refunds whose authorization was DERIVED as PRF(nk, nullifier0).
|
|
4336
|
+
* A swap whose refund keypair and blinding were drawn from raw randomness — every swap submitted
|
|
4337
|
+
* before that derivation shipped, and any swap built without an `nk` — has no derivation to replay,
|
|
4338
|
+
* and no key-only scan can ever recover it. Those secrets existed solely in the caller's in-memory
|
|
4339
|
+
* `UtxoSwapResult.refund`. An empty result is therefore NOT proof that a wallet has no stranded
|
|
4340
|
+
* refund; it means none of the leaves in the scanned window were derivable from this `nk`.
|
|
4341
|
+
*
|
|
4342
|
+
* @param connection Solana RPC connection.
|
|
4343
|
+
* @param programId Shield-pool program id.
|
|
4344
|
+
* @param viewingKeyNk 32-byte `nk` — the same value chain notes are decrypted with.
|
|
4345
|
+
*/
|
|
4346
|
+
declare function discoverSwapRefunds(connection: Connection, programId: PublicKey, viewingKeyNk: Uint8Array, options?: DiscoverSwapRefundsOptions): Promise<DiscoveredSwapRefund[]>;
|
|
4347
|
+
|
|
4348
|
+
/**
|
|
4349
|
+
* Compact deterministic chain note format.
|
|
4350
|
+
*
|
|
4351
|
+
* Envelope: [version 1][ciphertext (plaintext + AES-GCM tag)]
|
|
4352
|
+
*
|
|
4353
|
+
* Plaintext layout by version:
|
|
4354
|
+
* - v3: [timestamp: u64 LE (8)][noteSalt: u256 BE (32)]
|
|
4355
|
+
* - v2: [timestamp: u64 LE (8)]
|
|
3261
4356
|
*/
|
|
3262
4357
|
type ChainNoteTxType = "deposit" | "withdraw" | "transfer" | "swap" | "unknown";
|
|
3263
4358
|
interface CompactChainNote {
|
|
3264
4359
|
timestamp: bigint;
|
|
3265
4360
|
commitment: string;
|
|
4361
|
+
noteSalt?: bigint;
|
|
4362
|
+
/** v4 only: the three terms that make up `noteSemantics`. */
|
|
4363
|
+
outAmount0?: bigint;
|
|
4364
|
+
outPubkey0?: bigint;
|
|
4365
|
+
isSendToSelfKey0?: bigint;
|
|
3266
4366
|
}
|
|
3267
4367
|
/**
|
|
3268
4368
|
* Encrypt a compact deterministic chain note.
|
|
3269
|
-
*
|
|
3270
|
-
*
|
|
4369
|
+
*
|
|
4370
|
+
* v3 carries noteSalt so the recipient can recompute and verify the public
|
|
4371
|
+
* chainNoteHash without making the output commitment linkable by observers.
|
|
3271
4372
|
*/
|
|
3272
|
-
declare function encryptCompactChainNote(timestamp: bigint, nk: Uint8Array, commitmentHex: string
|
|
4373
|
+
declare function encryptCompactChainNote(timestamp: bigint, nk: Uint8Array, commitmentHex: string, noteSalt: bigint, semantics?: {
|
|
4374
|
+
outAmount0: bigint;
|
|
4375
|
+
outPubkey0: bigint;
|
|
4376
|
+
isSendToSelfKey0: bigint;
|
|
4377
|
+
}): Promise<Uint8Array>;
|
|
3273
4378
|
/**
|
|
3274
4379
|
* Decrypt a compact deterministic chain note with candidate output commitments.
|
|
3275
4380
|
* Tries each commitment-derived key until AES-GCM authentication succeeds.
|
|
4381
|
+
* Accepts current v3 notes and legacy v2 notes.
|
|
3276
4382
|
*/
|
|
3277
4383
|
declare function decryptCompactChainNote(noteBytes: Uint8Array, nk: Uint8Array, candidateCommitments: string[]): Promise<CompactChainNote>;
|
|
3278
4384
|
declare function chainNoteToBase64(noteBytes: Uint8Array): string;
|
|
@@ -3341,6 +4447,30 @@ interface ScanResult {
|
|
|
3341
4447
|
lastSignature?: string;
|
|
3342
4448
|
/** Number of RPC getTransaction calls actually made (for diagnostics). */
|
|
3343
4449
|
rpcCallsMade: number;
|
|
4450
|
+
/**
|
|
4451
|
+
* Notes delivered TO this wallet by other people's shield-to-shield sends, recovered from the
|
|
4452
|
+
* CLKD1 registry. These are SPENDABLE note secrets, not history records — they are intentionally
|
|
4453
|
+
* kept out of `transactions`/`summary` so the compliance report shape is unchanged.
|
|
4454
|
+
*/
|
|
4455
|
+
deliveredNotes: DeliveredNote[];
|
|
4456
|
+
/**
|
|
4457
|
+
* This wallet's OWN deposits, rebuilt in SPENDABLE form from `nk` alone (VK-01).
|
|
4458
|
+
*
|
|
4459
|
+
* `transactions` records that a deposit happened and for how much. These carry the keypair and
|
|
4460
|
+
* blinding as well, which is the difference between seeing a note and being able to spend it.
|
|
4461
|
+
* Only deposits built with {@link createRecoverableDepositUtxo} appear here; a deposit whose
|
|
4462
|
+
* blinding came from raw randomness has nothing to rebuild and shows up as a history row only.
|
|
4463
|
+
*
|
|
4464
|
+
* Additive, for the same reason as `deliveredNotes`: note secrets are not compliance rows.
|
|
4465
|
+
*/
|
|
4466
|
+
recoveredDepositNotes: RecoveredDepositNoteRecord[];
|
|
4467
|
+
}
|
|
4468
|
+
/** A recovered deposit note plus the chain coordinates it was recovered from. */
|
|
4469
|
+
interface RecoveredDepositNoteRecord extends RecoveredDepositNote {
|
|
4470
|
+
/** Signature of the deposit transaction. */
|
|
4471
|
+
signature: string;
|
|
4472
|
+
/** Millisecond timestamp from the chain note. */
|
|
4473
|
+
timestamp: bigint;
|
|
3344
4474
|
}
|
|
3345
4475
|
/** Options for `scanTransactions`. */
|
|
3346
4476
|
interface ScanOptions {
|
|
@@ -3376,15 +4506,74 @@ interface ScanOptions {
|
|
|
3376
4506
|
* matching the wallet's associated token account against the on-chain recipient ATA.
|
|
3377
4507
|
*/
|
|
3378
4508
|
walletPublicKey?: string;
|
|
4509
|
+
/**
|
|
4510
|
+
* Sweep the CLKD1 recipient-delivery registry for notes sent TO this wallet (default: true).
|
|
4511
|
+
* Costs one extra `getSignaturesForAddress` page plus one `getTransaction` per carrier.
|
|
4512
|
+
*/
|
|
4513
|
+
includeRecipientDeliveries?: boolean;
|
|
4514
|
+
/**
|
|
4515
|
+
* This wallet's UTXO public key. Supply it to authenticate each delivery carrier's declared
|
|
4516
|
+
* commitment (see `ScanRecipientDeliveryOptions.ownerUtxoPublicKey`). Without it the commitment
|
|
4517
|
+
* is reported unverified.
|
|
4518
|
+
*/
|
|
4519
|
+
ownerUtxoPublicKey?: bigint;
|
|
4520
|
+
/** Candidate pool mints for delivery-carrier commitment verification. Defaults to native SOL. */
|
|
4521
|
+
deliveryMints?: PublicKey[];
|
|
4522
|
+
}
|
|
4523
|
+
/** A note delivered TO the scanning wallet by someone else's shield-to-shield send. */
|
|
4524
|
+
interface DeliveredNote {
|
|
4525
|
+
/** Output commitment (lowercase hex) the carrier declares. */
|
|
4526
|
+
commitment: string;
|
|
4527
|
+
/** Note amount, decrypted from the envelope. */
|
|
4528
|
+
amount: bigint;
|
|
4529
|
+
/** Note blinding, decrypted from the envelope. */
|
|
4530
|
+
blinding: bigint;
|
|
4531
|
+
/** Carrier transaction signature (the discovery record, NOT the source transaction). */
|
|
4532
|
+
carrierSignature: string;
|
|
4533
|
+
/** Carrier block time in seconds, when the RPC supplied one. */
|
|
4534
|
+
blockTime?: number;
|
|
4535
|
+
/**
|
|
4536
|
+
* Pool mint the note lives in, resolved by recomputing the commitment. Present only when
|
|
4537
|
+
* `ownerUtxoPublicKey` was supplied — without it the declared commitment cannot be checked.
|
|
4538
|
+
*/
|
|
4539
|
+
mint?: string;
|
|
4540
|
+
/**
|
|
4541
|
+
* True when the declared commitment was recomputed from the decrypted note and matched. The
|
|
4542
|
+
* memo's commitment field is unauthenticated; only this recomputation binds it.
|
|
4543
|
+
*/
|
|
4544
|
+
commitmentVerified: boolean;
|
|
4545
|
+
}
|
|
4546
|
+
interface ScanRecipientDeliveryOptions {
|
|
4547
|
+
connection: Connection;
|
|
4548
|
+
programId: PublicKey;
|
|
4549
|
+
/** nk (32 bytes). The X25519 opening key is `deriveViewingKeyFromNk(nk).privateKey`. */
|
|
4550
|
+
viewingKeyNk: Uint8Array;
|
|
4551
|
+
/**
|
|
4552
|
+
* The scanning wallet's UTXO public key. Supply it to authenticate the carrier's declared
|
|
4553
|
+
* commitment: the memo's commitment field is written by the relay and is not covered by the
|
|
4554
|
+
* envelope's Poly1305 tag, so a carrier can claim any commitment it likes. With this set, a
|
|
4555
|
+
* carrier survives only if `Poseidon(amount, ownerPubkey, blinding, mint)` reproduces it.
|
|
4556
|
+
*/
|
|
4557
|
+
ownerUtxoPublicKey?: bigint;
|
|
4558
|
+
/** Candidate pool mints to try when verifying. Defaults to the native-SOL sentinel. */
|
|
4559
|
+
mints?: PublicKey[];
|
|
4560
|
+
onStatus?: (status: string) => void;
|
|
4561
|
+
debug?: boolean;
|
|
3379
4562
|
}
|
|
3380
4563
|
/**
|
|
3381
|
-
*
|
|
3382
|
-
*
|
|
3383
|
-
* and return a sorted list of the caller's transactions.
|
|
4564
|
+
* Sweep the recipient-delivery registry (CLKD1) and trial-open every carrier with the caller's
|
|
4565
|
+
* viewing key.
|
|
3384
4566
|
*
|
|
3385
|
-
* This is
|
|
3386
|
-
*
|
|
4567
|
+
* This is the READ half of the fix for S2S-08 / VK-01. The pre-existing sweep
|
|
4568
|
+
* (`scanSwapNoteCarriers`) reads the CLK1 registry, which carries the SENDER-encrypted compliance
|
|
4569
|
+
* note — which is why a cold scan found the withdrawal change note and nothing that was sent TO the
|
|
4570
|
+
* scanning wallet. This registry carries the recipient-encrypted envelope, and is the only place a
|
|
4571
|
+
* recipient's own note is discoverable from `(rpc, programId, nk)` alone.
|
|
3387
4572
|
*/
|
|
4573
|
+
declare function scanRecipientDeliveryNotes(opts: ScanRecipientDeliveryOptions): Promise<{
|
|
4574
|
+
notes: DeliveredNote[];
|
|
4575
|
+
rpcCalls: number;
|
|
4576
|
+
}>;
|
|
3388
4577
|
declare function scanTransactions(opts: ScanOptions): Promise<ScanResult>;
|
|
3389
4578
|
/** JSON-serializable compliance report (numbers instead of bigint). Used for cache and display. */
|
|
3390
4579
|
interface ComplianceReport {
|
|
@@ -3560,8 +4749,8 @@ declare class SimpleWallet {
|
|
|
3560
4749
|
* @packageDocumentation
|
|
3561
4750
|
*/
|
|
3562
4751
|
|
|
3563
|
-
declare const VERSION = "0.
|
|
4752
|
+
declare const VERSION = "0.2.0";
|
|
3564
4753
|
/** True when scanner supports TransactSwap (tag 1). Check this to verify the correct SDK bundle is loaded. */
|
|
3565
4754
|
declare const SCANNER_SUPPORTS_TRANSACT_SWAP = true;
|
|
3566
4755
|
|
|
3567
|
-
export { CLOAK_PROGRAM_ID, type ChainNoteTxType, type CircuitVerificationResult, type CloakConfig, CloakError, type CloakKeyPair, type CloakNote, CloakSDK, type CommitmentEntry, type CommitmentsResponse, type CompactChainNote, type ComplianceReport, type ComplianceTxType, DEFAULT_CIRCUITS_URL, DEFAULT_TRANSACTION_CIRCUITS_URL, type DepositInstructionParams, type DepositOptions, type DepositResult, type DepositStatus, EXPECTED_CIRCUIT_HASHES, type EncryptedMetadataBundle, type EncryptedNote$1 as EncryptedNote, type ErrorCategory, type ExpandedSpendKey, FIXED_FEE_LAMPORTS, type Groth16Proof, LAMPORTS_PER_SOL, LocalStorageAdapter, type LogLevel, type Logger, MERKLE_TREE_HEIGHT, MIN_DEPOSIT_LAMPORTS, type MasterKey, type MaxLengthArray, MemoryStorageAdapter, type MerkleProof, type MerkleRootResponse, MerkleTree, NATIVE_SOL_MINT, type Network, type NoteData, type OnchainMerkleProof, type PendingDeposit, type PendingWithdrawal, type ProofResult, RelayInternalError, RelayService, type RiskQuoteInstructionResponse, RootNotFoundError, SCANNER_SUPPORTS_TRANSACT_SWAP, SIGN_IN_MESSAGE, SanctionsQuoteError, type ScanOptions, type ScanResult, type ScanSummary, type ScannedTransaction, ShieldPoolErrors, type ShieldPoolPDAs, SimpleWallet, type SpendKey, type StorageAdapter, type SwapOptions, type SwapParams, type SwapResult, type TransactOptions, type TransactParams, type TransactResult, type TransactionMetadata, type Transfer, type TransferOptions, type TransferResult, type TxStatus, type UserFriendlyError, type Utxo, UtxoAlreadySpentError, type EncryptedNote as UtxoEncryptedNote, type UtxoKeypair, type UtxoSwapParams, type UtxoSwapResult, UtxoWallet, VARIABLE_FEE_DENOMINATOR, VARIABLE_FEE_NUMERATOR, VARIABLE_FEE_RATE, VERSION, type VerifyUtxosResult, type ViewKey, type ViewingKeyPair, type WalletAdapter, type WalletUtxo, type WithdrawOptions, type WithdrawRegularInputs, type WithdrawSubmissionResult, type WithdrawSwapInputs, areCircuitsAvailable, bigintToBytes32$1 as bigintToBytes32, bigintToHex, buildMerkleTree, buildMerkleTreeFromChain, buildMerkleTreeFromRelay, buildPublicInputsBytes, bytesToHex, calculateFee, calculateFeeBigint, calculateRelayFee, chainNoteFromBase64, chainNoteToBase64, classifyRelayError, cleanupStalePendingOperations, clearPendingDeposits, clearPendingWithdrawals, computeChainNoteHash, computeCommitment$1 as computeCommitment, computeExtDataHash, computeMerkleRoot, computeNullifier$1 as computeNullifier, computeNullifierAsync, computeNullifierSync, computeOutputsHash, computeOutputsHashAsync, computeOutputsHashSync, computeProofForLatestDeposit, computeProofFromChain, computeSignature, computeSwapOutputsHash, computeSwapOutputsHashAsync, computeSwapOutputsHashSync, computeCommitment as computeUtxoCommitment, computeNullifier as computeUtxoNullifier, copyNoteToClipboard, createCloakError, createDepositInstruction, createLogger, createUtxo, createZeroUtxo, decryptCompactChainNote, decryptComplianceMetadataWithMasterKey, decryptTransactionMetadata, deriveDiversifiedViewingKey, deriveDiversifier, derivePublicKey, deriveSpendKey, deriveUserCompliancePublicKey, deriveUserComplianceScalar, deriveUtxoKeypairFromSpendKey, deriveViewKey, deriveViewingKeyFromNk, deriveViewingKeyFromSpendKey, deriveViewingKeyFromUtxoPrivateKey, deserializeUtxo, detectNetworkFromRpcUrl, downloadNote, encodeNoteSimple, encryptCompactChainNote, encryptNoteForRecipient, encryptTransactionMetadata, encryptTransactionMetadataBundle, expandSpendKey, exportKeys, exportNote, exportWalletKeys, fetchCommitments, fetchRiskQuoteInstruction, fetchRiskQuoteIx, filterNotesByNetwork, filterWithdrawableNotes, findNoteByCommitment, formatAmount, formatComplianceCsv, formatErrorForLogging, formatSol, fullWithdraw, generateCloakKeys, generateCommitment, generateCommitmentAsync, generateMasterSeed, generateNote, generateNoteFromWallet, generateUtxoKeypair, generateViewingKeyPair, generateWithdrawRegularProof, generateWithdrawSwapProof, getAddressExplorerUrl, getCircuitsPath, getDefaultCircuitsPath, getDistributableAmount, getExplorerUrl, getNkFromUtxoPrivateKey, getNullifierPDA, getPendingOperationsSummary, getPublicKey, getPublicViewKey, getRecipientAmount, getRpcUrlForNetwork, getShieldPoolPDAs, getSwapStatePDA, getViewKey, hasPendingOperations, hexToBigint$1 as hexToBigint, hexToBytes, importKeys, importWalletKeys, isDebugEnabled, isRootNotFoundError, isValidHex, isValidRpcUrl, isValidSolanaAddress, isWithdrawAmountSufficient, isWithdrawable, keypairToAdapter, loadPendingDeposits, loadPendingWithdrawals, parseAmount, parseError, parseNote, parseRelayErrorResponse, parseTransactionError, partialWithdraw, poseidonHash, preflightCheck, preflightNullifiers, prepareEncryptedOutput, prepareEncryptedOutputForRecipient, proofToBytes, pubkeyToFieldElement, pubkeyToLimbs, randomBytes, randomFieldElement, readMerkleTreeState, registerViewingKey, removePendingDeposit, removePendingWithdrawal, savePendingDeposit, savePendingWithdrawal, scanNotesForWallet, scanTransactions, sdkLogger, selectUtxos, sendTransaction, serializeNote, serializeUtxo, setCircuitsPath, setDebugMode, signTransaction, splitTo2Limbs, sumUtxoAmounts, swapUtxo, swapWithChange, toComplianceReport, transact, transfer, truncate, tryDecryptNote, updateNoteWithDeposit, updatePendingDeposit, updatePendingWithdrawal, bigintToBytes32 as utxoBigintToBytes32, utxoEquals, hexToBigint as utxoHexToBigint, validateDepositParams, validateNote, validateOutputsSum, validateRoot, validateTransfers, validateWalletConnected, validateWithdrawableNote, verifyAllCircuits, verifyCircuitIntegrity, verifyUtxos, waitForRoot, withTiming };
|
|
4756
|
+
export { type BuildRecipientDeliveryNotesParams, CHAIN_NOTE_SALT_BITS, CLOAK_PROGRAM_ID, type ChainNoteTxType, type CircuitName, type CircuitVerificationResult, type ClaimTimeoutRefundOptions, type ClaimTimeoutRefundParams, type ClaimTimeoutRefundResult, type CloakConfig, CloakError, type CloakKeyPair, type CloakNote, CloakSDK, type CommitmentEntry, type CommitmentsResponse, type CompactChainNote, type ComplianceReport, type ComplianceTxType, type ConfirmSettlementParams, DEFAULT_CIRCUITS_URL, DEFAULT_TRANSACTION_CIRCUITS_URL, DELIVERY_MEMO_TAG, DELIVERY_REGISTRY_SEED, type DeliveredNote, type DepositInstructionParams, type DepositNoteSecrets, type DepositOptions, type DepositResult, type DepositStatus, type DiscoverSwapRefundsOptions, type DiscoveredSwapRefund, EXPECTED_CIRCUIT_HASHES, type EncryptedMetadataBundle, type EncryptedNote$1 as EncryptedNote, type ErrorCategory, type ExpandedSpendKey, FIXED_FEE_LAMPORTS, type Groth16Proof, InsecureRandomnessError, LAMPORTS_PER_SOL, LocalStorageAdapter, type LogLevel, type Logger, MERKLE_TREE_HEIGHT, MIN_DEPOSIT_LAMPORTS, type MasterKey, type MatchDepositNoteParams, type MatchSwapRefundLeafParams, type MaxLengthArray, MemoryStorageAdapter, type MerkleProof, type MerkleRootResponse, MerkleTree, NATIVE_SOL_MINT, type Network, type NoteData, type OnchainMerkleProof, type ParsedDeliveryCarrier, type PendingDeposit, type PendingWithdrawal, type ProofResult, RECIPIENT_DELIVERY_CIPHERTEXT_LEN, RECIPIENT_DELIVERY_EPHEMERAL_PK_LEN, RECIPIENT_DELIVERY_NONCE_LEN, RECIPIENT_DELIVERY_NOTE_BYTES, RECIPIENT_DELIVERY_PLAINTEXT_LEN, RECIPIENT_DELIVERY_TAG_LEN, type RecipientDeliveryNote, type RecoveredDepositNote, type RecoveredDepositNoteRecord, type RecoveredSwapRefund, type RefundVoucher, RelayInternalError, RelayService, type RelaySubmissionResult, type RiskQuoteInstructionResponse, RootNotFoundError, SCANNER_SUPPORTS_TRANSACT_SWAP, SIGN_IN_MESSAGE, SanctionsQuoteError, type ScanOptions, type ScanRecipientDeliveryOptions, type ScanResult, type ScanSummary, type ScannedTransaction, type SettlementConnection, type SettlementContext, type SettlementStatus, type SettlementVerdict, SettlementVerificationError, ShieldPoolErrors, type ShieldPoolPDAs, SimpleWallet, type SpendKey, type StorageAdapter, type SubmitTransactToRelayArgs, type SwapOptions, type SwapParams, type SwapRefundAuthorization, type SwapResult, TRANSACTION_CIRCUITS_VERSION, type TransactOptions, type TransactParams, type TransactRequestBodyParams, type TransactResult, type TransactionMetadata, type Transfer, type TransferOptions, type TransferResult, type TxStatus, type UserFriendlyError, type Utxo, UtxoAlreadySpentError, type EncryptedNote as UtxoEncryptedNote, type UtxoKeypair, type UtxoSwapParams, type UtxoSwapResult, UtxoWallet, VARIABLE_FEE_DENOMINATOR, VARIABLE_FEE_NUMERATOR, VARIABLE_FEE_RATE, VERSION, type VerifyUtxosResult, type ViewKey, type ViewingKeyPair, type WalletAdapter, type WalletUtxo, type WithdrawOptions, type WithdrawRegularInputs, type WithdrawSubmissionResult, type WithdrawSwapInputs, areCircuitsAvailable, assertDirectSubmissionLanded, assertTransactionCircuitIntegrity, bigintToBytes32$1 as bigintToBytes32, bigintToHex, buildMerkleTree, buildMerkleTreeFromChain, buildMerkleTreeFromRelay, buildPublicInputsBytes, buildRecipientDeliveryNotes, buildTransactRequestBody, bytesToHex, calculateFee, calculateFeeBigint, calculateRelayFee, canRebuildMerkleTreeFromChain, chainNoteFromBase64, chainNoteToBase64, claimTimeoutRefund, classifyRelayError, cleanupStalePendingOperations, clearPendingDeposits, clearPendingWithdrawals, computeChainNoteHash, computeCommitment$1 as computeCommitment, computeExtDataHash, computeMerkleRoot, computeNullifier$1 as computeNullifier, computeNullifierAsync, computeNullifierSync, computeOutputsHash, computeOutputsHashAsync, computeOutputsHashSync, computeProofForLatestDeposit, computeProofFromChain, computeSignature, computeSwapOutputsHash, computeSwapOutputsHashAsync, computeSwapOutputsHashSync, computeSwapRefundCommitment, computeCommitment as computeUtxoCommitment, computeNullifier as computeUtxoNullifier, confirmTransactSettlement, copyNoteToClipboard, createCloakError, createDepositInstruction, createLogger, createRecoverableDepositUtxo, createUtxo, createZeroUtxo, decryptCompactChainNote, decryptComplianceMetadataWithMasterKey, decryptTransactionMetadata, deriveDepositNoteSecrets, deriveDiversifiedViewingKey, deriveDiversifier, deriveInputNullifierPdas, derivePublicKey, deriveSpendKey, deriveSwapRefundAuthorization, deriveUserCompliancePublicKey, deriveUserComplianceScalar, deriveUtxoKeypairFromSpendKey, deriveViewKey, deriveViewingKeyFromNk, deriveViewingKeyFromSpendKey, deriveViewingKeyFromUtxoPrivateKey, deserializeUtxo, detectNetworkFromRpcUrl, discoverSwapRefunds, downloadNote, encodeDeliveryCarrierMemo, encodeNoteSimple, encodeRecipientDeliveryNote, encryptCompactChainNote, encryptNoteForRecipient, encryptTransactionMetadata, encryptTransactionMetadataBundle, expandSpendKey, exportKeys, exportNote, exportWalletKeys, fetchCommitments, fetchRiskQuoteInstruction, fetchRiskQuoteIx, filterNotesByNetwork, filterWithdrawableNotes, findNoteByCommitment, formatAmount, formatComplianceCsv, formatErrorForLogging, formatSol, fullWithdraw, generateCloakKeys, generateCommitment, generateCommitmentAsync, generateMasterSeed, generateNote, generateNoteFromWallet, generateUtxoKeypair, generateViewingKeyPair, generateWithdrawRegularProof, generateWithdrawSwapProof, getAddressExplorerUrl, getChainNoteRegistryPDA, getCircuitsPath, getDefaultCircuitsPath, getDeliveryRegistryPDA, getDistributableAmount, getExplorerUrl, getNkFromUtxoPrivateKey, getNullifierPDA, getPendingOperationsSummary, getPoolAuthorityConfigPDA, getPublicKey, getPublicViewKey, getRecipientAmount, getRefundClaimPDA, getRefundLedgerPDA, getRpcUrlForNetwork, getShieldPoolPDAs, getSwapStatePDA, getViewKey, hasPendingOperations, hexToBigint$1 as hexToBigint, hexToBytes, importKeys, importWalletKeys, isBrowser, isBrowserLike, isDebugEnabled, isPlausibleSignature, isReactNative, isRootNotFoundError, isSubmissionOutcomeUnknownResponse, isValidHex, isValidRpcUrl, isValidSolanaAddress, isWithdrawAmountSufficient, isWithdrawable, keypairToAdapter, loadPendingDeposits, loadPendingWithdrawals, loadVerifiedCircuitArtifacts, matchDepositNote, matchSwapRefundLeaf, openRecipientDeliveryNote, parseAmount, parseDeliveryCarrierMemo, parseError, parseNote, parseRelayErrorResponse, parseRelayErrorSignature, parseTransactionError, partialWithdraw, poseidonHash, preflightCheck, preflightNullifiers, prepareEncryptedOutput, prepareEncryptedOutputForRecipient, proofToBytes, pubkeyToFieldElement, pubkeyToLimbs, randomBytes, randomDepositNoteSalt, randomFieldElement, readMerkleTreeState, recipientDeliveryNoteToBase64, registerViewingKey, removePendingDeposit, removePendingWithdrawal, resolveCircuitsBase, savePendingDeposit, savePendingWithdrawal, scanNotesForWallet, scanRecipientDeliveryNotes, scanTransactions, sdkLogger, selectUtxos, sendTransaction, serializeNote, serializeUtxo, setCircuitsPath, setDebugMode, signTransaction, splitTo2Limbs, submitTransactToRelay, sumUtxoAmounts, swapUtxo, swapWithChange, toComplianceReport, transact, transfer, truncate, tryDecryptNote, updateNoteWithDeposit, updatePendingDeposit, updatePendingWithdrawal, bigintToBytes32 as utxoBigintToBytes32, utxoEquals, hexToBigint as utxoHexToBigint, validateDepositParams, validateNote, validateOutputsSum, validateRoot, validateTransfers, validateWalletConnected, validateWithdrawableNote, verifyAllCircuits, verifyCircuitIntegrity, verifyUtxos, waitForRoot, withTiming };
|