@cloak.dev/sdk 0.1.7 → 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/dist/index.d.cts CHANGED
@@ -1,5 +1,376 @@
1
1
  import { PublicKey, Transaction, Connection, AddressLookupTableAccount, TransactionInstruction, Keypair, SendOptions, VersionedTransaction } from '@solana/web3.js';
2
2
 
3
+ type ComplianceTxType = "deposit" | "withdraw" | "send" | "swap";
4
+ interface TransactionMetadata {
5
+ amount: number;
6
+ recipient: string;
7
+ timestamp: number;
8
+ txType: ComplianceTxType;
9
+ commitment: string;
10
+ signature?: string;
11
+ outputMint?: string;
12
+ }
13
+ interface EncryptedMetadataBundle {
14
+ encrypted_user: string;
15
+ encrypted_compliance: string;
16
+ user_pubkey: string;
17
+ commitment: string;
18
+ timestamp: number;
19
+ tx_type?: ComplianceTxType;
20
+ wallet_signature?: string;
21
+ viewing_key?: string;
22
+ }
23
+
24
+ /**
25
+ * Supported Solana networks
26
+ */
27
+ type Network = "localnet" | "devnet" | "mainnet" | "testnet";
28
+ /**
29
+ * Minimal wallet adapter interface
30
+ * Compatible with @solana/wallet-adapter-base
31
+ */
32
+ interface WalletAdapter {
33
+ publicKey: PublicKey | null;
34
+ signTransaction?<T extends Transaction>(transaction: T): Promise<T>;
35
+ signAllTransactions?<T extends Transaction>(transactions: T[]): Promise<T[]>;
36
+ sendTransaction?(transaction: Transaction, connection: any, options?: any): Promise<string>;
37
+ }
38
+ /**
39
+ * Cloak-specific error with categorization
40
+ */
41
+ declare class CloakError extends Error {
42
+ category: "network" | "indexer" | "prover" | "relay" | "validation" | "wallet" | "environment" | "service";
43
+ retryable: boolean;
44
+ originalError?: Error | undefined;
45
+ constructor(message: string, category: "network" | "indexer" | "prover" | "relay" | "validation" | "wallet" | "environment" | "service", retryable?: boolean, originalError?: Error | undefined);
46
+ }
47
+ /**
48
+ * Cloak Note - Represents a private transaction commitment
49
+ *
50
+ * A note contains all the information needed to withdraw funds from the Cloak protocol.
51
+ * Keep this safe and secret - anyone with access to this note can withdraw the funds!
52
+ */
53
+ interface CloakNote {
54
+ /** Protocol version */
55
+ version: string;
56
+ /** Amount in lamports */
57
+ amount: number;
58
+ /** Commitment hash (hex) */
59
+ commitment: string;
60
+ /** Spending secret key (hex, 64 chars) */
61
+ sk_spend: string;
62
+ /** Randomness value (hex, 64 chars) */
63
+ r: string;
64
+ /** Transaction signature from deposit (optional until deposited) */
65
+ depositSignature?: string;
66
+ /** Solana slot when deposited (optional until deposited) */
67
+ depositSlot?: number;
68
+ /** Index in the Merkle tree (optional until deposited) */
69
+ leafIndex?: number;
70
+ /** Historical Merkle root at time of deposit (optional until deposited) */
71
+ root?: string;
72
+ /** Merkle proof at time of deposit (optional until deposited) */
73
+ merkleProof?: MerkleProof;
74
+ /** Creation timestamp */
75
+ timestamp: number;
76
+ /** Network where this note was created */
77
+ network: Network;
78
+ }
79
+ /**
80
+ * Merkle proof for a leaf in the commitment tree
81
+ */
82
+ interface MerkleProof {
83
+ /** Sibling hashes along the path (hex strings) */
84
+ pathElements: string[];
85
+ /** Path directions (0 = left, 1 = right) */
86
+ pathIndices: number[];
87
+ /** Optional root for backward compatibility */
88
+ root?: string;
89
+ }
90
+ /**
91
+ * Transfer recipient - used in privateTransfer
92
+ */
93
+ interface Transfer {
94
+ /** Recipient's Solana public key */
95
+ recipient: PublicKey;
96
+ /** Amount to send in lamports */
97
+ amount: number;
98
+ }
99
+ /**
100
+ * Type-safe array with maximum length constraint
101
+ * Used to enforce 1-5 recipients in privateTransfer
102
+ */
103
+ type MaxLengthArray<T, Max extends number, A extends T[] = []> = A['length'] extends Max ? A : A | MaxLengthArray<T, Max, [T, ...A]>;
104
+ /**
105
+ * Result from a private transfer
106
+ */
107
+ interface TransferResult {
108
+ /** Solana transaction signature */
109
+ signature: string;
110
+ /** Recipients and amounts that were sent */
111
+ outputs: Array<{
112
+ recipient: string;
113
+ amount: number;
114
+ }>;
115
+ /** Nullifier used (prevents double-spending) */
116
+ nullifier: string;
117
+ /** Merkle root that was proven against */
118
+ root: string;
119
+ }
120
+ /**
121
+ * Result from a deposit operation
122
+ */
123
+ interface DepositResult {
124
+ /** The created note (save this securely!) */
125
+ note: CloakNote;
126
+ /** Solana transaction signature */
127
+ signature: string;
128
+ /** Leaf index in the Merkle tree */
129
+ leafIndex: number;
130
+ /** Current Merkle root */
131
+ root: string;
132
+ }
133
+ /**
134
+ * Configuration for the Cloak SDK
135
+ */
136
+ interface CloakConfig {
137
+ /** Solana network */
138
+ network?: Network;
139
+ /**
140
+ * Keypair bytes for signing (deprecated - use wallet instead)
141
+ * @deprecated Use wallet parameter for better integration
142
+ */
143
+ keypairBytes?: Uint8Array;
144
+ /**
145
+ * Wallet adapter for signing transactions
146
+ * Required unless using keypairBytes
147
+ */
148
+ wallet?: WalletAdapter;
149
+ /**
150
+ * Cloak key pair for v2.0 features (note scanning, encryption)
151
+ * Optional but recommended for full functionality
152
+ */
153
+ cloakKeys?: any;
154
+ /** Optional: Proof generation timeout in milliseconds (default: 5 minutes) */
155
+ proofTimeout?: number;
156
+ /** Optional: Program ID (defaults to Cloak mainnet program) */
157
+ programId?: PublicKey;
158
+ /** Optional: Pool account address (auto-derived from program ID if not provided) */
159
+ poolAddress?: PublicKey;
160
+ /** Optional: Merkle tree account address (auto-derived if not provided) */
161
+ merkleTreeAddress?: PublicKey;
162
+ /** Optional: Treasury account address (auto-derived if not provided) */
163
+ treasuryAddress?: PublicKey;
164
+ /**
165
+ * Enable debug logging with structured output similar to Rust tracing.
166
+ *
167
+ * When enabled, logs SDK operations with timestamps, module paths,
168
+ * and key-value pairs for context:
169
+ *
170
+ * ```
171
+ * 2026-01-23T00:51:46.489000Z INFO cloak::sdk: 📥 Deposit completed signature=42XB... leaf_index=757
172
+ * ```
173
+ *
174
+ * Can also be enabled via environment variable:
175
+ * - `CLOAK_DEBUG=1`
176
+ * - `DEBUG=cloak:*`
177
+ *
178
+ * Default: false
179
+ */
180
+ debug?: boolean;
181
+ }
182
+ /**
183
+ * Deposit progress status
184
+ */
185
+ type DepositStatus = "generating_note" | "awaiting_note_acknowledgment" | "note_saved" | "creating_transaction" | "simulating" | "sending" | "confirming" | "submitting_to_indexer" | "fetching_proof" | "complete";
186
+ /**
187
+ * Options for deposit operation
188
+ */
189
+ interface DepositOptions {
190
+ /** Optional callback for progress updates with detailed status */
191
+ onProgress?: (status: DepositStatus | string, details?: {
192
+ message?: string;
193
+ step?: number;
194
+ totalSteps?: number;
195
+ retryAttempt?: number;
196
+ }) => void;
197
+ /** Callback when transaction is sent (before confirmation) */
198
+ onTransactionSent?: (signature: string) => void;
199
+ /** Callback when transaction is confirmed */
200
+ onConfirmed?: (signature: string, slot: number) => void;
201
+ /**
202
+ * CRITICAL: Callback when note is generated, BEFORE any on-chain transaction.
203
+ *
204
+ * This is your chance to safely persist the note. If you don't save the note
205
+ * and the deposit succeeds but the browser crashes, your funds could be lost!
206
+ *
207
+ * The callback receives the note with all secrets needed for withdrawal.
208
+ * The deposit will NOT proceed until this callback completes.
209
+ *
210
+ * @example
211
+ * ```typescript
212
+ * onNoteGenerated: async (note) => {
213
+ * // Save to secure storage BEFORE deposit proceeds
214
+ * await localStorage.setItem(`pending_note_${note.commitment}`, JSON.stringify(note));
215
+ * // Or show user a modal to copy/download the note
216
+ * }
217
+ * ```
218
+ */
219
+ onNoteGenerated?: (note: CloakNote) => Promise<void> | void;
220
+ /**
221
+ * If true, require user to acknowledge the note before proceeding with deposit.
222
+ * When enabled with onNoteGenerated, the deposit will wait for the callback to complete.
223
+ * Default: true when onNoteGenerated is provided
224
+ */
225
+ requireNoteAcknowledgment?: boolean;
226
+ /** Skip simulation (default: false) */
227
+ skipPreflight?: boolean;
228
+ /**
229
+ * Compute units to request.
230
+ * - If `optimizeCU` is true, this is ignored and CU is determined via simulation
231
+ * - Otherwise defaults to 40,000 (suitable for typical deposits)
232
+ */
233
+ computeUnits?: number;
234
+ /**
235
+ * Enable simulation-based CU optimization.
236
+ * When true, simulates the transaction first to determine actual CU usage,
237
+ * then sets an optimal limit (simulated + 20% buffer).
238
+ *
239
+ * **Note: Only works in keypair mode (Node.js/scripts).**
240
+ * In wallet/browser mode, this is ignored because simulation would require
241
+ * the user to sign twice (simulation + actual tx) which is bad UX.
242
+ *
243
+ * Trade-offs:
244
+ * - ✅ Optimal block scheduling priority
245
+ * - ❌ Adds ~200-500ms latency (extra RPC call)
246
+ * - ❌ Only available in keypair mode
247
+ *
248
+ * Default: false (uses fixed 40K CU which is ~75% efficient for typical deposits)
249
+ */
250
+ optimizeCU?: boolean;
251
+ /** Priority fee in micro-lamports (default: 10,000) */
252
+ priorityFee?: number;
253
+ /**
254
+ * Loaded accounts data size limit in bytes.
255
+ *
256
+ * Without this, Solana defaults to 64MB which incurs CU overhead
257
+ * (charged at 8 CU per 32KB). Setting a lower limit improves priority.
258
+ *
259
+ * **Note for Cloak deposits:**
260
+ * The Shield Pool program is ~104KB, so the minimum practical limit is ~128KB.
261
+ *
262
+ * Default: 256 * 1024 (256KB) - provides safety margin above program size.
263
+ * Set to 0 to disable (use Solana 64MB default).
264
+ *
265
+ * @see https://www.anza.xyz/blog/cu-optimization-with-setloadedaccountsdatasizelimit
266
+ */
267
+ loadedAccountsDataSizeLimit?: number;
268
+ /**
269
+ * Optional: Encrypt output for specific recipient's view key
270
+ * If not provided, encrypts for the wallet's own view key (for self-scanning)
271
+ */
272
+ recipientViewKey?: string;
273
+ /**
274
+ * Skip privacy warning on testnet (default: false)
275
+ * Warning: Only skip if you understand the privacy limitations
276
+ */
277
+ skipPrivacyWarning?: boolean;
278
+ }
279
+ /**
280
+ * Options for private transfer/withdraw operation
281
+ */
282
+ interface TransferOptions {
283
+ /**
284
+ * Optional callback for progress updates
285
+ * Note: relayFeeBps is automatically calculated from protocol fees
286
+ */
287
+ onProgress?: (status: string) => void;
288
+ /** Optional callback for proof generation progress (0-100) */
289
+ onProofProgress?: (percent: number) => void;
290
+ /**
291
+ * Metadata bundle for compliance tracking.
292
+ * On first transaction, should include viewing_key for compliance registration.
293
+ */
294
+ metadataBundle?: {
295
+ encrypted_user: string;
296
+ encrypted_compliance: string;
297
+ user_pubkey: string;
298
+ commitment: string;
299
+ timestamp: number;
300
+ tx_type?: ComplianceTxType;
301
+ viewing_key?: string;
302
+ };
303
+ }
304
+ /**
305
+ * Options for withdrawal (convenience method with single recipient)
306
+ */
307
+ interface WithdrawOptions extends TransferOptions {
308
+ /** Whether to withdraw full amount minus fees (default: true) */
309
+ withdrawAll?: boolean;
310
+ /** Specific amount to withdraw in lamports (if not withdrawing all) */
311
+ amount?: number;
312
+ }
313
+ /**
314
+ * Merkle root response from indexer
315
+ */
316
+ interface MerkleRootResponse {
317
+ root: string;
318
+ next_index: number;
319
+ }
320
+ /**
321
+ * Transaction status from relay service
322
+ */
323
+ interface TxStatus {
324
+ status: "pending" | "processing" | "completed" | "failed";
325
+ txId?: string;
326
+ error?: string;
327
+ }
328
+ /**
329
+ * Swap parameters for token swaps
330
+ */
331
+ interface SwapParams {
332
+ /** Output token mint address */
333
+ output_mint: string;
334
+ /** Slippage tolerance in basis points (e.g., 100 = 1%) */
335
+ slippage_bps: number;
336
+ /** Minimum output amount in token's smallest unit */
337
+ min_output_amount: number;
338
+ }
339
+ /**
340
+ * Options for swap operation
341
+ */
342
+ interface SwapOptions extends TransferOptions {
343
+ /** Output token mint address */
344
+ outputMint: string;
345
+ /** Slippage tolerance in basis points (default: 100 = 1%) */
346
+ slippageBps?: number;
347
+ /** Minimum output amount (will be calculated from quote if not provided) */
348
+ minOutputAmount?: number;
349
+ /** Optional callback to get swap quote */
350
+ getQuote?: (amountLamports: number, outputMint: string, slippageBps: number) => Promise<{
351
+ outAmount: number;
352
+ minOutputAmount: number;
353
+ }>;
354
+ /**
355
+ * Recipient's associated token account for the output token.
356
+ * If not provided, will be computed automatically (requires @solana/spl-token).
357
+ * For browser environments, it's recommended to compute this in the frontend
358
+ * where @solana/spl-token is properly bundled.
359
+ */
360
+ recipientAta?: string;
361
+ }
362
+ /**
363
+ * Result from a swap operation
364
+ */
365
+ interface SwapResult extends TransferResult {
366
+ /** Output token mint address */
367
+ outputMint: string;
368
+ /** Minimum output amount that was guaranteed */
369
+ minOutputAmount: number;
370
+ /** Actual output amount received (may be higher than min) */
371
+ actualOutputAmount?: number;
372
+ }
373
+
3
374
  /**
4
375
  * Cloak Key Hierarchy (v2.0)
5
376
  *
@@ -84,310 +455,641 @@ declare function exportKeys(keys: CloakKeyPair): string;
84
455
  declare function importKeys(exported: string): CloakKeyPair;
85
456
 
86
457
  /**
87
- * Supported Solana networks.
458
+ * Storage Interface
459
+ *
460
+ * Defines a pluggable storage interface for notes and keys.
461
+ * Applications can implement their own storage (localStorage, IndexedDB, file system, etc.)
88
462
  */
89
- type Network = "localnet" | "devnet" | "mainnet" | "testnet";
463
+
90
464
  /**
91
- * Minimal wallet adapter interface — compatible with `@solana/wallet-adapter-base`.
465
+ * Storage adapter interface
466
+ *
467
+ * Implement this interface to provide custom storage for notes and keys.
468
+ * The SDK will use this adapter for all persistence operations.
92
469
  */
93
- interface WalletAdapter {
94
- publicKey: PublicKey | null;
95
- signTransaction?<T extends Transaction>(transaction: T): Promise<T>;
96
- signAllTransactions?<T extends Transaction>(transactions: T[]): Promise<T[]>;
97
- sendTransaction?(transaction: Transaction, connection: any, options?: any): Promise<string>;
470
+ interface StorageAdapter {
471
+ /**
472
+ * Save a note
473
+ */
474
+ saveNote(note: CloakNote): Promise<void> | void;
475
+ /**
476
+ * Load all notes
477
+ */
478
+ loadAllNotes(): Promise<CloakNote[]> | CloakNote[];
479
+ /**
480
+ * Update a note
481
+ */
482
+ updateNote(commitment: string, updates: Partial<CloakNote>): Promise<void> | void;
483
+ /**
484
+ * Delete a note
485
+ */
486
+ deleteNote(commitment: string): Promise<void> | void;
487
+ /**
488
+ * Clear all notes
489
+ */
490
+ clearAllNotes(): Promise<void> | void;
491
+ /**
492
+ * Save wallet keys
493
+ */
494
+ saveKeys(keys: CloakKeyPair): Promise<void> | void;
495
+ /**
496
+ * Load wallet keys
497
+ */
498
+ loadKeys(): Promise<CloakKeyPair | null> | CloakKeyPair | null;
499
+ /**
500
+ * Delete wallet keys
501
+ */
502
+ deleteKeys(): Promise<void> | void;
503
+ }
504
+ /**
505
+ * In-memory storage adapter (default, no persistence)
506
+ *
507
+ * Useful for testing or when storage is handled externally
508
+ */
509
+ declare class MemoryStorageAdapter implements StorageAdapter {
510
+ private notes;
511
+ private keys;
512
+ saveNote(note: CloakNote): void;
513
+ loadAllNotes(): CloakNote[];
514
+ updateNote(commitment: string, updates: Partial<CloakNote>): void;
515
+ deleteNote(commitment: string): void;
516
+ clearAllNotes(): void;
517
+ saveKeys(keys: CloakKeyPair): void;
518
+ loadKeys(): CloakKeyPair | null;
519
+ deleteKeys(): void;
520
+ }
521
+ /**
522
+ * Browser localStorage adapter (optional, for browser environments)
523
+ *
524
+ * Only use this if you're in a browser environment and want localStorage persistence.
525
+ * Import from a separate browser-specific module.
526
+ */
527
+ declare class LocalStorageAdapter implements StorageAdapter {
528
+ private notesKey;
529
+ private keysKey;
530
+ constructor(notesKey?: string, keysKey?: string);
531
+ private getStorage;
532
+ saveNote(note: CloakNote): void;
533
+ loadAllNotes(): CloakNote[];
534
+ updateNote(commitment: string, updates: Partial<CloakNote>): void;
535
+ deleteNote(commitment: string): void;
536
+ clearAllNotes(): void;
537
+ saveKeys(keys: CloakKeyPair): void;
538
+ loadKeys(): CloakKeyPair | null;
539
+ deleteKeys(): void;
98
540
  }
541
+
542
+ /** Default Cloak Program ID on Solana */
543
+ declare const CLOAK_PROGRAM_ID: PublicKey;
544
+ /**
545
+ * Main Cloak SDK
546
+ *
547
+ * Provides high-level API for interacting with the Cloak protocol,
548
+ * including deposits, withdrawals, and private transfers.
549
+ *
550
+ * Supports two modes:
551
+ * 1. Node.js mode with keypairBytes - for scripts and backend services
552
+ * 2. Wallet adapter mode - for browser applications with wallet integration
553
+ */
554
+ declare class CloakSDK {
555
+ private config;
556
+ private keypair?;
557
+ private wallet?;
558
+ private cloakKeys?;
559
+ private relay;
560
+ private storage;
561
+ /**
562
+ * Create a new Cloak SDK client
563
+ *
564
+ * @param config - Client configuration
565
+ *
566
+ * @example Node.js mode (with keypair)
567
+ * ```typescript
568
+ * const sdk = new CloakSDK({
569
+ * keypairBytes: keypair.secretKey,
570
+ * network: "devnet"
571
+ * });
572
+ * ```
573
+ *
574
+ * @example Browser mode (with wallet adapter)
575
+ * ```typescript
576
+ * const sdk = new CloakSDK({
577
+ * wallet: walletAdapter,
578
+ * network: "devnet"
579
+ * });
580
+ * ```
581
+ */
582
+ constructor(config: {
583
+ /** Keypair bytes for signing (Node.js mode) */
584
+ keypairBytes?: Uint8Array;
585
+ /** Wallet adapter for signing (Browser mode) */
586
+ wallet?: WalletAdapter;
587
+ network?: Network;
588
+ cloakKeys?: CloakKeyPair;
589
+ storage?: StorageAdapter;
590
+ programId?: PublicKey;
591
+ relayUrl?: string;
592
+ /** Enable debug logging with structured output */
593
+ debug?: boolean;
594
+ });
595
+ /**
596
+ * Get the public key for deposits (from keypair or wallet)
597
+ */
598
+ getPublicKey(): PublicKey;
599
+ /**
600
+ * Check if the SDK is using a wallet adapter
601
+ */
602
+ isWalletMode(): boolean;
603
+ /**
604
+ * Deposit SOL into the Cloak protocol
605
+ *
606
+ * Creates a new note (or uses a provided one), submits a deposit transaction,
607
+ * and registers with the indexer.
608
+ *
609
+ * @param connection - Solana connection
610
+ * @param payer - Payer wallet with sendTransaction method
611
+ * @param amountOrNote - Amount in lamports OR an existing note to deposit
612
+ * @param options - Optional configuration
613
+ * @returns Deposit result with note and transaction info
614
+ *
615
+ * @example
616
+ * ```typescript
617
+ * // Generate and deposit in one step
618
+ * const result = await client.deposit(
619
+ * connection,
620
+ * wallet,
621
+ * 1_000_000_000,
622
+ * {
623
+ * onProgress: (status) => console.log(status)
624
+ * }
625
+ * );
626
+ *
627
+ * // Or deposit a pre-generated note
628
+ * const note = client.generateNote(1_000_000_000);
629
+ * const result = await client.deposit(connection, wallet, note);
630
+ * ```
631
+ */
632
+ deposit(connection: Connection, amountOrNote: number | CloakNote, options?: DepositOptions): Promise<DepositResult>;
633
+ /**
634
+ * Private transfer with up to 5 recipients
635
+ *
636
+ * Handles the complete private transfer flow:
637
+ * 1. If note is not deposited, deposits it first and waits for confirmation
638
+ * 2. Generates a zero-knowledge proof
639
+ * 3. Submits the withdrawal via relay service to recipients
640
+ *
641
+ * This is the main method for performing private transfers - it handles everything!
642
+ *
643
+ * @param connection - Solana connection (required for deposit if not already deposited)
644
+ * @param payer - Payer wallet (required for deposit if not already deposited)
645
+ * @param note - Note to spend (can be deposited or not)
646
+ * @param recipients - Array of 1-5 recipients with amounts
647
+ * @param options - Optional configuration
648
+ * @returns Transfer result with signature and outputs
649
+ *
650
+ * @example
651
+ * ```typescript
652
+ * // Create a note (not deposited yet)
653
+ * const note = client.generateNote(1_000_000_000);
654
+ *
655
+ * // privateTransfer handles the full flow: deposit + withdraw
656
+ * const result = await client.privateTransfer(
657
+ * connection,
658
+ * wallet,
659
+ * note,
660
+ * [
661
+ * { recipient: new PublicKey("..."), amount: 500_000_000 },
662
+ * { recipient: new PublicKey("..."), amount: 492_500_000 }
663
+ * ],
664
+ * {
665
+ * relayFeeBps: 50, // 0.5%
666
+ * onProgress: (status) => console.log(status),
667
+ * onProofProgress: (pct) => console.log(`Proof: ${pct}%`)
668
+ * }
669
+ * );
670
+ * console.log(`Success! TX: ${result.signature}`);
671
+ * ```
672
+ */
673
+ privateTransfer(connection: Connection, note: CloakNote, recipients: MaxLengthArray<Transfer, 5>, options?: TransferOptions): Promise<TransferResult>;
674
+ /**
675
+ * Withdraw to a single recipient
676
+ *
677
+ * Convenience method for withdrawing to one address.
678
+ * Handles the complete flow: deposits if needed, then withdraws.
679
+ *
680
+ * @param connection - Solana connection
681
+ * @param payer - Payer wallet
682
+ * @param note - Note to spend
683
+ * @param recipient - Recipient address
684
+ * @param options - Optional configuration
685
+ * @returns Transfer result
686
+ *
687
+ * @example
688
+ * ```typescript
689
+ * const note = client.generateNote(1_000_000_000);
690
+ * const result = await client.withdraw(
691
+ * connection,
692
+ * wallet,
693
+ * note,
694
+ * new PublicKey("..."),
695
+ * { withdrawAll: true }
696
+ * );
697
+ * ```
698
+ */
699
+ withdraw(connection: Connection, note: CloakNote, recipient: PublicKey, options?: WithdrawOptions): Promise<TransferResult>;
700
+ /**
701
+ * Send SOL privately to multiple recipients
702
+ *
703
+ * Convenience method that wraps privateTransfer with a simpler API.
704
+ * Handles the complete flow: deposits if needed, then sends to recipients.
705
+ *
706
+ * @param connection - Solana connection
707
+ * @param note - Note to spend
708
+ * @param recipients - Array of 1-5 recipients with amounts
709
+ * @param options - Optional configuration
710
+ * @returns Transfer result
711
+ *
712
+ * @example
713
+ * ```typescript
714
+ * const note = client.generateNote(1_000_000_000);
715
+ * const result = await client.send(
716
+ * connection,
717
+ * note,
718
+ * [
719
+ * { recipient: new PublicKey("..."), amount: 500_000_000 },
720
+ * { recipient: new PublicKey("..."), amount: 492_500_000 }
721
+ * ]
722
+ * );
723
+ * ```
724
+ */
725
+ send(connection: Connection, note: CloakNote, recipients: MaxLengthArray<Transfer, 5>, options?: TransferOptions): Promise<TransferResult>;
726
+ /**
727
+ * Swap SOL for tokens privately
728
+ *
729
+ * Withdraws SOL from a note and swaps it for tokens via the relay service.
730
+ * Handles the complete flow: deposits if needed, generates proof, and submits swap.
731
+ *
732
+ * @param connection - Solana connection
733
+ * @param note - Note to spend
734
+ * @param recipient - Recipient address (will receive tokens)
735
+ * @param options - Swap configuration
736
+ * @returns Swap result with transaction signature
737
+ *
738
+ * @example
739
+ * ```typescript
740
+ * const note = client.generateNote(1_000_000_000);
741
+ * const result = await client.swap(
742
+ * connection,
743
+ * note,
744
+ * new PublicKey("..."), // recipient
745
+ * {
746
+ * outputMint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC
747
+ * slippageBps: 100, // 1%
748
+ * getQuote: async (amount, mint, slippage) => {
749
+ * // Fetch quote from your swap API
750
+ * const quote = await fetchSwapQuote(amount, mint, slippage);
751
+ * return {
752
+ * outAmount: quote.outAmount,
753
+ * minOutputAmount: quote.minOutputAmount
754
+ * };
755
+ * }
756
+ * }
757
+ * );
758
+ * ```
759
+ */
760
+ swap(connection: Connection, note: CloakNote, recipient: PublicKey, options: SwapOptions): Promise<SwapResult>;
761
+ /**
762
+ * Generate a new note without depositing
763
+ *
764
+ * @param amountLamports - Amount for the note
765
+ * @param useWalletKeys - Whether to use wallet keys (v2.0 recommended)
766
+ * @returns New note (not yet deposited)
767
+ */
768
+ generateNote(amountLamports: number, useWalletKeys?: boolean): Promise<CloakNote>;
769
+ /**
770
+ * Parse a note from JSON string
771
+ *
772
+ * @param jsonString - JSON representation
773
+ * @returns Parsed note
774
+ */
775
+ parseNote(jsonString: string): CloakNote;
776
+ /**
777
+ * Export a note to JSON string
778
+ *
779
+ * @param note - Note to export
780
+ * @param pretty - Format with indentation
781
+ * @returns JSON string
782
+ */
783
+ exportNote(note: CloakNote, pretty?: boolean): string;
784
+ /**
785
+ * Check if a note is ready for withdrawal
786
+ *
787
+ * @param note - Note to check
788
+ * @returns True if withdrawable
789
+ */
790
+ isWithdrawable(note: CloakNote): boolean;
791
+ /**
792
+ * Get Merkle proof for a leaf index directly from on-chain state
793
+ *
794
+ * @param connection - Solana connection
795
+ * @param leafIndex - Leaf index in tree
796
+ * @returns Merkle proof computed from on-chain data
797
+ */
798
+ getMerkleProof(connection: Connection, leafIndex: number): Promise<MerkleProof>;
799
+ /**
800
+ * Get current Merkle root directly from on-chain state
801
+ *
802
+ * @param connection - Solana connection
803
+ * @returns Current root hash from on-chain tree
804
+ */
805
+ getCurrentRoot(connection: Connection): Promise<string>;
806
+ /**
807
+ * Get transaction status from relay service
808
+ *
809
+ * @param requestId - Request ID from previous submission
810
+ * @returns Current status
811
+ */
812
+ getTransactionStatus(requestId: string): Promise<TxStatus>;
813
+ /**
814
+ * Fetch and decrypt this user's transaction metadata history.
815
+ * DEPRECATED: This functionality is no longer supported
816
+ */
817
+ getTransactionMetadata(_options?: {
818
+ _after?: number;
819
+ _before?: number;
820
+ }): Promise<TransactionMetadata[]>;
821
+ /**
822
+ * Load all notes from storage
823
+ *
824
+ * @returns Array of saved notes
825
+ */
826
+ loadNotes(): Promise<CloakNote[]>;
827
+ /**
828
+ * Save a note to storage
829
+ *
830
+ * @param note - Note to save
831
+ */
832
+ saveNote(note: CloakNote): Promise<void>;
833
+ /**
834
+ * Find a note by its commitment
835
+ *
836
+ * @param commitment - Commitment hash
837
+ * @returns Note if found
838
+ */
839
+ findNote(commitment: string): Promise<CloakNote | undefined>;
840
+ /**
841
+ * Import wallet keys from JSON
842
+ *
843
+ * @param keysJson - JSON string containing keys
844
+ */
845
+ importWalletKeys(keysJson: string): Promise<void>;
846
+ /**
847
+ * Export wallet keys to JSON
848
+ *
849
+ * WARNING: This exports secret keys! Store securely.
850
+ *
851
+ * @returns JSON string with keys
852
+ */
853
+ exportWalletKeys(): string;
854
+ /**
855
+ * Get the configuration
856
+ */
857
+ getConfig(): CloakConfig;
858
+ /**
859
+ * Wrap errors with better categorization and user-friendly messages
860
+ *
861
+ * @private
862
+ */
863
+ private wrapError;
864
+ }
865
+
866
+ /**
867
+ * Serialize a note to JSON string
868
+ *
869
+ * @param note - Note to serialize
870
+ * @param pretty - Whether to format with indentation (default: false)
871
+ * @returns JSON string
872
+ *
873
+ * @example
874
+ * ```typescript
875
+ * const json = serializeNote(note, true);
876
+ * console.log(json);
877
+ * // Or save to file, copy to clipboard, etc.
878
+ * ```
879
+ */
880
+ declare function serializeNote(note: CloakNote, pretty?: boolean): string;
881
+ /**
882
+ * Export note as downloadable JSON (browser only)
883
+ *
884
+ * @param note - Note to export
885
+ * @param filename - Optional custom filename
886
+ */
887
+ declare function downloadNote(note: CloakNote, filename?: string): void;
888
+ /**
889
+ * Copy note to clipboard as JSON (browser only)
890
+ *
891
+ * @param note - Note to copy
892
+ * @returns Promise that resolves when copied
893
+ */
894
+ declare function copyNoteToClipboard(note: CloakNote): Promise<void>;
895
+
896
+ /**
897
+ * Fee calculation utilities for Cloak Protocol
898
+ *
899
+ * The protocol charges a fixed fee plus a variable percentage fee
900
+ * to prevent sybil attacks and cover operational costs.
901
+ *
902
+ * IMPORTANT: These constants are the single source of truth for fee calculations.
903
+ * When updating fees, only change these values - all other code should import from here.
904
+ */
905
+ /** Lamports per SOL */
906
+ declare const LAMPORTS_PER_SOL = 1000000000;
907
+ /** Fixed fee: 0.005 SOL (5M lamports) */
908
+ declare const FIXED_FEE_LAMPORTS = 5000000;
909
+ /** Variable fee numerator (3 = 0.3%) */
910
+ declare const VARIABLE_FEE_NUMERATOR = 3;
911
+ /** Variable fee denominator (1000 = divide by 1000) */
912
+ declare const VARIABLE_FEE_DENOMINATOR = 1000;
913
+ /** Variable fee rate as decimal: 0.3% = 0.003 */
914
+ declare const VARIABLE_FEE_RATE: number;
915
+ /** Minimum deposit amount to prevent dust/UX footguns (0.01 SOL). */
916
+ declare const MIN_DEPOSIT_LAMPORTS = 10000000;
917
+ /**
918
+ * Calculate the total protocol fee for a given amount
919
+ *
920
+ * Formula: FIXED_FEE + floor((amount * VARIABLE_FEE_NUMERATOR) / VARIABLE_FEE_DENOMINATOR)
921
+ *
922
+ * @param amountLamports - Amount in lamports
923
+ * @returns Total fee in lamports
924
+ *
925
+ * @example
926
+ * ```typescript
927
+ * const fee = calculateFee(1_000_000_000); // 1 SOL
928
+ * // Returns: 5_000_000 (fixed) + 3_000_000 (0.3%) = 8_000_000 lamports
929
+ * ```
930
+ */
931
+ declare function calculateFee(amountLamports: number): number;
932
+ /**
933
+ * Bigint version of calculateFee (for UTXO flows that use bigint lamports).
934
+ */
935
+ declare function calculateFeeBigint(amountLamports: bigint): bigint;
936
+ /**
937
+ * Returns true if a withdrawal amount can pay fees and still leave >0 lamports.
938
+ * (On-chain uses the same integer math and enforces this as well.)
939
+ */
940
+ declare function isWithdrawAmountSufficient(amountLamports: bigint): boolean;
941
+ /**
942
+ * Calculate the distributable amount after protocol fees
943
+ *
944
+ * This is the amount available to send to recipients.
945
+ *
946
+ * @param amountLamports - Total note amount in lamports
947
+ * @returns Amount available for recipients in lamports
948
+ *
949
+ * @example
950
+ * ```typescript
951
+ * const distributable = getDistributableAmount(1_000_000_000);
952
+ * // Returns: 1_000_000_000 - 7_500_000 = 992_500_000 lamports
953
+ * ```
954
+ */
955
+ declare function getDistributableAmount(amountLamports: number): number;
956
+ /**
957
+ * Format lamports as SOL string
958
+ *
959
+ * @param lamports - Amount in lamports
960
+ * @param decimals - Number of decimal places (default: 9)
961
+ * @returns Formatted string (e.g., "1.000000000")
962
+ *
963
+ * @example
964
+ * ```typescript
965
+ * formatAmount(1_000_000_000); // "1.000000000"
966
+ * formatAmount(1_500_000_000); // "1.500000000"
967
+ * formatAmount(123_456_789, 4); // "0.1235"
968
+ * ```
969
+ */
970
+ declare function formatAmount(lamports: number, decimals?: number): string;
971
+ /**
972
+ * Parse SOL string to lamports
973
+ *
974
+ * @param sol - SOL amount as string (e.g., "1.5")
975
+ * @returns Amount in lamports
976
+ * @throws Error if invalid format
977
+ *
978
+ * @example
979
+ * ```typescript
980
+ * parseAmount("1.5"); // 1_500_000_000
981
+ * parseAmount("0.001"); // 1_000_000
982
+ * ```
983
+ */
984
+ declare function parseAmount(sol: string): number;
985
+ /**
986
+ * Validate that outputs sum equals expected amount
987
+ *
988
+ * @param outputs - Array of output amounts
989
+ * @param expectedTotal - Expected total amount
990
+ * @returns True if amounts match
991
+ */
992
+ declare function validateOutputsSum(outputs: Array<{
993
+ amount: number;
994
+ }>, expectedTotal: number): boolean;
995
+ /**
996
+ * Calculate relay fee from basis points
997
+ *
998
+ * @param amountLamports - Amount in lamports
999
+ * @param feeBps - Fee in basis points (100 bps = 1%)
1000
+ * @returns Relay fee in lamports
1001
+ *
1002
+ * @example
1003
+ * ```typescript
1004
+ * calculateRelayFee(1_000_000_000, 50); // 0.5% = 5_000_000 lamports
1005
+ * ```
1006
+ */
1007
+ declare function calculateRelayFee(amountLamports: number, feeBps: number): number;
1008
+
1009
+ /**
1010
+ * Note Manager
1011
+ *
1012
+ * Standalone note management - no browser dependencies.
1013
+ * Storage is handled externally via StorageAdapter.
1014
+ *
1015
+ * Core functionality:
1016
+ * - Generate notes (v1.0 and v2.0)
1017
+ * - Parse and validate notes
1018
+ * - Note utilities (formatting, fees, etc.)
1019
+ * - Key management (without storage)
1020
+ */
1021
+
1022
+ /**
1023
+ * Generate a new note without wallet keys (legacy v1.0)
1024
+ * Uses Poseidon hashing to match circuit implementation
1025
+ * @deprecated Use generateNoteFromWallet instead for enhanced security
1026
+ */
1027
+ declare function generateNote(amountLamports: number, network?: Network): Promise<CloakNote>;
1028
+ /**
1029
+ * Generate a note using wallet's spend key (v2.0 recommended)
1030
+ * Uses Poseidon hashing to match circuit implementation
1031
+ */
1032
+ declare function generateNoteFromWallet(amountLamports: number, keys: CloakKeyPair, network?: Network): Promise<CloakNote>;
1033
+ /**
1034
+ * Parse and validate a note from JSON string
1035
+ */
1036
+ declare function parseNote(jsonString: string): CloakNote;
1037
+ /**
1038
+ * Export note to JSON string
1039
+ */
1040
+ declare function exportNote(note: CloakNote, pretty?: boolean): string;
99
1041
  /**
100
- * Cloak-specific error with categorization.
1042
+ * Check if a note is withdrawable (has been deposited)
1043
+ * Note: merkleProof is optional - it may be fetched lazily at withdrawal time
101
1044
  */
102
- declare class CloakError extends Error {
103
- category: "network" | "indexer" | "prover" | "relay" | "validation" | "wallet" | "environment" | "service";
104
- retryable: boolean;
105
- originalError?: Error | undefined;
106
- constructor(message: string, category: "network" | "indexer" | "prover" | "relay" | "validation" | "wallet" | "environment" | "service", retryable?: boolean, originalError?: Error | undefined);
107
- }
1045
+ declare function isWithdrawable(note: CloakNote): boolean;
108
1046
  /**
109
- * Merkle proof for a leaf in the commitment tree.
1047
+ * Update note with deposit information
1048
+ * Returns a new note object with deposit info added
110
1049
  */
111
- interface MerkleProof {
112
- /** Sibling hashes along the path (hex strings) */
113
- pathElements: string[];
114
- /** Path directions (0 = left, 1 = right) */
115
- pathIndices: number[];
116
- /** Optional root for backward compatibility */
117
- root?: string;
118
- }
1050
+ declare function updateNoteWithDeposit(note: CloakNote, depositInfo: {
1051
+ signature: string;
1052
+ slot: number;
1053
+ leafIndex: number;
1054
+ root: string;
1055
+ merkleProof?: {
1056
+ pathElements: string[];
1057
+ pathIndices: number[];
1058
+ };
1059
+ }): CloakNote;
119
1060
  /**
120
- * Snapshot of the active SDK configuration, returned by `CloakSDK.getConfig()`.
121
- *
122
- * This type is intentionally narrower than the constructor input
123
- * (`CloakSDKOptions` in `core/CloakSDK.ts`): it omits `keypairBytes`,
124
- * `wallet`, and `storage` because those are consumed into private SDK
125
- * state (`this.keypair`, `this.wallet`, `this.storage`) at construction
126
- * time and are never persisted on the config object. The type system
127
- * therefore enforces the same invariant exercised at runtime by
128
- * `cloak-sdk.test.ts`.
129
- *
130
- * If you're configuring the SDK at construction, use `CloakSDKOptions`.
1061
+ * Find note by commitment from an array
131
1062
  */
132
- interface CloakConfig {
133
- /** Network the SDK was constructed for. Defaults to `"mainnet"`. */
134
- network?: Network;
135
- /** Cloak key pair for note scanning / encryption (when provided). */
136
- cloakKeys?: CloakKeyPair;
137
- /** Resolved program ID (defaults to the mainnet shield-pool). */
138
- programId?: PublicKey;
139
- /** Pool PDA derived from `programId` + native SOL mint. */
140
- poolAddress?: PublicKey;
141
- /** Merkle-tree PDA. */
142
- merkleTreeAddress?: PublicKey;
143
- /** Treasury PDA. */
144
- treasuryAddress?: PublicKey;
145
- /**
146
- * Whether structured debug logging was enabled at construction.
147
- *
148
- * Can also be enabled via env: `CLOAK_DEBUG=1` or `DEBUG=cloak:*`.
149
- */
150
- debug?: boolean;
151
- }
1063
+ declare function findNoteByCommitment(notes: CloakNote[], commitment: string): CloakNote | undefined;
152
1064
  /**
153
- * Merkle root response from the relay/indexer.
1065
+ * Filter notes by network
154
1066
  */
155
- interface MerkleRootResponse {
156
- root: string;
157
- next_index: number;
158
- }
1067
+ declare function filterNotesByNetwork(notes: CloakNote[], network: Network): CloakNote[];
159
1068
  /**
160
- * Transaction status returned by the relay.
1069
+ * Filter notes that can be withdrawn
161
1070
  */
162
- interface TxStatus {
163
- status: "pending" | "processing" | "completed" | "failed";
164
- txId?: string;
165
- error?: string;
166
- }
167
-
1071
+ declare function filterWithdrawableNotes(notes: CloakNote[]): CloakNote[];
168
1072
  /**
169
- * Storage Interface
170
- *
171
- * Pluggable storage for Cloak wallet keys (master seed / spend / view keys).
172
- *
173
- * As of `0.1.6`, note persistence is the consumer's responsibility: the OLD
174
- * `CloakNote` model was retired (see `CloakSDK` jsdoc), and UTXO persistence
175
- * in the new flow is owned by callers.
1073
+ * Export keys to JSON string
1074
+ * WARNING: This exports secret keys! Store securely.
176
1075
  */
177
-
178
- interface StorageAdapter {
179
- /** Save wallet keys. */
180
- saveKeys(keys: CloakKeyPair): Promise<void> | void;
181
- /** Load wallet keys (`null` when none stored). */
182
- loadKeys(): Promise<CloakKeyPair | null> | CloakKeyPair | null;
183
- /** Delete wallet keys. */
184
- deleteKeys(): Promise<void> | void;
185
- }
1076
+ declare function exportWalletKeys(keys: CloakKeyPair): string;
186
1077
  /**
187
- * In-memory storage adapter (default; no persistence). Useful for tests or
188
- * when storage is handled externally.
1078
+ * Import keys from JSON string
189
1079
  */
190
- declare class MemoryStorageAdapter implements StorageAdapter {
191
- private keys;
192
- saveKeys(keys: CloakKeyPair): void;
193
- loadKeys(): CloakKeyPair | null;
194
- deleteKeys(): void;
195
- }
1080
+ declare function importWalletKeys(keysJson: string): CloakKeyPair;
196
1081
  /**
197
- * Browser `localStorage` adapter only safe in browser environments.
198
- *
199
- * **Auto-purge on construction:** the first time any `LocalStorageAdapter`
200
- * is constructed in a given browser, it removes the legacy `cloak_notes`
201
- * localStorage entry (pre-0.1.6 plaintext `r` + `sk_spend` blob) and writes
202
- * a sentinel under {@link LEGACY_PURGE_SENTINEL_KEY} so subsequent
203
- * constructions skip the work. This closes the upgrade-time security gap
204
- * without requiring every consumer to explicitly call
205
- * {@link LocalStorageAdapter.purgeLegacyNoteStorage}. Consumers using a
206
- * non-default legacy key can still call the static helper with their own
207
- * key name.
1082
+ * Get public view key from keys
208
1083
  */
209
- declare class LocalStorageAdapter implements StorageAdapter {
210
- private keysKey;
211
- constructor(keysKey?: string);
212
- private getStorage;
213
- saveKeys(keys: CloakKeyPair): void;
214
- loadKeys(): CloakKeyPair | null;
215
- deleteKeys(): void;
216
- /**
217
- * Sentinel key set after the auto-purge runs. Persists across browser
218
- * sessions so the purge never re-runs on the same origin.
219
- */
220
- static readonly LEGACY_PURGE_SENTINEL_KEY = "cloak_purged_v0_1_5";
221
- /**
222
- * Default localStorage key the legacy `<= 0.1.5` SDK used for the
223
- * plaintext `CloakNote[]` blob.
224
- */
225
- static readonly LEGACY_NOTES_KEY = "cloak_notes";
226
- /**
227
- * Runs the legacy-purge exactly once per browser origin. Invoked
228
- * automatically by the constructor; exposed as a static for tests and
229
- * for callers who want to force the check before the first adapter
230
- * instantiation.
231
- */
232
- static runLegacyPurgeOnce(): void;
233
- /**
234
- * Manual cleanup helper for callers that stored notes under a non-default
235
- * key. The auto-purge in the constructor only handles the canonical
236
- * `cloak_notes` slot; pass the custom key here.
237
- *
238
- * Pre-0.1.6 versions stored the user's `CloakNote[]` (including
239
- * `r` randomness and `sk_spend` secret-key hex) as JSON plaintext.
240
- * Those notes are no longer usable (the OLD `withdraw_regular.circom`
241
- * flow is incompatible with the deployed UTXO program) but the plaintext
242
- * would otherwise linger indefinitely.
243
- *
244
- * Idempotent; safe to call when the key doesn't exist or when running
245
- * outside a browser (no-op). Does NOT update the auto-purge sentinel.
246
- *
247
- * @param notesKey - localStorage key to remove (default `"cloak_notes"`,
248
- * matching the pre-0.1.6 default).
249
- */
250
- static purgeLegacyNoteStorage(notesKey?: string): void;
251
- }
252
-
253
- /** Default Cloak Program ID on Solana mainnet. */
254
- declare const CLOAK_PROGRAM_ID: PublicKey;
1084
+ declare function getPublicViewKey(keys: CloakKeyPair): string;
255
1085
  /**
256
- * Constructor options for {@link CloakSDK}.
257
- *
258
- * Contains *all* the settings a caller can supply at construction time —
259
- * including the secret-bearing fields (`keypairBytes`, `wallet`, `cloakKeys`).
260
- * Those secrets are consumed into private SDK state and are NOT persisted on
261
- * the snapshot returned by {@link CloakSDK.getConfig}; see
262
- * {@link CloakConfig} for the (narrower) snapshot shape.
1086
+ * Get view key from keys
263
1087
  */
264
- interface CloakSDKOptions {
265
- /** Keypair bytes for signing (Node.js mode). Consumed into a `Keypair`; not retained. */
266
- keypairBytes?: Uint8Array;
267
- /** Wallet adapter for signing (browser mode). */
268
- wallet?: WalletAdapter;
269
- /** Network the SDK targets. Defaults to `"mainnet"`. */
270
- network?: Network;
271
- /** Cloak key pair for note scanning / encryption. */
272
- cloakKeys?: CloakKeyPair;
273
- /** Storage adapter for wallet keys (defaults to in-memory). */
274
- storage?: StorageAdapter;
275
- /** Program ID override (defaults to the mainnet shield-pool). */
276
- programId?: PublicKey;
277
- /** Relay URL override (defaults to `https://api.cloak.ag`). */
278
- relayUrl?: string;
279
- /** Enable structured debug logging. Also via env `CLOAK_DEBUG=1` / `DEBUG=cloak:*`. */
280
- debug?: boolean;
281
- }
1088
+ declare function getViewKey(keys: CloakKeyPair): ViewKey;
282
1089
  /**
283
- * Lightweight Cloak client.
284
- *
285
- * Holds program/relay/storage configuration and exposes a small set of
286
- * read-only chain helpers. **All transaction-emitting methods (deposit,
287
- * privateTransfer, withdraw, send, swap) were removed in 0.1.6** — they
288
- * shipped a legacy `[discriminator: 1, amount: u64, commitment: 32]` deposit
289
- * instruction (`createDepositInstruction`) that the on-chain program no
290
- * longer understands. Tag `1` is now `TransactSwap`, not `Deposit`, so every
291
- * call hit `0x1063 MissingAccounts`. The OLD note model
292
- * (3-input `Poseidon(amount, r0, r1, pk_spend)` for `withdraw_regular.circom`)
293
- * is also incompatible with the deployed `transaction.circom` UTXO circuit.
294
- *
295
- * The supported flow is the functional UTXO API:
296
- *
297
- * ```ts
298
- * import {
299
- * transact,
300
- * createUtxo,
301
- * createZeroUtxo,
302
- * generateUtxoKeypair,
303
- * CLOAK_PROGRAM_ID,
304
- * NATIVE_SOL_MINT,
305
- * } from "@cloak.dev/sdk";
306
- *
307
- * const outputKeypair = await generateUtxoKeypair();
308
- * const outputUtxo = await createUtxo(amountLamports, outputKeypair);
309
- * const result = await transact(
310
- * {
311
- * inputUtxos: [await createZeroUtxo(), await createZeroUtxo()],
312
- * outputUtxos: [outputUtxo, await createZeroUtxo()],
313
- * externalAmount: amountLamports, // positive = deposit
314
- * depositor: payer.publicKey,
315
- * },
316
- * {
317
- * connection,
318
- * programId: CLOAK_PROGRAM_ID,
319
- * relayUrl: "https://api.cloak.ag",
320
- * depositorKeypair: payer,
321
- * },
322
- * );
323
- * ```
324
- *
325
- * See `transfer`, `partialWithdraw`, `fullWithdraw`, `swapUtxo` for the
326
- * non-deposit flows.
1090
+ * Get recipient amount after fees
327
1091
  */
328
- declare class CloakSDK {
329
- private config;
330
- private keypair?;
331
- private wallet?;
332
- private cloakKeys?;
333
- private relay;
334
- private storage;
335
- constructor(config: CloakSDKOptions);
336
- /** Public key of the configured signer (keypair or wallet). */
337
- getPublicKey(): PublicKey;
338
- /** True when the SDK was constructed with a wallet adapter (browser). */
339
- isWalletMode(): boolean;
340
- /**
341
- * Compute a Merkle proof for `leafIndex` from on-chain state. No relay /
342
- * indexer round-trip required.
343
- */
344
- getMerkleProof(connection: Connection, leafIndex: number): Promise<MerkleProof>;
345
- /** Read the current SOL-pool Merkle root from on-chain state. */
346
- getCurrentRoot(connection: Connection): Promise<string>;
347
- /** Poll relay for the status of a previously-submitted request. */
348
- getTransactionStatus(requestId: string): Promise<TxStatus>;
349
- /** Import wallet keys from JSON; persists to the configured storage adapter. */
350
- importWalletKeys(keysJson: string): Promise<void>;
351
- /**
352
- * Export wallet keys to JSON.
353
- *
354
- * WARNING: this exports secret keys. Store securely.
355
- */
356
- exportWalletKeys(): string;
357
- /**
358
- * Snapshot of the active SDK configuration.
359
- *
360
- * The return type ({@link CloakConfig}) is structurally narrower than the
361
- * constructor input ({@link CloakSDKOptions}): `keypairBytes`, `wallet`,
362
- * and `storage` are intentionally absent from the snapshot. The secrets
363
- * are consumed into private SDK state at construction time and are not
364
- * re-exposable through this method — the type system enforces it. To
365
- * sign, pass a `Keypair` / `WalletAdapter` directly to the standalone
366
- * `transact` / `partialWithdraw` / `fullWithdraw` / `swapUtxo` helpers.
367
- */
368
- getConfig(): CloakConfig;
369
- }
370
-
371
- type ComplianceTxType = "deposit" | "withdraw" | "send" | "swap";
372
- interface TransactionMetadata {
373
- amount: number;
374
- recipient: string;
375
- timestamp: number;
376
- txType: ComplianceTxType;
377
- commitment: string;
378
- signature?: string;
379
- outputMint?: string;
380
- }
381
- interface EncryptedMetadataBundle {
382
- encrypted_user: string;
383
- encrypted_compliance: string;
384
- user_pubkey: string;
385
- commitment: string;
386
- timestamp: number;
387
- tx_type?: ComplianceTxType;
388
- wallet_signature?: string;
389
- viewing_key?: string;
390
- }
1092
+ declare function getRecipientAmount(amountLamports: number): number;
391
1093
 
392
1094
  interface ViewingKeyPair {
393
1095
  privateKey: Uint8Array;
@@ -542,186 +1244,246 @@ declare function computeMerkleRoot(leaf: bigint, pathElements: bigint[], pathInd
542
1244
  */
543
1245
  declare function hexToBigint$1(hex: string): bigint;
544
1246
  /**
545
- * Convert bigint to 32-byte big-endian Uint8Array
1247
+ * Compute commitment = Poseidon(amount, r0, r1, pk_spend)
1248
+ * where pk_spend = Poseidon(sk0, sk1)
1249
+ * (matching withdraw_regular.circom)
1250
+ *
1251
+ * This is the test-style function that takes bigints directly
1252
+ *
1253
+ * @param amount - Amount as bigint
1254
+ * @param r - Randomness as bigint
1255
+ * @param sk_spend - Spending secret key as bigint
1256
+ * @returns Commitment hash as bigint
546
1257
  */
547
- declare function bigintToBytes32$1(n: bigint): Uint8Array;
1258
+ declare function computeCommitment$1(amount: bigint, r: bigint, sk_spend: bigint): Promise<bigint>;
548
1259
  /**
549
- * Convert hex string to Uint8Array
1260
+ * Generate a Poseidon commitment for a note
550
1261
  *
551
- * @param hex - Hex string (with or without 0x prefix)
552
- * @returns Decoded bytes
1262
+ * Formula: Poseidon(amount, r0, r1, pk_spend)
1263
+ * where pk_spend = Poseidon(sk0, sk1)
1264
+ *
1265
+ * This matches the withdraw_regular.circom circuit
1266
+ *
1267
+ * @param amountLamports - Amount in lamports
1268
+ * @param r - Randomness bytes (32 bytes)
1269
+ * @param skSpend - Spending secret key bytes (32 bytes)
1270
+ * @returns Commitment hash as bigint
553
1271
  */
554
- declare function hexToBytes(hex: string): Uint8Array;
1272
+ declare function generateCommitmentAsync(amountLamports: number, r: Uint8Array, skSpend: Uint8Array): Promise<bigint>;
555
1273
  /**
556
- * Convert Uint8Array to hex string
1274
+ * Generate a Poseidon commitment for a note (sync wrapper)
1275
+ * Returns bytes instead of bigint for backward compatibility
557
1276
  *
558
- * @param bytes - Bytes to encode
559
- * @param prefix - Whether to include 0x prefix (default: false)
560
- * @returns Hex-encoded string
1277
+ * @deprecated Use generateCommitmentAsync instead
561
1278
  */
562
- declare function bytesToHex(bytes: Uint8Array, prefix?: boolean): string;
1279
+ declare function generateCommitment(_amountLamports: number, _r: Uint8Array, _skSpend: Uint8Array): Uint8Array;
563
1280
  /**
564
- * Generate random bytes using Web Crypto API
1281
+ * Compute nullifier = Poseidon(sk0, sk1, leaf_index)
1282
+ * (matching withdraw_regular.circom)
565
1283
  *
566
- * @param length - Number of bytes to generate
567
- * @returns Random bytes
1284
+ * This is the test-style function that takes bigint directly
1285
+ *
1286
+ * @param sk_spend - Spending secret key as bigint
1287
+ * @param leafIndex - Index in the Merkle tree as bigint
1288
+ * @returns Nullifier as bigint
568
1289
  */
569
- declare function randomBytes(length: number): Uint8Array;
1290
+ declare function computeNullifier$1(sk_spend: bigint, leafIndex: bigint): Promise<bigint>;
570
1291
  /**
571
- * Validate a hex string format
1292
+ * Compute nullifier from spending key and leaf index
572
1293
  *
573
- * @param hex - Hex string to validate
574
- * @param expectedLength - Expected length in bytes (optional)
575
- * @returns True if valid hex string
1294
+ * Formula: Poseidon(sk0, sk1, leaf_index)
1295
+ *
1296
+ * This matches the circuit's nullifier computation
1297
+ *
1298
+ * @param skSpend - Spending secret key bytes (32 bytes) or hex string
1299
+ * @param leafIndex - Index in the Merkle tree
1300
+ * @returns Nullifier as bigint
576
1301
  */
577
- declare function isValidHex(hex: string, expectedLength?: number): boolean;
1302
+ declare function computeNullifierAsync(skSpend: Uint8Array | string, leafIndex: number): Promise<bigint>;
578
1303
  /**
579
- * Groth16 proof structure from snarkjs
1304
+ * Compute nullifier (sync wrapper for backward compatibility)
1305
+ * @deprecated Use computeNullifierAsync instead
580
1306
  */
581
- interface Groth16Proof {
582
- pi_a: string[];
583
- pi_b: string[][];
584
- pi_c: string[];
585
- protocol: string;
586
- curve: string;
587
- }
1307
+ declare function computeNullifierSync(_skSpend: Uint8Array, _leafIndex: number): Uint8Array;
588
1308
  /**
589
- * Convert snarkjs Groth16 proof to 256-byte format for Solana (pinocchio-groth16 format)
1309
+ * Compute outputs hash from recipients and amounts
590
1310
  *
591
- * Based on pinocchio-groth16/src/proof_parser.rs convert_proof function:
592
- * - Proof_a: Must be NEGATED (as G1 point) and converted from LE to BE
593
- * - Proof_b: Converted from LE to BE (reversing each 64-byte chunk)
594
- * - Proof_c: Converted from LE to BE (reversing each 32-byte chunk)
1311
+ * Formula: Chain of Poseidon(prev_hash, addr_lo, addr_hi, amount) for each active output
595
1312
  *
596
- * Format: pi_a (64) + pi_b (128) + pi_c (64) = 256 bytes
1313
+ * This matches the withdraw_regular.circom circuit's outputs hash computation
1314
+ *
1315
+ * @param outputs - Array of {recipient: PublicKey, amount: number}
1316
+ * @returns Outputs hash as bigint
597
1317
  */
598
- declare function proofToBytes(proof: Groth16Proof): Uint8Array;
1318
+ declare function computeOutputsHashAsync(outputs: Array<{
1319
+ recipient: PublicKey;
1320
+ amount: number;
1321
+ }>): Promise<bigint>;
599
1322
  /**
600
- * Build public inputs bytes for on-chain verification
601
- * Format: root (32) + nullifier (32) + outputs_hash (32) + public_amount (8) = 104 bytes
1323
+ * Compute outputs hash for withdraw_regular circuit
1324
+ * outputs_hash = chain of Poseidon(prev_hash, addr_lo, addr_hi, amount) for each active output
1325
+ *
1326
+ * This is the test-style function that takes raw limbs
1327
+ *
1328
+ * @param outAddr - Array of [lo, hi] limb pairs for each address (5x2 array)
1329
+ * @param outAmount - Array of amounts as bigints (5 elements)
1330
+ * @param outFlags - Array of flags (1 = active, 0 = inactive) (5 elements)
1331
+ * @returns Outputs hash as bigint
602
1332
  */
603
- declare function buildPublicInputsBytes(root: bigint, nullifier: bigint, outputsHash: bigint, publicAmount: bigint): Uint8Array;
604
-
1333
+ declare function computeOutputsHash(outAddr: bigint[][], outAmount: bigint[], outFlags: number[]): Promise<bigint>;
605
1334
  /**
606
- * Fee calculation utilities for Cloak Protocol
1335
+ * Compute outputs hash (sync wrapper for backward compatibility)
1336
+ * @deprecated Use computeOutputsHashAsync instead
1337
+ */
1338
+ declare function computeOutputsHashSync(_outputs: Array<{
1339
+ recipient: PublicKey;
1340
+ amount: number;
1341
+ }>): Uint8Array;
1342
+ /**
1343
+ * Compute outputs hash for withdraw_swap circuit
1344
+ * outputs_hash = Poseidon(input_mint limbs, output_mint limbs, recipient_ata limbs, min_output_amount, public_amount)
607
1345
  *
608
- * The protocol charges a fixed fee plus a variable percentage fee
609
- * to prevent sybil attacks and cover operational costs.
1346
+ * This is the test-style function that takes raw limbs
610
1347
  *
611
- * IMPORTANT: These constants are the single source of truth for fee calculations.
612
- * When updating fees, only change these values - all other code should import from here.
1348
+ * @param inputMintLimbs - Input mint address as [lo, hi] limbs
1349
+ * @param outputMintLimbs - Output mint address as [lo, hi] limbs
1350
+ * @param recipientAtaLimbs - Recipient ATA as [lo, hi] limbs
1351
+ * @param minOutputAmount - Minimum output amount as bigint
1352
+ * @param publicAmount - Public amount as bigint
1353
+ * @returns Outputs hash as bigint
613
1354
  */
614
- /** Lamports per SOL */
615
- declare const LAMPORTS_PER_SOL = 1000000000;
616
- /** Fixed fee: 0.005 SOL (5M lamports) */
617
- declare const FIXED_FEE_LAMPORTS = 5000000;
618
- /** Variable fee numerator (3 = 0.3%) */
619
- declare const VARIABLE_FEE_NUMERATOR = 3;
620
- /** Variable fee denominator (1000 = divide by 1000) */
621
- declare const VARIABLE_FEE_DENOMINATOR = 1000;
622
- /** Variable fee rate as decimal: 0.3% = 0.003 */
623
- declare const VARIABLE_FEE_RATE: number;
624
- /** Minimum deposit amount to prevent dust/UX footguns (0.01 SOL). */
625
- declare const MIN_DEPOSIT_LAMPORTS = 10000000;
1355
+ declare function computeSwapOutputsHash(inputMintLimbs: [bigint, bigint], outputMintLimbs: [bigint, bigint], recipientAtaLimbs: [bigint, bigint], minOutputAmount: bigint, publicAmount: bigint): Promise<bigint>;
626
1356
  /**
627
- * Calculate the total protocol fee for a given amount
1357
+ * Compute outputs hash for swap transactions
628
1358
  *
629
- * Formula: FIXED_FEE + floor((amount * VARIABLE_FEE_NUMERATOR) / VARIABLE_FEE_DENOMINATOR)
1359
+ * Formula: Poseidon(input_mint_lo, input_mint_hi, output_mint_lo, output_mint_hi,
1360
+ * recipient_ata_lo, recipient_ata_hi, min_output_amount, public_amount)
630
1361
  *
631
- * @param amountLamports - Amount in lamports
632
- * @returns Total fee in lamports
1362
+ * This matches the withdraw_swap.circom circuit
633
1363
  *
634
- * @example
635
- * ```typescript
636
- * const fee = calculateFee(1_000_000_000); // 1 SOL
637
- * // Returns: 5_000_000 (fixed) + 3_000_000 (0.3%) = 8_000_000 lamports
638
- * ```
1364
+ * @param inputMint - Input token mint address (SOL = SystemProgram)
1365
+ * @param outputMint - Output token mint address
1366
+ * @param recipientAta - Recipient's associated token account
1367
+ * @param minOutputAmount - Minimum output amount in token's smallest unit
1368
+ * @param amount - Note amount in lamports (public_amount)
1369
+ * @returns Outputs hash as bigint
639
1370
  */
640
- declare function calculateFee(amountLamports: number): number;
1371
+ declare function computeSwapOutputsHashAsync(inputMint: PublicKey, outputMint: PublicKey, recipientAta: PublicKey, minOutputAmount: number, amount: number): Promise<bigint>;
641
1372
  /**
642
- * Bigint version of calculateFee (for UTXO flows that use bigint lamports).
1373
+ * Compute swap outputs hash (sync wrapper for backward compatibility)
1374
+ * @deprecated Use computeSwapOutputsHashAsync instead
643
1375
  */
644
- declare function calculateFeeBigint(amountLamports: bigint): bigint;
1376
+ declare function computeSwapOutputsHashSync(_outputMint: PublicKey, _recipientAta: PublicKey, _minOutputAmount: number, _amount: number): Uint8Array;
645
1377
  /**
646
- * Returns true if a withdrawal amount can pay fees and still leave >0 lamports.
647
- * (On-chain uses the same integer math and enforces this as well.)
1378
+ * Convert bigint to 32-byte big-endian Uint8Array
648
1379
  */
649
- declare function isWithdrawAmountSufficient(amountLamports: bigint): boolean;
1380
+ declare function bigintToBytes32$1(n: bigint): Uint8Array;
650
1381
  /**
651
- * Calculate the distributable amount after protocol fees
652
- *
653
- * This is the amount available to send to recipients.
654
- *
655
- * @param amountLamports - Total note amount in lamports
656
- * @returns Amount available for recipients in lamports
1382
+ * Convert hex string to Uint8Array
657
1383
  *
658
- * @example
659
- * ```typescript
660
- * const distributable = getDistributableAmount(1_000_000_000);
661
- * // Returns: 1_000_000_000 - 7_500_000 = 992_500_000 lamports
662
- * ```
1384
+ * @param hex - Hex string (with or without 0x prefix)
1385
+ * @returns Decoded bytes
663
1386
  */
664
- declare function getDistributableAmount(amountLamports: number): number;
1387
+ declare function hexToBytes(hex: string): Uint8Array;
665
1388
  /**
666
- * Format lamports as SOL string
1389
+ * Convert Uint8Array to hex string
667
1390
  *
668
- * @param lamports - Amount in lamports
669
- * @param decimals - Number of decimal places (default: 9)
670
- * @returns Formatted string (e.g., "1.000000000")
1391
+ * @param bytes - Bytes to encode
1392
+ * @param prefix - Whether to include 0x prefix (default: false)
1393
+ * @returns Hex-encoded string
1394
+ */
1395
+ declare function bytesToHex(bytes: Uint8Array, prefix?: boolean): string;
1396
+ /**
1397
+ * Thrown when no cryptographically secure randomness source is reachable.
671
1398
  *
672
- * @example
673
- * ```typescript
674
- * formatAmount(1_000_000_000); // "1.000000000"
675
- * formatAmount(1_500_000_000); // "1.500000000"
676
- * formatAmount(123_456_789, 4); // "0.1235"
677
- * ```
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.
678
1402
  */
679
- declare function formatAmount(lamports: number, decimals?: number): string;
1403
+ declare class InsecureRandomnessError extends CloakError {
1404
+ constructor(message: string, originalError?: Error);
1405
+ }
680
1406
  /**
681
- * Parse SOL string to lamports
1407
+ * Generate cryptographically secure random bytes.
682
1408
  *
683
- * @param sol - SOL amount as string (e.g., "1.5")
684
- * @returns Amount in lamports
685
- * @throws Error if invalid format
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.
686
1414
  *
687
- * @example
688
- * ```typescript
689
- * parseAmount("1.5"); // 1_500_000_000
690
- * parseAmount("0.001"); // 1_000_000
691
- * ```
1415
+ * @param length - Number of bytes to generate
1416
+ * @returns Random bytes
1417
+ * @throws {InsecureRandomnessError} If no cryptographically secure source is available
692
1418
  */
693
- declare function parseAmount(sol: string): number;
1419
+ declare function randomBytes(length: number): Uint8Array;
694
1420
  /**
695
- * Validate that outputs sum equals expected amount
1421
+ * Validate a hex string format
696
1422
  *
697
- * @param outputs - Array of output amounts
698
- * @param expectedTotal - Expected total amount
699
- * @returns True if amounts match
1423
+ * @param hex - Hex string to validate
1424
+ * @param expectedLength - Expected length in bytes (optional)
1425
+ * @returns True if valid hex string
700
1426
  */
701
- declare function validateOutputsSum(outputs: Array<{
702
- amount: number;
703
- }>, expectedTotal: number): boolean;
1427
+ declare function isValidHex(hex: string, expectedLength?: number): boolean;
704
1428
  /**
705
- * Calculate relay fee from basis points
1429
+ * Groth16 proof structure from snarkjs
1430
+ */
1431
+ interface Groth16Proof {
1432
+ pi_a: string[];
1433
+ pi_b: string[][];
1434
+ pi_c: string[];
1435
+ protocol: string;
1436
+ curve: string;
1437
+ }
1438
+ /**
1439
+ * Convert snarkjs Groth16 proof to 256-byte format for Solana (pinocchio-groth16 format)
706
1440
  *
707
- * @param amountLamports - Amount in lamports
708
- * @param feeBps - Fee in basis points (100 bps = 1%)
709
- * @returns Relay fee in lamports
1441
+ * Based on pinocchio-groth16/src/proof_parser.rs convert_proof function:
1442
+ * - Proof_a: Must be NEGATED (as G1 point) and converted from LE to BE
1443
+ * - Proof_b: Converted from LE to BE (reversing each 64-byte chunk)
1444
+ * - Proof_c: Converted from LE to BE (reversing each 32-byte chunk)
710
1445
  *
711
- * @example
712
- * ```typescript
713
- * calculateRelayFee(1_000_000_000, 50); // 0.5% = 5_000_000 lamports
714
- * ```
1446
+ * Format: pi_a (64) + pi_b (128) + pi_c (64) = 256 bytes
715
1447
  */
716
- declare function calculateRelayFee(amountLamports: number, feeBps: number): number;
1448
+ declare function proofToBytes(proof: Groth16Proof): Uint8Array;
1449
+ /**
1450
+ * Build public inputs bytes for on-chain verification
1451
+ * Format: root (32) + nullifier (32) + outputs_hash (32) + public_amount (8) = 104 bytes
1452
+ */
1453
+ declare function buildPublicInputsBytes(root: bigint, nullifier: bigint, outputsHash: bigint, publicAmount: bigint): Uint8Array;
717
1454
 
718
1455
  /**
719
- * Validate that a string is a valid Solana public key.
1456
+ * Validate a Solana public key
720
1457
  *
721
1458
  * @param address - Address string to validate
722
1459
  * @returns True if valid Solana address
723
1460
  */
724
1461
  declare function isValidSolanaAddress(address: string): boolean;
1462
+ /**
1463
+ * Validate a Cloak note structure
1464
+ *
1465
+ * @param note - Note object to validate
1466
+ * @throws Error if invalid
1467
+ */
1468
+ declare function validateNote(note: any): asserts note is CloakNote;
1469
+ /**
1470
+ * Validate that a note is ready for withdrawal
1471
+ *
1472
+ * @param note - Note to validate
1473
+ * @throws Error if note cannot be used for withdrawal
1474
+ */
1475
+ declare function validateWithdrawableNote(note: CloakNote): void;
1476
+ /**
1477
+ * Validate transfer recipients
1478
+ *
1479
+ * @param recipients - Array of transfers to validate
1480
+ * @param totalAmount - Total amount available
1481
+ * @throws Error if invalid
1482
+ */
1483
+ declare function validateTransfers(recipients: Array<{
1484
+ recipient: PublicKey;
1485
+ amount: number;
1486
+ }>, totalAmount: number): void;
725
1487
 
726
1488
  /**
727
1489
  * Network Utilities
@@ -927,6 +1689,67 @@ declare class RelayInternalError extends Error {
927
1689
  */
928
1690
  cachedTreeFromChain?: MerkleTree | undefined);
929
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;
930
1753
  /**
931
1754
  * Classify a relay error (response body text + HTTP status) into one of the
932
1755
  * structured error types above, or fall back to RelayInternalError.
@@ -1307,6 +2130,141 @@ declare function verifyUtxos(utxos: Utxo[], connection: Connection, programId: P
1307
2130
  */
1308
2131
  declare function preflightNullifiers(utxos: Utxo[], connection: Connection, programId: PublicKey, commitment?: "processed" | "confirmed" | "finalized"): Promise<void>;
1309
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
+
1310
2268
  /**
1311
2269
  * Structured Logger for Cloak SDK
1312
2270
  *
@@ -1604,6 +2562,36 @@ declare function fetchRiskQuoteIx(connection: Connection, wallet: PublicKey, ran
1604
2562
  queue: PublicKey;
1605
2563
  }>;
1606
2564
 
2565
+ /**
2566
+ * Encrypted Output Helpers
2567
+ *
2568
+ * Functions for creating encrypted outputs that enable note scanning
2569
+ */
2570
+
2571
+ /**
2572
+ * Prepare encrypted output for scanning by wallet owner
2573
+ *
2574
+ * @param note - Note to encrypt
2575
+ * @param cloakKeys - Wallet's Cloak keys (for self-encryption)
2576
+ * @returns Base64-encoded encrypted output
2577
+ */
2578
+ declare function prepareEncryptedOutput(note: CloakNote, cloakKeys: CloakKeyPair): string;
2579
+ /**
2580
+ * Prepare encrypted output for a specific recipient
2581
+ *
2582
+ * @param note - Note to encrypt
2583
+ * @param recipientPvkHex - Recipient's public view key (hex)
2584
+ * @returns Base64-encoded encrypted output
2585
+ */
2586
+ declare function prepareEncryptedOutputForRecipient(note: CloakNote, recipientPvkHex: string): string;
2587
+ /**
2588
+ * Simple base64 encoding for v1.0 notes (no encryption)
2589
+ *
2590
+ * @param note - Note to encode
2591
+ * @returns Base64-encoded note data
2592
+ */
2593
+ declare function encodeNoteSimple(note: CloakNote): string;
2594
+
1607
2595
  /**
1608
2596
  * Wallet Integration Helpers
1609
2597
  *
@@ -1631,6 +2619,58 @@ declare function signTransaction<T extends Transaction>(transaction: T, wallet:
1631
2619
  */
1632
2620
  declare function keypairToAdapter(keypair: Keypair): WalletAdapter;
1633
2621
 
2622
+ /**
2623
+ * Create a deposit instruction
2624
+ *
2625
+ * Deposits SOL into the Cloak protocol by creating a commitment.
2626
+ *
2627
+ * Instruction format:
2628
+ * - Byte 0: Discriminant (0x01 for Deposit, per ShieldPoolInstruction enum)
2629
+ * - Bytes 1-8: Amount (u64, little-endian)
2630
+ * - Bytes 9-40: Commitment (32 bytes)
2631
+ *
2632
+ * @param params - Deposit parameters
2633
+ * @returns Transaction instruction
2634
+ *
2635
+ * @example
2636
+ * ```typescript
2637
+ * const instruction = createDepositInstruction({
2638
+ * programId: CLOAK_PROGRAM_ID,
2639
+ * payer: wallet.publicKey,
2640
+ * pool: POOL_ADDRESS,
2641
+ * commitments: COMMITMENTS_ADDRESS,
2642
+ * amount: 1_000_000_000, // 1 SOL
2643
+ * commitment: commitmentBytes
2644
+ * });
2645
+ * ```
2646
+ */
2647
+ declare function createDepositInstruction(params: {
2648
+ programId: PublicKey;
2649
+ payer: PublicKey;
2650
+ pool: PublicKey;
2651
+ merkleTree: PublicKey;
2652
+ amount: number;
2653
+ commitment: Uint8Array;
2654
+ }): TransactionInstruction;
2655
+ /**
2656
+ * Deposit instruction parameters for type safety
2657
+ */
2658
+ interface DepositInstructionParams {
2659
+ programId: PublicKey;
2660
+ payer: PublicKey;
2661
+ pool: PublicKey;
2662
+ merkleTree: PublicKey;
2663
+ amount: number;
2664
+ commitment: Uint8Array;
2665
+ }
2666
+ /**
2667
+ * Validate deposit instruction parameters
2668
+ *
2669
+ * @param params - Parameters to validate
2670
+ * @throws Error if invalid
2671
+ */
2672
+ declare function validateDepositParams(params: DepositInstructionParams): void;
2673
+
1634
2674
  /**
1635
2675
  * Program Derived Address (PDA) utilities for Shield Pool
1636
2676
  *
@@ -1683,6 +2723,111 @@ declare function getNullifierPDA(poolPubkey: PublicKey, nullifier: Uint8Array |
1683
2723
  * @returns [PublicKey, bump] - The swap state PDA and its bump seed
1684
2724
  */
1685
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>;
1686
2831
 
1687
2832
  /**
1688
2833
  * On-chain Merkle proof computation
@@ -1798,7 +2943,12 @@ declare function buildMerkleTreeFromRelay(relayUrl: string, options?: {
1798
2943
  maxRetries?: number;
1799
2944
  waitForIndex?: number;
1800
2945
  }): Promise<MerkleTree>;
1801
- declare function buildMerkleTreeFromChain(connection: Connection, programId: PublicKey, merkleTree: PublicKey, onProgress?: (message: string) => void): Promise<MerkleTree>;
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>;
1802
2952
  /**
1803
2953
  * Pre-flight root validation
1804
2954
  *
@@ -1851,6 +3001,111 @@ declare function preflightCheck(relayUrl: string, rootHex: string): Promise<{
1851
3001
  estimatedWaitMs?: number;
1852
3002
  }>;
1853
3003
 
3004
+ /**
3005
+ * The SDK's host-environment predicates, in one place (X-S-02C).
3006
+ *
3007
+ * ── The defect this closes ────────────────────────────────────────────────────────────────────
3008
+ * The merkle-from-chain rebuild was guarded by TWO independently written predicates that disagreed
3009
+ * about React Native:
3010
+ *
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.
3018
+ *
3019
+ * So one half of the same mechanism classified RN as a browser and the other half classified it as
3020
+ * not-a-browser.
3021
+ *
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.
3030
+ *
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.
3036
+ *
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.
3050
+ */
3051
+ /**
3052
+ * True on React Native. `navigator.product === "ReactNative"` is the canonical flag and is what
3053
+ * `utils/proof-generation.ts` has always used.
3054
+ */
3055
+ declare function isReactNative(): boolean;
3056
+ /**
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.
3060
+ */
3061
+ declare function isBrowser(): boolean;
3062
+ /**
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.
3065
+ *
3066
+ * Use for CONSERVATIVE opt-outs — expensive work that should not be started speculatively.
3067
+ */
3068
+ declare function isBrowserLike(): boolean;
3069
+
3070
+ /**
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
+
1854
3109
  /**
1855
3110
  * UTXO Transaction Methods
1856
3111
  *
@@ -1860,23 +3115,73 @@ declare function preflightCheck(relayUrl: string, rootHex: string): Promise<{
1860
3115
  * - partialWithdraw(): Withdraw with change
1861
3116
  */
1862
3117
 
1863
- declare const DEFAULT_TRANSACTION_CIRCUITS_URL = "https://storage.googleapis.com/cloak-circuits/circuits/0.1.0";
3118
+ /**
3119
+ * Can this host attempt the last-resort "rebuild the merkle tree from chain" recovery?
3120
+ *
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.
3127
+ *
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.
3131
+ *
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.
3134
+ */
3135
+ declare function canRebuildMerkleTreeFromChain(): boolean;
3136
+
3137
+ /**
3138
+ * Base the ceremony-frozen `transaction` artifacts are fetched from by default.
3139
+ *
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.
3147
+ */
3148
+ declare const DEFAULT_TRANSACTION_CIRCUITS_URL: string | null;
3149
+
1864
3150
  /**
1865
3151
  * Set circuits base path: local directory containing `transaction_js/` and `transaction_final.zkey`,
1866
- * or an `http(s)` base URL to those artifacts (Node will download once per process to a temp dir).
3152
+ * or an `http(s)` base URL to those artifacts (loaded into memory once per process).
3153
+ *
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.
1867
3157
  */
1868
3158
  declare function setCircuitsPath(next: string): void;
1869
3159
  /**
1870
- * Get the current circuits path
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`).
3162
+ */
3163
+ declare function getCircuitsPath(): string | null;
3164
+ /**
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.
1871
3172
  */
1872
- declare function getCircuitsPath(): string;
1873
- declare function computeChainNoteHash(publicAmount: bigint, extDataHash: bigint, noteTimestamp: bigint, noteCommitment: bigint): Promise<bigint>;
3173
+ declare function resolveCircuitsBase(explicit?: string): string;
1874
3174
  /**
1875
- * Compute extDataHash for binding proof to external parameters
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.
1876
3179
  *
1877
- * extDataHash = Poseidon(recipient, relayerFee, relayer)
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.
1878
3182
  */
1879
- 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>;
1880
3185
  /**
1881
3186
  * Options for transact operation
1882
3187
  */
@@ -1894,8 +3199,8 @@ interface TransactOptions {
1894
3199
  /** Relayer address for fee payment */
1895
3200
  relayer?: PublicKey;
1896
3201
  /**
1897
- * Relay URL override.
1898
- * Defaults to `https://api.cloak.ag` when omitted.
3202
+ * Relay URL. Never defaulted: resolved from this option, then from `CLOAK_RELAY_URL`.
3203
+ * Name production explicitly (`https://api.cloak.ag`).
1899
3204
  */
1900
3205
  relayUrl?: string;
1901
3206
  /** Keypair of the depositor (signs the deposit transaction) - for programmatic use */
@@ -1981,6 +3286,13 @@ interface TransactOptions {
1981
3286
  * Each entry must be base64-encoded note bytes.
1982
3287
  */
1983
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;
1984
3296
  /**
1985
3297
  * Optional nk (32 bytes, hex or bytes) for diversified chain note encryption (Phase 3).
1986
3298
  * When provided, 2 per-output encrypted notes are embedded on-chain.
@@ -1992,6 +3304,32 @@ interface TransactOptions {
1992
3304
  * passing nk at every call site — e.g. pass a getter from your key manager.
1993
3305
  */
1994
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;
1995
3333
  /**
1996
3334
  * Cached Merkle tree from a previous transaction.
1997
3335
  * When provided, the SDK skips fetching commitments from the relay and uses this
@@ -2067,17 +3405,119 @@ interface RiskQuoteInstructionResponse {
2067
3405
  data: string;
2068
3406
  };
2069
3407
  }
2070
- /**
2071
- * Fetch the risk quote instruction from a backend (e.g. relay /risk-quote).
2072
- * The backend should call Switchboard's fetchQuoteIx for the given wallet
2073
- * (Range Risk API) and return the serialized instruction.
2074
- * See: https://www.range.org/blog/integrate-range-onchain-risk-verifier-into-your-solana-program
2075
- */
2076
- declare function fetchRiskQuoteInstruction(riskQuoteUrl: string, wallet: PublicKey, options?: {
3408
+ declare function fetchRiskQuoteInstruction(riskQuoteUrl: string, wallet: PublicKey, options: {
3409
+ poolMint: PublicKey;
2077
3410
  recipient?: PublicKey;
2078
3411
  amount?: bigint;
2079
3412
  token?: PublicKey;
3413
+ commitment?: string | Uint8Array;
3414
+ context?: "deposit" | "send" | "withdraw";
3415
+ nullifier0?: string;
3416
+ nullifier1?: string;
2080
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>;
2081
3521
  declare function transact(params: TransactParams, options: TransactOptions): Promise<TransactResult>;
2082
3522
  /**
2083
3523
  * Execute a shield-to-shield transfer
@@ -2142,6 +3582,25 @@ interface UtxoSwapResult extends TransactResult {
2142
3582
  nullifier: string;
2143
3583
  /** Relay request ID for swap execution status */
2144
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
+ };
2145
3604
  }
2146
3605
  /**
2147
3606
  * Execute a UTXO swap withdrawal
@@ -2171,24 +3630,755 @@ declare function swapUtxo(params: UtxoSwapParams, options: TransactOptions): Pro
2171
3630
  declare function swapWithChange(inputUtxos: Utxo[], swapAmount: bigint, outputMint: PublicKey, recipientAta: PublicKey, minOutputAmount: bigint, options: TransactOptions, recipientWallet?: PublicKey): Promise<UtxoSwapResult>;
2172
3631
 
2173
3632
  /**
2174
- * Compact deterministic chain note format (current format only).
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`.
3735
+ *
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)]
2175
4352
  *
2176
- * Envelope: [version 1][ciphertext (timestamp + tag)]
4353
+ * Plaintext layout by version:
4354
+ * - v3: [timestamp: u64 LE (8)][noteSalt: u256 BE (32)]
4355
+ * - v2: [timestamp: u64 LE (8)]
2177
4356
  */
2178
4357
  type ChainNoteTxType = "deposit" | "withdraw" | "transfer" | "swap" | "unknown";
2179
4358
  interface CompactChainNote {
2180
4359
  timestamp: bigint;
2181
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;
2182
4366
  }
2183
4367
  /**
2184
4368
  * Encrypt a compact deterministic chain note.
2185
- * Payload is minimal to keep relay withdrawals under Solana packet size limits.
2186
- * Envelope: [version 1][ciphertext (timestamp + tag)]
4369
+ *
4370
+ * v3 carries noteSalt so the recipient can recompute and verify the public
4371
+ * chainNoteHash without making the output commitment linkable by observers.
2187
4372
  */
2188
- declare function encryptCompactChainNote(timestamp: bigint, nk: Uint8Array, commitmentHex: string): Promise<Uint8Array>;
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>;
2189
4378
  /**
2190
4379
  * Decrypt a compact deterministic chain note with candidate output commitments.
2191
4380
  * Tries each commitment-derived key until AES-GCM authentication succeeds.
4381
+ * Accepts current v3 notes and legacy v2 notes.
2192
4382
  */
2193
4383
  declare function decryptCompactChainNote(noteBytes: Uint8Array, nk: Uint8Array, candidateCommitments: string[]): Promise<CompactChainNote>;
2194
4384
  declare function chainNoteToBase64(noteBytes: Uint8Array): string;
@@ -2257,6 +4447,30 @@ interface ScanResult {
2257
4447
  lastSignature?: string;
2258
4448
  /** Number of RPC getTransaction calls actually made (for diagnostics). */
2259
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;
2260
4474
  }
2261
4475
  /** Options for `scanTransactions`. */
2262
4476
  interface ScanOptions {
@@ -2292,15 +4506,74 @@ interface ScanOptions {
2292
4506
  * matching the wallet's associated token account against the on-chain recipient ATA.
2293
4507
  */
2294
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;
2295
4562
  }
2296
4563
  /**
2297
- * Scan on-chain transactions for the Cloak program, extract chain
2298
- * notes, trial-decrypt with the provided viewing key, verify integrity,
2299
- * 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.
2300
4566
  *
2301
- * This is fully self-contained it only needs a Solana RPC connection
2302
- * and the user's viewing key (private).
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.
2303
4572
  */
4573
+ declare function scanRecipientDeliveryNotes(opts: ScanRecipientDeliveryOptions): Promise<{
4574
+ notes: DeliveredNote[];
4575
+ rpcCalls: number;
4576
+ }>;
2304
4577
  declare function scanTransactions(opts: ScanOptions): Promise<ScanResult>;
2305
4578
  /** JSON-serializable compliance report (numbers instead of bigint). Used for cache and display. */
2306
4579
  interface ComplianceReport {
@@ -2474,25 +4747,10 @@ declare class SimpleWallet {
2474
4747
  * Cloak SDK - TypeScript SDK for Private Transactions on Solana
2475
4748
  *
2476
4749
  * @packageDocumentation
2477
- *
2478
- * # 0.1.6 — breaking changes
2479
- *
2480
- * The legacy `CloakNote` API (`CloakSDK.deposit/privateTransfer/withdraw/
2481
- * send/swap`, `createDepositInstruction`, `generateNote`, `withdraw_regular`
2482
- * proofs, etc.) was removed because every code path eventually emitted a
2483
- * legacy `[discriminator: 1, amount: u64, commitment: 32]` instruction that
2484
- * the deployed shield-pool program no longer accepts (tag `1` is now
2485
- * `TransactSwap`, not `Deposit`). The OLD 3-input commitment scheme is
2486
- * also incompatible with the deployed `transaction.circom` UTXO circuit.
2487
- *
2488
- * Use the functional UTXO API instead — `transact`, `transfer`,
2489
- * `partialWithdraw`, `fullWithdraw`, `swapUtxo`, `createUtxo`,
2490
- * `createZeroUtxo`, `generateUtxoKeypair`. The `CloakSDK` class is kept as
2491
- * a thin config + read-only chain helper.
2492
4750
  */
2493
4751
 
2494
- declare const VERSION = "0.1.6";
2495
- /** True when scanner supports TransactSwap (tag 1). */
4752
+ declare const VERSION = "0.2.0";
4753
+ /** True when scanner supports TransactSwap (tag 1). Check this to verify the correct SDK bundle is loaded. */
2496
4754
  declare const SCANNER_SUPPORTS_TRANSACT_SWAP = true;
2497
4755
 
2498
- export { CLOAK_PROGRAM_ID, type ChainNoteTxType, type CloakConfig, CloakError, type CloakKeyPair, CloakSDK, type CloakSDKOptions, type CommitmentEntry, type CommitmentsResponse, type CompactChainNote, type ComplianceReport, type ComplianceTxType, DEFAULT_TRANSACTION_CIRCUITS_URL, 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, MemoryStorageAdapter, type MerkleProof, type MerkleRootResponse, MerkleTree, NATIVE_SOL_MINT, type Network, type NoteData, type OnchainMerkleProof, 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 TransactOptions, type TransactParams, type TransactResult, type TransactionMetadata, 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 WithdrawSubmissionResult, bigintToBytes32$1 as bigintToBytes32, bigintToHex, buildMerkleTree, buildMerkleTreeFromChain, buildMerkleTreeFromRelay, buildPublicInputsBytes, bytesToHex, calculateFee, calculateFeeBigint, calculateRelayFee, chainNoteFromBase64, chainNoteToBase64, classifyRelayError, computeChainNoteHash, computeExtDataHash, computeMerkleRoot, computeProofForLatestDeposit, computeProofFromChain, computeSignature, computeCommitment as computeUtxoCommitment, computeNullifier as computeUtxoNullifier, createCloakError, createLogger, createUtxo, createZeroUtxo, decryptCompactChainNote, decryptComplianceMetadataWithMasterKey, decryptTransactionMetadata, deriveDiversifiedViewingKey, deriveDiversifier, derivePublicKey, deriveSpendKey, deriveUserCompliancePublicKey, deriveUserComplianceScalar, deriveUtxoKeypairFromSpendKey, deriveViewKey, deriveViewingKeyFromNk, deriveViewingKeyFromSpendKey, deriveViewingKeyFromUtxoPrivateKey, deserializeUtxo, detectNetworkFromRpcUrl, encryptCompactChainNote, encryptNoteForRecipient, encryptTransactionMetadata, encryptTransactionMetadataBundle, expandSpendKey, exportKeys, fetchCommitments, fetchRiskQuoteInstruction, fetchRiskQuoteIx, formatAmount, formatComplianceCsv, formatErrorForLogging, formatSol, fullWithdraw, generateCloakKeys, generateMasterSeed, generateUtxoKeypair, generateViewingKeyPair, getAddressExplorerUrl, getCircuitsPath, getDistributableAmount, getExplorerUrl, getNkFromUtxoPrivateKey, getNullifierPDA, getPublicKey, getRpcUrlForNetwork, getShieldPoolPDAs, getSwapStatePDA, hexToBigint$1 as hexToBigint, hexToBytes, importKeys, isDebugEnabled, isRootNotFoundError, isValidHex, isValidRpcUrl, isValidSolanaAddress, isWithdrawAmountSufficient, keypairToAdapter, parseAmount, parseError, parseRelayErrorResponse, parseTransactionError, partialWithdraw, poseidonHash, preflightCheck, preflightNullifiers, proofToBytes, pubkeyToFieldElement, pubkeyToLimbs, randomBytes, randomFieldElement, readMerkleTreeState, registerViewingKey, scanNotesForWallet, scanTransactions, sdkLogger, selectUtxos, sendTransaction, serializeUtxo, setCircuitsPath, setDebugMode, signTransaction, splitTo2Limbs, sumUtxoAmounts, swapUtxo, swapWithChange, toComplianceReport, transact, transfer, truncate, tryDecryptNote, bigintToBytes32 as utxoBigintToBytes32, utxoEquals, hexToBigint as utxoHexToBigint, validateOutputsSum, validateRoot, validateWalletConnected, 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 };