@cloak.dev/sdk 0.2.0 → 0.2.2

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.ts CHANGED
@@ -1,4 +1,24 @@
1
- import { PublicKey, Transaction, Connection, AddressLookupTableAccount, TransactionInstruction, Keypair, SendOptions, VersionedTransaction } from '@solana/web3.js';
1
+ import { PublicKey, Transaction, AddressLookupTableAccount, Connection, TransactionInstruction, Keypair, SendOptions, VersionedTransaction } from '@solana/web3.js';
2
+
3
+ /**
4
+ * The deployed Cloak program.
5
+ *
6
+ * This lived in `core/CloakSDK.ts` until that class was removed. It is protocol
7
+ * configuration, not client state: every PDA derivation, every scan and every
8
+ * `transact()` submission is addressed to it, so it belongs beside the other
9
+ * pinned protocol facts in `config/` rather than inside one client object.
10
+ */
11
+
12
+ /**
13
+ * Address of the Cloak shield-pool program on Solana mainnet.
14
+ *
15
+ * The same address is used by every environment the SDK talks to (a local
16
+ * Surfpool fork mirrors mainnet, so it carries the same program id). Callers
17
+ * that need a different deployment pass an explicit `programId` to the function
18
+ * they are calling — `getShieldPoolPDAs`, `transact`, `scanTransactions` and
19
+ * friends all take one.
20
+ */
21
+ declare const CLOAK_PROGRAM_ID: PublicKey;
2
22
 
3
23
  type ComplianceTxType = "deposit" | "withdraw" | "send" | "swap";
4
24
  interface TransactionMetadata {
@@ -454,415 +474,6 @@ declare function exportKeys(keys: CloakKeyPair): string;
454
474
  */
455
475
  declare function importKeys(exported: string): CloakKeyPair;
456
476
 
457
- /**
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.)
462
- */
463
-
464
- /**
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.
469
- */
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;
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
477
  /**
867
478
  * Serialize a note to JSON string
868
479
  *
@@ -1013,18 +624,12 @@ declare function calculateRelayFee(amountLamports: number, feeBps: number): numb
1013
624
  * Storage is handled externally via StorageAdapter.
1014
625
  *
1015
626
  * Core functionality:
1016
- * - Generate notes (v1.0 and v2.0)
627
+ * - Generate notes from wallet keys
1017
628
  * - Parse and validate notes
1018
629
  * - Note utilities (formatting, fees, etc.)
1019
630
  * - Key management (without storage)
1020
631
  */
1021
632
 
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
633
  /**
1029
634
  * Generate a note using wallet's spend key (v2.0 recommended)
1030
635
  * Uses Poseidon hashing to match circuit implementation
@@ -1087,9 +692,94 @@ declare function getPublicViewKey(keys: CloakKeyPair): string;
1087
692
  */
1088
693
  declare function getViewKey(keys: CloakKeyPair): ViewKey;
1089
694
  /**
1090
- * Get recipient amount after fees
695
+ * Get recipient amount after fees
696
+ */
697
+ declare function getRecipientAmount(amountLamports: number): number;
698
+
699
+ /**
700
+ * Storage Interface
701
+ *
702
+ * Defines a pluggable storage interface for notes and keys.
703
+ * Applications can implement their own storage (localStorage, IndexedDB, file system, etc.)
704
+ */
705
+
706
+ /**
707
+ * Storage adapter interface
708
+ *
709
+ * Implement this interface to provide custom storage for notes and keys.
710
+ * The SDK will use this adapter for all persistence operations.
711
+ */
712
+ interface StorageAdapter {
713
+ /**
714
+ * Save a note
715
+ */
716
+ saveNote(note: CloakNote): Promise<void> | void;
717
+ /**
718
+ * Load all notes
719
+ */
720
+ loadAllNotes(): Promise<CloakNote[]> | CloakNote[];
721
+ /**
722
+ * Update a note
723
+ */
724
+ updateNote(commitment: string, updates: Partial<CloakNote>): Promise<void> | void;
725
+ /**
726
+ * Delete a note
727
+ */
728
+ deleteNote(commitment: string): Promise<void> | void;
729
+ /**
730
+ * Clear all notes
731
+ */
732
+ clearAllNotes(): Promise<void> | void;
733
+ /**
734
+ * Save wallet keys
735
+ */
736
+ saveKeys(keys: CloakKeyPair): Promise<void> | void;
737
+ /**
738
+ * Load wallet keys
739
+ */
740
+ loadKeys(): Promise<CloakKeyPair | null> | CloakKeyPair | null;
741
+ /**
742
+ * Delete wallet keys
743
+ */
744
+ deleteKeys(): Promise<void> | void;
745
+ }
746
+ /**
747
+ * In-memory storage adapter (default, no persistence)
748
+ *
749
+ * Useful for testing or when storage is handled externally
750
+ */
751
+ declare class MemoryStorageAdapter implements StorageAdapter {
752
+ private notes;
753
+ private keys;
754
+ saveNote(note: CloakNote): void;
755
+ loadAllNotes(): CloakNote[];
756
+ updateNote(commitment: string, updates: Partial<CloakNote>): void;
757
+ deleteNote(commitment: string): void;
758
+ clearAllNotes(): void;
759
+ saveKeys(keys: CloakKeyPair): void;
760
+ loadKeys(): CloakKeyPair | null;
761
+ deleteKeys(): void;
762
+ }
763
+ /**
764
+ * Browser localStorage adapter (optional, for browser environments)
765
+ *
766
+ * Only use this if you're in a browser environment and want localStorage persistence.
767
+ * Import from a separate browser-specific module.
1091
768
  */
1092
- declare function getRecipientAmount(amountLamports: number): number;
769
+ declare class LocalStorageAdapter implements StorageAdapter {
770
+ private notesKey;
771
+ private keysKey;
772
+ constructor(notesKey?: string, keysKey?: string);
773
+ private getStorage;
774
+ saveNote(note: CloakNote): void;
775
+ loadAllNotes(): CloakNote[];
776
+ updateNote(commitment: string, updates: Partial<CloakNote>): void;
777
+ deleteNote(commitment: string): void;
778
+ clearAllNotes(): void;
779
+ saveKeys(keys: CloakKeyPair): void;
780
+ loadKeys(): CloakKeyPair | null;
781
+ deleteKeys(): void;
782
+ }
1093
783
 
1094
784
  interface ViewingKeyPair {
1095
785
  privateKey: Uint8Array;
@@ -1243,26 +933,13 @@ declare function computeMerkleRoot(leaf: bigint, pathElements: bigint[], pathInd
1243
933
  * Convert hex string to bigint
1244
934
  */
1245
935
  declare function hexToBigint$1(hex: string): bigint;
1246
- /**
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
1257
- */
1258
- declare function computeCommitment$1(amount: bigint, r: bigint, sk_spend: bigint): Promise<bigint>;
1259
936
  /**
1260
937
  * Generate a Poseidon commitment for a note
1261
938
  *
1262
939
  * Formula: Poseidon(amount, r0, r1, pk_spend)
1263
940
  * where pk_spend = Poseidon(sk0, sk1)
1264
941
  *
1265
- * This matches the withdraw_regular.circom circuit
942
+ * Legacy CloakNote commitment layout (see `computeCommitment`).
1266
943
  *
1267
944
  * @param amountLamports - Amount in lamports
1268
945
  * @param r - Randomness bytes (32 bytes)
@@ -1270,110 +947,6 @@ declare function computeCommitment$1(amount: bigint, r: bigint, sk_spend: bigint
1270
947
  * @returns Commitment hash as bigint
1271
948
  */
1272
949
  declare function generateCommitmentAsync(amountLamports: number, r: Uint8Array, skSpend: Uint8Array): Promise<bigint>;
1273
- /**
1274
- * Generate a Poseidon commitment for a note (sync wrapper)
1275
- * Returns bytes instead of bigint for backward compatibility
1276
- *
1277
- * @deprecated Use generateCommitmentAsync instead
1278
- */
1279
- declare function generateCommitment(_amountLamports: number, _r: Uint8Array, _skSpend: Uint8Array): Uint8Array;
1280
- /**
1281
- * Compute nullifier = Poseidon(sk0, sk1, leaf_index)
1282
- * (matching withdraw_regular.circom)
1283
- *
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
1289
- */
1290
- declare function computeNullifier$1(sk_spend: bigint, leafIndex: bigint): Promise<bigint>;
1291
- /**
1292
- * Compute nullifier from spending key and leaf index
1293
- *
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
1301
- */
1302
- declare function computeNullifierAsync(skSpend: Uint8Array | string, leafIndex: number): Promise<bigint>;
1303
- /**
1304
- * Compute nullifier (sync wrapper for backward compatibility)
1305
- * @deprecated Use computeNullifierAsync instead
1306
- */
1307
- declare function computeNullifierSync(_skSpend: Uint8Array, _leafIndex: number): Uint8Array;
1308
- /**
1309
- * Compute outputs hash from recipients and amounts
1310
- *
1311
- * Formula: Chain of Poseidon(prev_hash, addr_lo, addr_hi, amount) for each active output
1312
- *
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
1317
- */
1318
- declare function computeOutputsHashAsync(outputs: Array<{
1319
- recipient: PublicKey;
1320
- amount: number;
1321
- }>): Promise<bigint>;
1322
- /**
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
1332
- */
1333
- declare function computeOutputsHash(outAddr: bigint[][], outAmount: bigint[], outFlags: number[]): Promise<bigint>;
1334
- /**
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)
1345
- *
1346
- * This is the test-style function that takes raw limbs
1347
- *
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
1354
- */
1355
- declare function computeSwapOutputsHash(inputMintLimbs: [bigint, bigint], outputMintLimbs: [bigint, bigint], recipientAtaLimbs: [bigint, bigint], minOutputAmount: bigint, publicAmount: bigint): Promise<bigint>;
1356
- /**
1357
- * Compute outputs hash for swap transactions
1358
- *
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)
1361
- *
1362
- * This matches the withdraw_swap.circom circuit
1363
- *
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
1370
- */
1371
- declare function computeSwapOutputsHashAsync(inputMint: PublicKey, outputMint: PublicKey, recipientAta: PublicKey, minOutputAmount: number, amount: number): Promise<bigint>;
1372
- /**
1373
- * Compute swap outputs hash (sync wrapper for backward compatibility)
1374
- * @deprecated Use computeSwapOutputsHashAsync instead
1375
- */
1376
- declare function computeSwapOutputsHashSync(_outputMint: PublicKey, _recipientAta: PublicKey, _minOutputAmount: number, _amount: number): Uint8Array;
1377
950
  /**
1378
951
  * Convert bigint to 32-byte big-endian Uint8Array
1379
952
  */
@@ -1446,11 +1019,6 @@ interface Groth16Proof {
1446
1019
  * Format: pi_a (64) + pi_b (128) + pi_c (64) = 256 bytes
1447
1020
  */
1448
1021
  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;
1454
1022
 
1455
1023
  /**
1456
1024
  * Validate a Solana public key
@@ -1692,7 +1260,7 @@ declare class RelayInternalError extends Error {
1692
1260
  /**
1693
1261
  * Raised when a submission's outcome could NOT be established as "landed" from chain state.
1694
1262
  *
1695
- * This is the failure side of settlement verification (see `core/settlement.ts`). It exists
1263
+ * This is the failure side of settlement verification (see `flows/settlement.ts`). It exists
1696
1264
  * because the campaign's worst outcome was not a failed transaction — it was an AMBIGUOUS one
1697
1265
  * whose error text carried no signature (REL-A-10: seven POSTs, the transaction landed, the SDK
1698
1266
  * threw `RelayInternalError`, and the user had nothing to look up). Losing the signature is what
@@ -2124,7 +1692,7 @@ interface VerifyUtxosResult {
2124
1692
  declare function verifyUtxos(utxos: Utxo[], connection: Connection, programId: PublicKey, commitment?: "processed" | "confirmed" | "finalized"): Promise<VerifyUtxosResult>;
2125
1693
  /**
2126
1694
  * Pre-flight gate: throw `UtxoAlreadySpentError` if any input is already
2127
- * spent on-chain. Called at the top of spend entry points in core/transact.
1695
+ * spent on-chain. Called at the top of spend entry points in flows/transact.
2128
1696
  *
2129
1697
  * Browser-safe: uses one batched RPC call.
2130
1698
  */
@@ -2272,7 +1840,7 @@ declare function assertDirectSubmissionLanded(params: {
2272
1840
  * 2026-01-23T00:51:46.489317Z INFO cloak::module: 📥 Message key=value
2273
1841
  *
2274
1842
  * Enable via:
2275
- * - SDK config: new CloakSDK({ debug: true })
1843
+ * - Code: setDebugMode(true)
2276
1844
  * - Environment: CLOAK_DEBUG=1 or DEBUG=cloak:*
2277
1845
  */
2278
1846
  type LogLevel = "DEBUG" | "INFO" | "WARN" | "ERROR";
@@ -2724,20 +2292,8 @@ declare function getNullifierPDA(poolPubkey: PublicKey, nullifier: Uint8Array |
2724
2292
  */
2725
2293
  declare function getSwapStatePDA(poolPubkey: PublicKey, nullifier: Uint8Array | Buffer, programId?: PublicKey): [PublicKey, number];
2726
2294
  /**
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).
2295
+ * M-07: derive the per-pool PoolAuthorityConfig PDA (holds the mint-scoped
2296
+ * withdraw-authorizer).
2741
2297
  *
2742
2298
  * Seeds: ["pool_authority", pool_mint]
2743
2299
  */
@@ -2759,76 +2315,6 @@ declare function getDeliveryRegistryPDA(programId?: PublicKey): PublicKey;
2759
2315
  */
2760
2316
  declare function getChainNoteRegistryPDA(programId?: PublicKey): PublicKey;
2761
2317
 
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>;
2831
-
2832
2318
  /**
2833
2319
  * On-chain Merkle proof computation
2834
2320
  *
@@ -2889,6 +2375,86 @@ declare function computeProofForLatestDeposit(connection: Connection, merkleTree
2889
2375
  leafIndex: number;
2890
2376
  }>;
2891
2377
 
2378
+ /**
2379
+ * The Cloak endpoints THIS BUILD of the SDK is allowed to talk to.
2380
+ *
2381
+ * ── Why this is a checked-in constant and not configuration ───────────────────────────────────
2382
+ * `dist/` is what `package.json` main/module/types resolve to, so `dist/` is what every consumer
2383
+ * executes. Anything a consumer can set at THEIR build or run time — `NODE_ENV`, an environment
2384
+ * variable, a bundler define, a call option — is not a pin, because we do not own the moment it is
2385
+ * resolved. `process.env.NODE_ENV === "production"` in particular is defeated by one line in a
2386
+ * consumer's bundler config or one `NODE_ENV=development` in front of `node`, so it is deliberately
2387
+ * not used anywhere in this package.
2388
+ *
2389
+ * The only value a published artifact carries that a consumer cannot supply is one that was decided
2390
+ * when WE built it. That is this file. It is compiled into the bundle, it is greppable in the
2391
+ * bundle, and changing it requires editing source and rebuilding.
2392
+ *
2393
+ * ── How to point the SDK somewhere else ───────────────────────────────────────────────────────
2394
+ * REPLACE the entry in {@link RELAY_ORIGIN_ALLOWLIST} with your local origin. Do not append:
2395
+ * appending leaves production reachable from a local build, which is exactly the pairing that made
2396
+ * a rehearsal POST real proof data to production (see SQ-F1 in
2397
+ * `src/__tests__/relay-url-no-production-default.test.ts`). Replacing makes production *unreachable*
2398
+ * from a local build, at every egress point, rather than merely "not the default".
2399
+ *
2400
+ * export const RELAY_ORIGIN_ALLOWLIST: readonly string[] = ["http://127.0.0.1:5500"];
2401
+ *
2402
+ * Inside this repo nothing else has to happen: jest (`roots: src`), `tsx examples/...` and
2403
+ * `scripts/` all resolve `@cloak.dev/sdk` to `src/index.ts` through the tsconfig path mapping, so
2404
+ * they pick the edit up with no build step. Only a consumer that imports `dist/` — the soak harness,
2405
+ * or a web/mobile checkout linked with `file:` / `npm link` — needs `npm run build` afterwards.
2406
+ *
2407
+ * `src/__tests__/relay-origin-lock.test.ts` goes red while the allowlist is local. That is the
2408
+ * mechanism working: a local allowlist cannot be committed or published without one deliberate red
2409
+ * test and a one-line `git diff` saying so. Run `git checkout src/config/relay.ts` before pushing.
2410
+ *
2411
+ * ── Scope, stated honestly ────────────────────────────────────────────────────────────────────
2412
+ * This is an integrity and product-control mechanism, not a security boundary. Anyone who can run
2413
+ * code in the consumer's process can `sed` this literal in `node_modules`, `patch-package` it, alias
2414
+ * the module, monkey-patch `globalThis.fetch`, or skip the SDK entirely. What it buys is that the
2415
+ * correct endpoint is the only one reachable BY ACCIDENT, and that any deviation is a deliberate,
2416
+ * visible, auditable edit.
2417
+ */
2418
+ /**
2419
+ * Identity of Cloak's production endpoint. This is a NAME, never a default: no code path in this
2420
+ * SDK falls back to it, and `resolveRelayUrl` still returns `undefined` when the caller passes
2421
+ * nothing (SQ-F1). It exists so a caller who genuinely wants production can say so by importing a
2422
+ * value instead of re-typing a host.
2423
+ */
2424
+ declare const CLOAK_PRODUCTION_RELAY_URL = "https://api.cloak.ag";
2425
+ /**
2426
+ * The origins this build may send a request to. Enforced at every network egress in the package by
2427
+ * `assertAllowedRelayOrigin` / `relayFetch` in `src/relay/endpoint.ts`, so it covers the entry
2428
+ * points that take a URL directly (`submitTransactToRelay`, `RelayService`, `fetchCommitments`,
2429
+ * `registerViewingKey`, `readMerkleTreeState`, …) and not only the `relayUrl` transact option.
2430
+ *
2431
+ * Typed `readonly string[]` and not `as const` on purpose: a developer replaces the entry, and a
2432
+ * literal tuple type would make that edit a `tsc` error in unrelated files.
2433
+ */
2434
+ declare const RELAY_ORIGIN_ALLOWLIST: readonly string[];
2435
+ /**
2436
+ * ONE switch, DERIVED — not a second flag to keep in sync.
2437
+ *
2438
+ * True exactly when {@link RELAY_ORIGIN_ALLOWLIST} names a loopback origin, i.e. when this artifact
2439
+ * was built for a local stack. It is what "this build is a production artifact" means for the
2440
+ * localhost-RPC guard, and because it is derived there is no state where the endpoint is local but
2441
+ * the RPC guard is still armed. The single edit above flips both.
2442
+ *
2443
+ * ── Why the build, and not the program id, the RPC URL, or the relay URL ──────────────────────
2444
+ * - **Program id**: identical on mainnet and on the whole local estate by design — a Surfpool fork
2445
+ * mirrors mainnet, so it carries the same program id (see `src/program/ids.ts`). Zero
2446
+ * discriminating power.
2447
+ * - **`detectNetworkFromRpcUrl`**: answers "localnet" for any localhost URL and defaults unknown
2448
+ * strings to "mainnet" (`src/shared/network.ts`). For Surfpool — a localhost URL mirroring
2449
+ * mainnet — that is exactly inverted, which is why `src/relay/risk-quote.ts` calls it and
2450
+ * immediately undoes the answer. It also reads its own environment variable.
2451
+ * - **"the relay URL is the production one"**: the same fact as this flag, reached by string
2452
+ * comparison, and undefined on the `relayUrl: ""` self-submit path.
2453
+ * - **This flag**: fixed at the same instant the endpoint is fixed, unchangeable by a consumer
2454
+ * without forking, and needs no run-time classification of an attacker-supplied string.
2455
+ */
2456
+ declare const BUILD_ALLOWS_LOCAL_ENDPOINTS: boolean;
2457
+
2892
2458
  /**
2893
2459
  * Relay client utilities for fetching data from the relay service
2894
2460
  */
@@ -3008,11 +2574,11 @@ declare function preflightCheck(relayUrl: string, rootHex: string): Promise<{
3008
2574
  * The merkle-from-chain rebuild was guarded by TWO independently written predicates that disagreed
3009
2575
  * about React Native:
3010
2576
  *
3011
- * - the GATE in `core/transact.ts` — `!IS_BROWSER && isMerkleClass && relayUrl`, where
2577
+ * - the GATE in `flows/transact.ts` — `!IS_BROWSER && isMerkleClass && relayUrl`, where
3012
2578
  * `IS_BROWSER` was `typeof window !== "undefined" || typeof globalThis.document !== "undefined"`,
3013
2579
  * with NO React-Native carve-out. React Native defines `window`, so RN read as a browser and the
3014
2580
  * last-resort chain replay was silently skipped there.
3015
- * - the BUILDER it guards — `utils/relay-client.ts::buildMerkleTreeFromChain`, whose own
2581
+ * - the BUILDER it guards — `relay/client.ts::buildMerkleTreeFromChain`, whose own
3016
2582
  * `isBrowser()` short-circuits on `navigator.product === "ReactNative"` and therefore explicitly
3017
2583
  * PERMITS React Native to rebuild from chain.
3018
2584
  *
@@ -3050,7 +2616,7 @@ declare function preflightCheck(relayUrl: string, rootHex: string): Promise<{
3050
2616
  */
3051
2617
  /**
3052
2618
  * True on React Native. `navigator.product === "ReactNative"` is the canonical flag and is what
3053
- * `utils/proof-generation.ts` has always used.
2619
+ * `proving/artifacts.ts` has always used.
3054
2620
  */
3055
2621
  declare function isReactNative(): boolean;
3056
2622
  /**
@@ -3068,42 +2634,137 @@ declare function isBrowser(): boolean;
3068
2634
  declare function isBrowserLike(): boolean;
3069
2635
 
3070
2636
  /**
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. */
2637
+ * How long the relay accepts a signed request after its `auth_issued_at`
2638
+ * (`REQUEST_AUTH_MAX_AGE_SECONDS`, `api/request_auth.rs`).
2639
+ *
2640
+ * A FIRST-USE request past this window is rejected outright. The expiry exception the relay grants
2641
+ * applies only to an exact replay of a request whose durable row already exists, which by
2642
+ * definition never happens for a request that has not been accepted once.
2643
+ */
2644
+ declare const REQUEST_AUTH_MAX_AGE_SECONDS = 300;
2645
+ /**
2646
+ * How far ahead of the relay's clock a request may be stamped
2647
+ * (`REQUEST_AUTH_MAX_FUTURE_SKEW_SECONDS`, `api/request_auth.rs`).
2648
+ *
2649
+ * `auth_issued_at` comes from `Date.now()`. On a server that is NTP-disciplined; in a browser it
2650
+ * is the user's own machine clock, and a laptop more than 30 seconds fast cannot authenticate at
2651
+ * all until its clock is corrected.
2652
+ */
2653
+ declare const REQUEST_AUTH_MAX_FUTURE_SKEW_SECONDS = 30;
2654
+ /**
2655
+ * The exact fields each endpoint signs. Both lists mirror the relay's `*_auth_request` builders and
2656
+ * must stay in lockstep with them: adding a field on one side alone silently invalidates every
2657
+ * signature, and the failure surfaces as a bare 401 with nothing pointing here.
2658
+ */
2659
+ declare const TRANSACT_AUTH_FIELDS: readonly ["encrypted_notes", "max_fee", "mint", "proof_bytes", "public_inputs", "recipient", "recipient_delivery_notes", "risk_quote", "sender"];
2660
+ declare const TRANSACT_SWAP_AUTH_FIELDS: readonly ["close_timed_out", "dexes", "encrypted_notes", "exclude_dexes", "max_fee", "min_output_amount", "output_mint", "proof_bytes", "public_inputs", "recipient", "recipient_ata", "refund_blinding", "refund_pubkey", "retry_request_id", "risk_quote", "route_retry_attempts", "sender", "slippage_bps", "swap_max_retries"];
2661
+ /**
2662
+ * Serialize exactly like the relay's `canonical_json`: keys sorted bytewise, no whitespace.
2663
+ *
2664
+ * SCOPE. This is the SDK's half of a byte-for-byte agreement with one specific Rust function over
2665
+ * one specific schema: the values reachable through {@link TRANSACT_AUTH_FIELDS} and
2666
+ * {@link TRANSACT_SWAP_AUTH_FIELDS}, which are ASCII keys over strings, small unsigned integers,
2667
+ * booleans, nulls, arrays and plain objects. Inside that schema the two implementations agree.
2668
+ *
2669
+ * Outside it they need not, so anything that could serialize differently on the two sides is
2670
+ * REFUSED here rather than signed into a digest that silently fails to match:
2671
+ *
2672
+ * - Non-integer, non-finite and beyond-safe-integer numbers. `serde_json` renders an f64 as Rust
2673
+ * does (`1e21`) where JavaScript renders `1e+21`, and `0.1 + 0.2` has no single spelling. Every
2674
+ * number in both field lists is a small unsigned integer (`slippage_bps`, `route_retry_attempts`,
2675
+ * `swap_max_retries`); u64 amounts already travel as decimal STRINGS for exactly this reason.
2676
+ * - Functions and symbols as object VALUES. `JSON.stringify` drops such a key from the wire body
2677
+ * while it stays in the signed view, so the two digests can never agree.
2678
+ *
2679
+ * `undefined` is NOT refused: it serializes as `null` here, `JSON.stringify` omits the key on the
2680
+ * wire, and every optional field on the relay side is `Option<T>` with `#[serde(default)]`, so the
2681
+ * relay sees `null` too. That is the same agreement {@link buildAuthRequest} relies on. The one
2682
+ * field where it does not hold is `slippage_bps`, which is why that field is checked by name.
2683
+ *
2684
+ * `bigint` is accepted and rendered as a bare decimal integer, matching serde's u64/i64 output.
2685
+ * Note that such a value cannot also go on the wire: `JSON.stringify` throws on a bigint. Use a
2686
+ * decimal string in the body, as every SDK-built body does.
2687
+ */
2688
+ declare function canonicalJson(value: unknown): string;
2689
+ /**
2690
+ * Everything an authenticated request needs EXCEPT the signature: the three fields that go on the
2691
+ * wire alongside it, plus the exact bytes to sign.
2692
+ *
2693
+ * This exists because the holder of the pen is not always a `Keypair`. A browser wallet adapter
2694
+ * exposes `signMessage(bytes): Promise<Uint8Array>` and no secret key at all, so the scheme has to
2695
+ * be reachable in two halves: build the preimage here, sign it wherever the key actually lives,
2696
+ * then put `sender` / `auth_issued_at` / `auth_nonce` and the base64 signature on the body.
2697
+ *
2698
+ * `message` is a plain ed25519 detached-signature preimage — nothing about the scheme changes
2699
+ * between a local keypair and a wallet, only who signs it.
2700
+ */
2701
+ interface RelayAuthPreimage {
2702
+ sender: string;
2703
+ auth_issued_at: string;
2704
+ auth_nonce: string;
2705
+ message: Uint8Array;
2706
+ }
2707
+ /**
2708
+ * Build the signed view and its preimage for one request, WITHOUT signing.
2709
+ *
2710
+ * `sender` is bound into the signed view and returned for the body — the relay rejects a request
2711
+ * whose authenticated sender is not also present inside the signed payload. It must be the end
2712
+ * user's own wallet: `sender` is the key screened for sanctions on a shield-to-shield send, so
2713
+ * substituting an ephemeral or service key here moves the screening off the actual user.
2714
+ *
2715
+ * `nonce` and `issued_at` are generated here, once per call. Callers that re-POST a request must
2716
+ * reuse the same preimage rather than rebuilding it, or the relay sees a brand-new request.
2717
+ */
2718
+ declare function buildRelayAuthPreimage(endpoint: string, programId: PublicKey, body: Record<string, unknown>, sender: PublicKey, nowSeconds?: number, fields?: readonly string[]): RelayAuthPreimage;
2719
+ /**
2720
+ * An async message signer standing in for a `Keypair`.
2721
+ *
2722
+ * A browser wallet adapter has no secret key to hand over — it exposes `signMessage`, and what
2723
+ * this scheme needs signed is a plain ed25519 detached signature, which is exactly what that
2724
+ * produces. Only the holder of the pen changes.
2725
+ *
2726
+ * COMPLIANCE — `walletPublicKey` becomes the request's authenticated `sender`, and `sender` is the
2727
+ * key screened for sanctions on a shield-to-shield send. It MUST be the end user's own wallet.
2728
+ * Putting an ephemeral, service-held or otherwise substituted key here moves the screening onto a
2729
+ * key that is not the user: a compliance regression, not a shortcut.
2730
+ */
2731
+ interface RelayAuthSigner {
2732
+ /** The end user's real wallet. Becomes the authenticated `sender`. Never an ephemeral key. */
2733
+ walletPublicKey: PublicKey;
2734
+ /** Wallet-adapter `signMessage`; must return the 64-byte ed25519 detached signature. */
2735
+ signMessage: (message: Uint8Array) => Promise<Uint8Array>;
2736
+ }
2737
+ /**
2738
+ * Turn a relay rejection into something the person in front of the screen can act on.
2739
+ *
2740
+ * Every string matched here is an `Error::Unauthorized` from `api/request_auth.rs`, and all of them
2741
+ * arrive as the same bare 401. Two of them are not the caller's mistake at all: an approval that
2742
+ * sat too long, and a machine clock that is simply wrong. Returns `null` for anything that is not
2743
+ * an authentication rejection, so callers can append it only when there is something to add.
2744
+ */
2745
+ declare function explainRelayAuthRejection(responseText: string): string | null;
2746
+
2747
+ /**
2748
+ * The circuit artifacts this SDK build proves against.
2749
+ *
2750
+ * One program, one circuit, one bundle: the deployed shield-pool embeds the
2751
+ * ceremony verifying key, so any other artifact set produces proofs the program
2752
+ * rejects (0x1010). There is deliberately no table, no bundle registry and no
2753
+ * version negotiation here -- an SDK build either matches the deployed program
2754
+ * or it is the wrong build.
2755
+ *
2756
+ * The digests are the security control: `loadVerifiedCircuitArtifacts` hashes
2757
+ * whatever it fetched and refuses to prove on a mismatch, so a compromised or
2758
+ * stale CDN cannot feed this build artifacts from another ceremony.
2759
+ *
2760
+ * Published and verified 2026-08-12; re-verify after any publish with
2761
+ * `packages/scripts/publish-circuits.sh --verify-only`. Two things that publish
2762
+ * established, both worth keeping in mind here:
2763
+ * - the prefix once held a half-finished upload (zkey and wasm, no witness
2764
+ * helpers), which is exactly what these digests defend against;
2765
+ * - the edge served the previous wasm for ~50 minutes after a correct upload,
2766
+ * so uploading the right bytes is not the same as serving them.
2767
+ */
3107
2768
  declare const TRANSACTION_CIRCUITS_VERSION = "0.2.0";
3108
2769
 
3109
2770
  /**
@@ -3123,7 +2784,7 @@ declare const TRANSACTION_CIRCUITS_VERSION = "0.2.0";
3123
2784
  * RN defines `window`, so the old inline `typeof window !== "undefined" || typeof document !==
3124
2785
  * "undefined"` classified RN as a browser and skipped the rebuild, removing RN's only recovery path
3125
2786
  * 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.
2787
+ * `shared/environment`, so they cannot disagree about React Native again.
3127
2788
  *
3128
2789
  * The SSR conservatism is kept: `isBrowserLike()` is true when EITHER `window` or `document` exists,
3129
2790
  * because an SSR host that polyfills only `document` must not be mistaken for Node and made to run a
@@ -3137,7 +2798,7 @@ declare function canRebuildMerkleTreeFromChain(): boolean;
3137
2798
  /**
3138
2799
  * Base the ceremony-frozen `transaction` artifacts are fetched from by default.
3139
2800
  *
3140
- * Derived from {@link TRANSACTION_CIRCUIT_BUNDLE} — the same record that pins the
2801
+ * Derived from {@link TRANSACTION_CIRCUITS_BASE_URL} — the same record that pins the
3141
2802
  * digests — so the version in the URL and the digests checked against it cannot
3142
2803
  * drift apart. It is `null` while this SDK build pins no location whose bytes
3143
2804
  * were verified to hash to those digests; that makes an unconfigured SDK a
@@ -3151,9 +2812,26 @@ declare const DEFAULT_TRANSACTION_CIRCUITS_URL: string | null;
3151
2812
  * Set circuits base path: local directory containing `transaction_js/` and `transaction_final.zkey`,
3152
2813
  * or an `http(s)` base URL to those artifacts (loaded into memory once per process).
3153
2814
  *
2815
+ * ── Kept, not removed, and now checked ────────────────────────────────────────────────────────
2816
+ * It stays public API for one reason the pin cannot serve: an offline, air-gapped or React-Native
2817
+ * caller must be able to name a LOCAL directory holding the ceremony artifacts, and every example
2818
+ * in this repo calls it. What changed is that the string is no longer honoured verbatim. It goes
2819
+ * through {@link assertAllowedCircuitsBase}, the same shape of check `relayUrl` gets: a local
2820
+ * directory is accepted (it puts nothing on the wire, and the unconditional digest check governs
2821
+ * its bytes), an `http(s)` base must be at or under this build's pinned bundle base, and a loopback
2822
+ * base is accepted only in a build whose relay allowlist is already local.
2823
+ *
2824
+ * Asserted HERE for a good early error, and again at the read itself in `proving/artifacts.ts` —
2825
+ * because this setter is not the only door: `loadVerifiedCircuitArtifacts`, `verifyCircuitIntegrity`,
2826
+ * `assertTransactionCircuitIntegrity` and `verifyAllCircuits` are all exported and all take a base
2827
+ * directly. A setter-only check would have been theatre, exactly as it would have been for the
2828
+ * relay URL.
2829
+ *
3154
2830
  * 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
2831
+ * per base inside `proving/artifacts.ts`, so a new base loads and re-verifies its
3156
2832
  * own bytes.
2833
+ *
2834
+ * @throws when `next` names a location this build may not read circuit artifacts from.
3157
2835
  */
3158
2836
  declare function setCircuitsPath(next: string): void;
3159
2837
  /**
@@ -3162,13 +2840,22 @@ declare function setCircuitsPath(next: string): void;
3162
2840
  */
3163
2841
  declare function getCircuitsPath(): string | null;
3164
2842
  /**
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.
2843
+ * Resolve a base to prove from: an explicit argument, else this build's pinned default.
2844
+ *
2845
+ * ── The environment reads are gone ────────────────────────────────────────────────────────────
2846
+ * This used to fall back to `CLOAK_CIRCUITS_PATH` / `CLOAK_CIRCUITS`. Both resolve in the
2847
+ * CONSUMER's process, which is the same reason `NODE_ENV` is not used anywhere in this package:
2848
+ * `CLOAK_CIRCUITS_PATH=http://evil.example/circuits node app.js`, or one
2849
+ * `--define:process.env.CLOAK_CIRCUITS_PATH='"http://evil.example/circuits"'` in a consumer's
2850
+ * bundler, silently moved where the witness generator came from — and the witness generator is
2851
+ * handed the spend key. An integrator who genuinely needs a different location passes it
2852
+ * explicitly and it is checked against the pin like any other.
3167
2853
  *
3168
2854
  * Use this instead of writing an artifact URL out by hand — a hand-written URL
3169
2855
  * is exactly how the base's version segment came to disagree with the digests
3170
2856
  * this SDK checks against. Throws an explanatory error (naming the expected
3171
- * bundle version and both expected digests) when nothing resolves.
2857
+ * bundle version and both expected digests) when nothing resolves, and refuses a
2858
+ * base this build may not read from.
3172
2859
  */
3173
2860
  declare function resolveCircuitsBase(explicit?: string): string;
3174
2861
  /**
@@ -3182,6 +2869,68 @@ declare function resolveCircuitsBase(explicit?: string): string;
3182
2869
  */
3183
2870
  declare function computeChainNoteHash(publicAmount: bigint, extDataHash: bigint, noteTimestamp: bigint, noteCommitment: bigint, noteSalt: bigint, outAmount0: bigint, outPubkey0: bigint, noteIsSendToSelfKey0: bigint): Promise<bigint>;
3184
2871
  declare function computeExtDataHash(recipient: PublicKey | null, relayerFee: bigint, relayer: PublicKey | null, maxFee?: bigint): Promise<bigint>;
2872
+ /**
2873
+ * Adapter for a third-party fee-payer relayer (e.g. Kora, https://github.com/solana-foundation/kora)
2874
+ * that lets the depositor pay network fees — and, via `buildTopUpInstructions`, the on-chain rent
2875
+ * this deposit itself needs — in an SPL token instead of native SOL. Only consulted on the deposit
2876
+ * path (externalAmount > 0); ignored for transfers/withdrawals/swaps, which already go through the
2877
+ * relay's own SOL-funded fee payer. Optional — omitting this leaves every existing caller (including
2878
+ * the web app) byte-for-byte unchanged: the depositor remains the fee payer and must hold SOL.
2879
+ */
2880
+ interface ExternalFeePayerAdapter {
2881
+ /** Returns the external fee payer's pubkey (Kora's `getPayerSigner`). */
2882
+ getPayerPubkey: () => Promise<PublicKey>;
2883
+ /**
2884
+ * Instructions to insert right after the risk-quote instruction (if any) and before every
2885
+ * instruction that spends the depositor's own SOL balance — typically a single
2886
+ * `SystemProgram.transfer(payerPubkey → depositor, neededLamports)`, so the depositor never
2887
+ * needs to hold SOL up front. Return an empty array when no top-up is needed (the depositor
2888
+ * already has enough SOL for on-chain rent).
2889
+ *
2890
+ * A plain transfer works because the external payer's own fee-pricing engine (e.g. Kora's
2891
+ * `calculate_fee_payer_outflow`) already accounts for ANY lamport outflow from its own account
2892
+ * across the whole transaction — not just the base network fee — when it prices
2893
+ * `getPaymentInstruction`. No swap instruction or DEX integration is needed on the client: the
2894
+ * payer's server-side pricing (margin/fixed, its own config) charges for this transfer
2895
+ * automatically. The payer is expected to replenish its own SOL out-of-band (e.g. periodically
2896
+ * converting collected SPL fees back to SOL) — that is entirely the payer's operational concern,
2897
+ * invisible to this adapter and to the depositor.
2898
+ *
2899
+ * Must NOT be inserted before the risk-quote instruction: the on-chain program verifies the risk
2900
+ * quote's Ed25519 signature via instruction introspection at a fixed transaction-level index (0),
2901
+ * so the risk-quote instruction must remain first no matter what else this adapter adds.
2902
+ *
2903
+ * Must NOT include ComputeBudgetProgram instructions of its own — the transaction already
2904
+ * carries one of each, sized for the whole thing including this top-up; a second pair is
2905
+ * rejected by the runtime as duplicate instructions.
2906
+ */
2907
+ buildTopUpInstructions: (payerPubkey: PublicKey) => Promise<TransactionInstruction[]>;
2908
+ /**
2909
+ * Given a base64-encoded, unsigned V0 transaction (feePayer = the external payer's pubkey, every
2910
+ * instruction the deposit needs including any top-up, fresh blockhash), returns the fee-payment
2911
+ * instruction to append before final signing (Kora's `getPaymentInstruction`).
2912
+ */
2913
+ getPaymentInstruction: (unsignedTxBase64: string) => Promise<TransactionInstruction>;
2914
+ /**
2915
+ * Given a base64-encoded transaction the depositor has already partially signed (their own signer
2916
+ * slot filled; the external payer's slot still empty — same instructions plus the payment
2917
+ * instruction, fresh blockhash), returns the fully co-signed transaction, base64-encoded (Kora's
2918
+ * `signTransaction`).
2919
+ */
2920
+ cosign: (partiallySignedTxBase64: string) => Promise<string>;
2921
+ /**
2922
+ * Optional: accounts the payment instruction will reference, resolvable BEFORE that instruction
2923
+ * exists (for Kora: the fee payer's and depositor's fee-token ATAs).
2924
+ *
2925
+ * Purely a size optimization, never correctness — the real accounts always come from
2926
+ * `getPaymentInstruction`. Without it, the supplemental ALT built for the quote transaction can't
2927
+ * know about the payment instruction's accounts, so a deposit that lands just over the packet
2928
+ * limit once that instruction is appended needs a SECOND supplemental ALT (an extra on-chain
2929
+ * transaction and extra rent, paid by the external payer). Measured on the first real deposit:
2930
+ * 1236 bytes vs the 1232 limit — over by 4. With the hint, one ALT covers the final transaction.
2931
+ */
2932
+ getPaymentAccountHints?: () => Promise<PublicKey[]>;
2933
+ }
3185
2934
  /**
3186
2935
  * Options for transact operation
3187
2936
  */
@@ -3199,8 +2948,19 @@ interface TransactOptions {
3199
2948
  /** Relayer address for fee payment */
3200
2949
  relayer?: PublicKey;
3201
2950
  /**
3202
- * Relay URL. Never defaulted: resolved from this option, then from `CLOAK_RELAY_URL`.
3203
- * Name production explicitly (`https://api.cloak.ag`).
2951
+ * Cloak endpoint to submit through.
2952
+ *
2953
+ * This option no longer SELECTS the endpoint — the endpoint is pinned when the SDK is built
2954
+ * (`RELAY_ORIGIN_ALLOWLIST` in `src/config/relay.ts`). Only two values are accepted:
2955
+ *
2956
+ * - an origin on this build's allowlist — say `CLOAK_PRODUCTION_RELAY_URL` rather than typing a
2957
+ * host; anything else throws, naming the value and the allowlist;
2958
+ * - `""`, which means NO endpoint: the caller signs and submits the deposit itself. Unchanged,
2959
+ * still supported, and checked before any validation.
2960
+ *
2961
+ * Never defaulted. Omitting it still yields `undefined` (SQ-F1), not production. To point at a
2962
+ * local stack, edit `src/config/relay.ts` and rebuild — not an option, not an environment
2963
+ * variable, because a published build must not be repointable at either.
3204
2964
  */
3205
2965
  relayUrl?: string;
3206
2966
  /** Keypair of the depositor (signs the deposit transaction) - for programmatic use */
@@ -3219,6 +2979,16 @@ interface TransactOptions {
3219
2979
  walletPublicKey?: PublicKey;
3220
2980
  /** Maximum retries on RootNotFound error (default: 5) */
3221
2981
  maxRootRetries?: number;
2982
+ /**
2983
+ * How many times a wallet adapter may be asked to approve ONE swap (default: 5).
2984
+ *
2985
+ * Only applies to the wallet-adapter path, and only to `swapUtxo` / `swapWithChange`: a swap
2986
+ * re-proves on every retry, so every retry needs a fresh approval, and `maxRootRetries` alone
2987
+ * would allow 41 dialogs for a single swap. A `depositorKeypair` signs without prompting and is
2988
+ * bounded by `maxRootRetries` as before. A private send or withdrawal signs exactly once,
2989
+ * whatever happens on the network.
2990
+ */
2991
+ maxWalletApprovals?: number;
3222
2992
  /** Delay between retries in ms (default: 3000) */
3223
2993
  retryDelayMs?: number;
3224
2994
  /**
@@ -3231,6 +3001,12 @@ interface TransactOptions {
3231
3001
  * Used for deposits when riskOracleQueue is set. The backend must return a signed
3232
3002
  * quote instruction for the depositor wallet so the program can verify at index 0.
3233
3003
  * Defaults to `${relayUrl}/range-quote` when relayUrl is set.
3004
+ *
3005
+ * Held to the SAME build-pinned allowlist as `relayUrl`: it is fetched directly and it is
3006
+ * converted back into a base URL by `deriveRelayUrlFromRiskQuoteUrl`, so leaving it unchecked
3007
+ * would leave a second door onto the first. It must therefore be an ABSOLUTE URL on this build's
3008
+ * allowlist; a relative path (e.g. `/api/risk-quote`) is no longer resolved against the page
3009
+ * origin. `""` disables the SDK-side prefetch, unchanged.
3234
3010
  */
3235
3011
  riskQuoteUrl?: string;
3236
3012
  /**
@@ -3251,6 +3027,15 @@ interface TransactOptions {
3251
3027
  * to compress account addresses from 32 bytes to 1-byte indices.
3252
3028
  */
3253
3029
  addressLookupTableAccounts?: AddressLookupTableAccount[];
3030
+ /**
3031
+ * Opt-in: when true and `relayUrl` is set, an SPL deposit that needs a supplemental lookup
3032
+ * table asks the relay to extend its shared table (`${relayUrl}/supplemental-alt`) instead of
3033
+ * creating a depositor-signed throwaway table, so the user signs exactly once. Any relay
3034
+ * failure — a bad response, a missing address, or the client-side slot gate never clearing —
3035
+ * falls back to the old depositor-signed ephemeral-ALT path automatically. Off (the default)
3036
+ * preserves the old behaviour exactly.
3037
+ */
3038
+ relaySupplementalAlt?: boolean;
3254
3039
  /**
3255
3040
  * Optional Range.org API key for direct SDK-side quote fetching.
3256
3041
  *
@@ -3352,7 +3137,7 @@ interface TransactOptions {
3352
3137
  useUniqueNullifiers?: boolean;
3353
3138
  /**
3354
3139
  * Optional DEX allow-list for swaps (Jupiter `dexes`).
3355
- * Example: ["Orca V2", "Raydium CLMM"]
3140
+ * Example: ["Meteora DLMM", "Raydium CLMM"]
3356
3141
  */
3357
3142
  swapDexes?: string[];
3358
3143
  /**
@@ -3392,8 +3177,24 @@ interface TransactOptions {
3392
3177
  * Use when relay may be behind the chain (e.g. commitment_sync lag) to avoid ProofInvalid.
3393
3178
  */
3394
3179
  useChainRootForProof?: boolean;
3180
+ /**
3181
+ * Optional external fee-payer adapter (e.g. Kora) for deposits only. When set, the depositor no
3182
+ * longer needs to hold SOL: the adapter's payer covers the network fee (and, via
3183
+ * `buildTopUpInstructions`, any on-chain rent) and is reimbursed in an SPL token within the same
3184
+ * transaction. Omit for unchanged default behavior.
3185
+ */
3186
+ externalFeePayer?: ExternalFeePayerAdapter;
3395
3187
  }
3396
- /** Switchboard-style response: pre-built instruction. */
3188
+ /**
3189
+ * Switchboard-style response: a pre-built instruction.
3190
+ *
3191
+ * @deprecated No longer accepted. `fetchRiskQuote` rejects this shape outright:
3192
+ * a relay that can name the program id, the account metas and the data of an
3193
+ * instruction the user's wallet signs is a signing oracle. The relay only ever
3194
+ * returns `{ signature, message, signer_pubkey }`, which the SDK verifies and
3195
+ * rebuilds locally as an account-free Ed25519 instruction. The type is kept as
3196
+ * an export purely so consumers importing it still compile.
3197
+ */
3397
3198
  interface RiskQuoteInstructionResponse {
3398
3199
  instruction: {
3399
3200
  programId: string;
@@ -3470,12 +3271,23 @@ type RelaySubmissionResult = {
3470
3271
  kind: "failed";
3471
3272
  error: Error;
3472
3273
  };
3274
+
3473
3275
  interface SubmitTransactToRelayArgs {
3474
3276
  relayUrl: string;
3475
3277
  /** The exact body to POST. Auth fields are added ONCE, in place, and then never changed. */
3476
3278
  requestBody: Record<string, unknown>;
3477
3279
  programId: PublicKey;
3478
3280
  depositorKeypair?: Keypair;
3281
+ /**
3282
+ * Wallet-adapter alternative to `depositorKeypair` for request authentication, used only when
3283
+ * no keypair is supplied. Lets a browser or mobile caller — which holds no secret key — submit
3284
+ * through this same function instead of hand-rolling the scheme.
3285
+ *
3286
+ * COMPLIANCE: `walletPublicKey` becomes the authenticated `sender`, which is the key screened
3287
+ * for sanctions on shield-to-shield sends. It must be the end user's real wallet; an ephemeral
3288
+ * or server-side key here is a compliance regression, not a shortcut.
3289
+ */
3290
+ relayAuthSigner?: RelayAuthSigner;
3479
3291
  settlement: SettlementContext;
3480
3292
  /** True while the caller still has a re-prove budget for a stale root. */
3481
3293
  canRetryStaleRoot: boolean;
@@ -3606,9 +3418,9 @@ interface UtxoSwapResult extends TransactResult {
3606
3418
  * Execute a UTXO swap withdrawal
3607
3419
  *
3608
3420
  * This spends input UTXOs and creates a SwapState PDA for swapping SOL to SPL tokens.
3609
- * After this transaction, the relay can execute:
3610
- * 1. PrepareSwapSol - Wrap SOL to wSOL
3611
- * 2. ExecuteSwapViaOrca - Execute the swap on Orca
3421
+ * The swap is then completed on-chain in two follow-up instructions:
3422
+ * 1. PrepareSwapSol - Wrap the SOL held by the SwapState PDA into wSOL
3423
+ * 2. ExecuteSwap - Route the wSOL through Jupiter and deliver the output token to `recipientAta`
3612
3424
  *
3613
3425
  * @param params Swap parameters
3614
3426
  * @param options Transaction options
@@ -3630,129 +3442,59 @@ declare function swapUtxo(params: UtxoSwapParams, options: TransactOptions): Pro
3630
3442
  declare function swapWithChange(inputUtxos: Utxo[], swapAmount: bigint, outputMint: PublicKey, recipientAta: PublicKey, minOutputAmount: bigint, options: TransactOptions, recipientWallet?: PublicKey): Promise<UtxoSwapResult>;
3631
3443
 
3632
3444
  /**
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.
3445
+ * Circuit artifact loading and integrity verification.
3644
3446
  *
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
3447
+ * Artifacts come from the pinned bundle in `config/circuits`, verified
3448
+ * by digest; no backend prover service is required. Proof generation itself
3449
+ * lives with the flows that own it (`flows/transact.ts`), which passes the
3450
+ * verified buffers this module returns straight to `snarkjs.groth16.fullProve`.
3708
3451
  *
3709
- * This matches the approach in services-new/tests/src/proof.ts
3452
+ * The bytes are canonical HERE and copied out: every hand-off mints a fresh buffer from an
3453
+ * intrinsic captured at module load, and hashes a twin of that buffer, so the digest covers what is
3454
+ * used and not merely what was once loaded, and the buffer that is used was allocated by something
3455
+ * a consumer cannot redefine. See the note on `_verifiedArtifacts` for the break that made the copy
3456
+ * necessary (F7), and THE PRIMORDIALS for the one that made the copy's PROVENANCE necessary (F9).
3710
3457
  *
3711
- * @param inputs - Circuit inputs
3712
- * @param circuitsPath - Ignored. Proof generation always uses pinned S3 circuits.
3458
+ * Only the ceremony-frozen `transaction` circuit exists here. The deployed
3459
+ * program verifies proofs against that circuit's verifying key and nothing
3460
+ * else, so the pre-ceremony `withdraw_regular` / `withdraw_swap` circuits —
3461
+ * and every code path that loaded them — were removed rather than kept as
3462
+ * compatibility the program would reject anyway.
3713
3463
  */
3714
- declare function generateWithdrawRegularProof(inputs: WithdrawRegularInputs, circuitsPath: string): Promise<ProofResult>;
3464
+
3715
3465
  /**
3716
- * Generate Groth16 proof for swap withdrawal using Circom WASM
3466
+ * Base URL of the only bundle this SDK pins: the ceremony `transaction` bundle.
3717
3467
  *
3718
- * This matches the approach in services-new/tests/src/proof.ts
3468
+ * Identical to `DEFAULT_TRANSACTION_CIRCUITS_URL` in `flows/transact`; kept
3469
+ * under this name so existing imports keep resolving. Derived from
3470
+ * {@link TRANSACTION_CIRCUITS_BASE_URL}, so the version segment is the same one
3471
+ * the pinned digests were declared under — do not write this URL out by hand
3472
+ * anywhere; change the bundle instead.
3719
3473
  *
3720
- * @param inputs - Circuit inputs
3721
- * @param circuitsPath - Ignored. Proof generation always uses pinned S3 circuits.
3474
+ * `string | null` `null` when this build pins no location whose bytes were
3475
+ * verified against the bundle's digests.
3722
3476
  */
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>;
3477
+ declare const DEFAULT_CIRCUITS_URL: string;
3732
3478
  /**
3733
3479
  * Pinned circuit artifact hashes (SHA-256), flattened from the release table in
3734
- * `config/circuit-release.ts`.
3480
+ * `proving/circuits.ts`.
3735
3481
  *
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.
3482
+ * A read-only VIEW of {@link PINNED_TRANSACTION_WASM_DIGEST} / {@link PINNED_TRANSACTION_ZKEY_DIGEST},
3483
+ * kept because it is part of the published surface and integrators print it in start-up
3484
+ * diagnostics. FROZEN: it is not the source of truth and must not be able to pretend it is. Writing
3485
+ * to it throws in strict mode (every ES module is strict) and is a silent no-op in sloppy CJS
3486
+ * scope — either way the digests the check uses are unchanged.
3739
3487
  */
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;
3488
+ declare const EXPECTED_CIRCUIT_HASHES: Readonly<{
3745
3489
  transaction_wasm: string;
3746
3490
  transaction_zkey: string;
3747
- };
3491
+ }>;
3748
3492
  /**
3749
3493
  * Circuit verification result
3750
3494
  */
3751
3495
  interface CircuitVerificationResult {
3752
3496
  /** Whether verification passed */
3753
3497
  valid: boolean;
3754
- /** Which circuit was checked */
3755
- circuit: CircuitName;
3756
3498
  /** Error message if verification failed */
3757
3499
  error?: string;
3758
3500
  computed?: {
@@ -3764,13 +3506,25 @@ interface CircuitVerificationResult {
3764
3506
  zkey: string;
3765
3507
  };
3766
3508
  }
3767
- /** Circuit artifact bytes together with the digests computed over those bytes. */
3509
+ /**
3510
+ * Circuit artifact bytes together with the digests computed over those bytes.
3511
+ *
3512
+ * The buffers are a PRIVATE COPY, minted for this call from an intrinsic captured at module load
3513
+ * and hashed via a throwaway twin after they were copied. Nothing outside this module was handed
3514
+ * the object or a view onto it, so `digests` describes `wasm`/`zkey` as of the moment they were
3515
+ * handed over rather than as of some earlier load. Mutate them if you like; the SDK's canonical
3516
+ * bytes are elsewhere and the next call re-copies and re-hashes.
3517
+ *
3518
+ * The guarantee stops at the hand-off, and stops honestly. Once these buffers are inside
3519
+ * `snarkjs.groth16.fullProve` what snarkjs does with them is snarkjs's business; what this module
3520
+ * promises is that the bytes it handed over are the bytes whose SHA-256 it reported.
3521
+ */
3768
3522
  interface VerifiedCircuitArtifacts {
3769
- /** `<circuit>_js/<circuit>.wasm` bytes. */
3523
+ /** `<circuit>_js/<circuit>.wasm` bytes. A fresh copy, not shared with any other caller. */
3770
3524
  wasm: Uint8Array;
3771
- /** `<circuit>_final.zkey` bytes. */
3525
+ /** `<circuit>_final.zkey` bytes. A fresh copy, not shared with any other caller. */
3772
3526
  zkey: Uint8Array;
3773
- /** SHA-256 (lowercase hex) of the buffers in this object. */
3527
+ /** SHA-256 (lowercase hex) of the buffers in this object, computed over these very buffers. */
3774
3528
  digests: {
3775
3529
  wasm: string;
3776
3530
  zkey: string;
@@ -3789,8 +3543,23 @@ interface VerifiedCircuitArtifacts {
3789
3543
  * Fails closed: any digest mismatch, unreachable artifact, or environment that
3790
3544
  * cannot produce bytes (a browser pointed at a local directory) throws rather
3791
3545
  * than falling back to an unverified source.
3546
+ *
3547
+ * Returns a FRESH COPY on every call, hashed after it was copied — see
3548
+ * {@link copyAndVerify} and the note on {@link _verifiedArtifacts}. Two calls never share a buffer,
3549
+ * and the digests in the returned object are the digests of the buffers in that same object, taken
3550
+ * at that call. Mutating what you are given affects nothing but your own copy.
3551
+ *
3552
+ * The environment cannot switch any of this off. It used to be skippable with
3553
+ * `CLOAK_SKIP_CIRCUIT_INTEGRITY_CHECK=1`, and that escape hatch was the worst hole in the package:
3554
+ * the variable resolves in the CONSUMER's process, so one deploy variable — or one
3555
+ * `--define:process.env.CLOAK_SKIP_CIRCUIT_INTEGRITY_CHECK='"1"'` in a consumer's bundler, which
3556
+ * compiles the guard down to a constant `true` — made this function accept whatever bytes the base
3557
+ * served. Paired with a repointable base that meant attacker wasm, and attacker wasm sees the spend
3558
+ * key. A check an environment variable disables is not a check. If a local build genuinely needs
3559
+ * different artifacts it needs different DIGESTS, which is a source edit and a rebuild, exactly
3560
+ * like the endpoint pin.
3792
3561
  */
3793
- declare function loadVerifiedCircuitArtifacts(circuitsPath: string | null, circuit: CircuitName): Promise<VerifiedCircuitArtifacts>;
3562
+ declare function loadVerifiedCircuitArtifacts(circuitsPath: string | null): Promise<VerifiedCircuitArtifacts>;
3794
3563
  /**
3795
3564
  * Report whether a circuit's artifacts match the digests pinned in this SDK.
3796
3565
  *
@@ -3800,35 +3569,38 @@ declare function loadVerifiedCircuitArtifacts(circuitsPath: string | null, circu
3800
3569
  * Proof paths must call {@link loadVerifiedCircuitArtifacts} and hand the bytes
3801
3570
  * it returns to snarkjs.
3802
3571
  *
3572
+ * Reports digests, never bytes. It can read the canonical memo to avoid a second download, but the
3573
+ * result object carries only hex strings, so calling this cannot obtain a handle on the bytes a
3574
+ * later proof will run over — which is the other half of the F7 fix, and the reason this function
3575
+ * and `loadVerifiedCircuitArtifacts` can no longer disagree about the same cache entry.
3576
+ *
3803
3577
  * IMPORTANT: If the hashes don't match, the circuit may produce proofs
3804
3578
  * that will be rejected by the on-chain verifier!
3805
3579
  *
3806
- * @param circuitsPath - Ignored for the legacy withdraw circuits (they always use
3807
- * their own pinned bundle); honoured for `transaction`.
3580
+ * @param circuitsPath - Base directory or URL holding the circuit's artifacts;
3581
+ * honoured verbatim.
3808
3582
  * @param circuit - Which circuit to verify
3809
3583
  * @returns Verification result
3810
3584
  *
3811
3585
  * @example
3812
3586
  * ```typescript
3813
- * const result = await verifyCircuitIntegrity(DEFAULT_CIRCUITS_URL, 'withdraw_regular');
3587
+ * const result = await verifyCircuitIntegrity(getCircuitsPath(), 'transaction');
3814
3588
  * if (!result.valid) {
3815
3589
  * console.error('Circuit verification failed:', result.error);
3816
3590
  * // Don't proceed with proof generation!
3817
3591
  * }
3818
3592
  * ```
3819
3593
  */
3820
- declare function verifyCircuitIntegrity(circuitsPath: string | null, circuit: CircuitName, prefetched?: {
3594
+ declare function verifyCircuitIntegrity(circuitsPath: string | null, prefetched?: {
3821
3595
  wasm: Uint8Array;
3822
3596
  zkey: Uint8Array;
3823
3597
  }): Promise<CircuitVerificationResult>;
3824
3598
  /**
3825
3599
  * Assert the ceremony-frozen `transaction` circuit artifacts are the pinned ones.
3826
3600
  *
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.
3601
+ * Throws (fail-closed) when the digests do not match. Proving against an
3602
+ * unpinned zkey silently produces proofs the on-chain verifying key rejects,
3603
+ * so failing here is strictly better than failing on-chain.
3832
3604
  *
3833
3605
  * @param circuitsPath - Base directory or URL holding `transaction_js/transaction.wasm`
3834
3606
  * and `transaction_final.zkey`.
@@ -3839,18 +3611,21 @@ declare function assertTransactionCircuitIntegrity(circuitsPath: string | null,
3839
3611
  zkey: Uint8Array;
3840
3612
  }): Promise<void>;
3841
3613
  /**
3842
- * Verify all circuits before use
3614
+ * Verify every circuit this SDK pins — which is exactly one: the ceremony
3615
+ * `transaction` circuit.
3843
3616
  *
3844
3617
  * Call this at SDK initialization to ensure circuits are valid.
3845
3618
  *
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.
3619
+ * @param circuitsPath - Base for the ceremony-frozen `transaction` circuit.
3849
3620
  * Pass `getCircuitsPath()` when the caller has reconfigured it;
3850
3621
  * `null` reports the "no base configured" state rather than throwing.
3622
+ * @param transactionCircuitsPath - Same base; takes precedence when given. Kept so
3623
+ * two-argument callers from before the legacy withdraw
3624
+ * circuits were removed keep compiling, with unchanged
3625
+ * behaviour for the `transaction` entry.
3851
3626
  * @returns Array of verification results (one per circuit)
3852
3627
  */
3853
- declare function verifyAllCircuits(circuitsPath: string, transactionCircuitsPath?: string | null): Promise<CircuitVerificationResult[]>;
3628
+ declare function verifyAllCircuits(circuitsPath: string | null, transactionCircuitsPath?: string | null): Promise<CircuitVerificationResult[]>;
3854
3629
 
3855
3630
  /**
3856
3631
  * Pending Operations Manager
@@ -4000,7 +3775,7 @@ declare function cleanupStalePendingOperations(maxAgeMs?: number): {
4000
3775
  *
4001
3776
  * ── Crypto ────────────────────────────────────────────────────────────────────────────────────
4002
3777
  * 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.
3778
+ * `notes/keypair.ts` (`nacl.box.before` + `nacl.secretbox`). No new primitive is introduced here.
4004
3779
  */
4005
3780
 
4006
3781
  /** Ephemeral X25519 public key, at offset 0. */
@@ -4126,7 +3901,7 @@ declare function parseDeliveryCarrierMemo(data: Uint8Array): ParsedDeliveryCarri
4126
3901
  * transaction actually published. That equality is the authentication: it is not a heuristic, and a
4127
3902
  * wrong `nk` cannot produce it. Zero extra bytes on chain, no new envelope, no relay change.
4128
3903
  *
4129
- * This is the same shape already blessed for swap timeout refunds in `core/swap-refund.ts`
3904
+ * This is the same shape already blessed for swap timeout refunds in `notes/swap-refund.ts`
4130
3905
  * (PRF(nk, nullifier0)), for the same reason: unrecoverable randomness becomes recoverable
4131
3906
  * randomness without changing anything an observer can see.
4132
3907
  *
@@ -4213,6 +3988,137 @@ interface MatchDepositNoteParams {
4213
3988
  */
4214
3989
  declare function matchDepositNote(params: MatchDepositNoteParams): Promise<RecoveredDepositNote | null>;
4215
3990
 
3991
+ /**
3992
+ * Recoverable change notes (VK-01, change shape).
3993
+ *
3994
+ * ── The defect this closes ────────────────────────────────────────────────────────────────────
3995
+ * A partial withdrawal spends a note worth more than the withdrawal and puts the remainder back in
3996
+ * the pool as a change note. `partialWithdraw` built that note with plain `createUtxo`, whose
3997
+ * blinding comes from `randomFieldElement()` — a 252-bit CSPRNG draw written to exactly one place:
3998
+ * whatever the caller does with `TransactResult.outputUtxos`. A caller that drops it, or a throw
3999
+ * between the relay accepting the transaction and the caller persisting the result, destroys the
4000
+ * only copy. The inputs are spent, the change is on chain, and it can never be spent by anyone.
4001
+ *
4002
+ * This is not hypothetical. It stranded 19.939819 USDC on 2026-09-02 (commitment
4003
+ * `138d126b58521b5d14e5bc85ed4e38db3f1218ef4061ad86e07ad4784ee40b6c`, leaf 2290 of the USDC tree):
4004
+ * a payment-link claim ran `partialWithdraw`, the claim page never read `outputUtxos`, and the
4005
+ * blinding died with the page. Every other value needed to spend that note is still recoverable —
4006
+ * amount, owner key, mint, commitment, leaf index. Only the randomness is gone.
4007
+ *
4008
+ * The SDK already ships key-only recovery for DEPOSITS (`notes/deposit-note.ts`, PRF(nk, noteSalt)),
4009
+ * for RECEIVED transfers (`notes/delivery-note.ts`, CLKD1 envelope) and for SWAP REFUNDS
4010
+ * (`notes/swap-refund.ts`, PRF(nk, nullifier0)). Change was the one shape with no recovery path at
4011
+ * all, and it is the shape every withdrawal produces.
4012
+ *
4013
+ * ── Why derivation, and why nothing else was available ────────────────────────────────────────
4014
+ * The CLKD1 delivery envelope is the only rail in the protocol that publishes a blinding, and it
4015
+ * rejects this shape twice over: `buildRecipientDeliveryNotes` returns `undefined` when
4016
+ * `externalAmount !== 0` (a withdrawal) and again when the note's owner is the spender (change is
4017
+ * self-owned). Both gates are correct — a delivery envelope exists to reach someone else, and
4018
+ * change has no one to reach. So make the chain note the protocol already emits carry the recovery
4019
+ * instead, at zero additional bytes:
4020
+ *
4021
+ * seed = BLAKE3("cloak_change_note_v1" || nk || noteSalt(32B BE) || outputIndex(1B) || "blinding")
4022
+ * blinding = seed reduced into the field
4023
+ *
4024
+ * The output index is in the preimage because one transaction carries one salt but two output
4025
+ * slots, and a send-to-self puts a self-owned note in BOTH. Without the index those two notes would
4026
+ * derive one blinding, and `transact`'s fail-closed check could not tell which slot it was looking
4027
+ * at. With it, every slot has its own answer.
4028
+ *
4029
+ * ── Why only the blinding, when deposits derive the keypair too ───────────────────────────────
4030
+ * A deposit's output note has no prior owner, so `deposit-note.ts` is free to give it a per-deposit
4031
+ * key and gains unlinkability by doing so. A change note is different: its owner is already fixed —
4032
+ * it is the keypair of the note being spent (`inputUtxos[0].keypair`), which the spender by
4033
+ * definition holds, and which callers rely on to keep spending their own change. Re-owning it under
4034
+ * a derived key would change `outPubkey0`, break that expectation, and buy nothing, because the
4035
+ * spender's key is the one value in a change note that was never at risk. Only the randomness was.
4036
+ *
4037
+ * ── What a cold scan can rebuild, and what it cannot ──────────────────────────────────────────
4038
+ * For the shape that lost the money — a partial withdrawal — recovery is COMPLETE from `nk` alone.
4039
+ * `partialWithdraw` emits the change as `outputUtxos[0]`, and a v4 chain note binds
4040
+ * `noteSemantics = Poseidon(outAmount0, outPubkey0, isSendToSelfKey0)` and carries all three in its
4041
+ * (encrypted, authenticated) plaintext. So a scanner holding `nk` decrypts the note, reads
4042
+ * `noteSalt`, `outAmount0` and `outPubkey0`, replays the line above, recomputes
4043
+ * `Poseidon(amount, pubkey, blinding, mint)` and requires it to equal a commitment the transaction
4044
+ * actually published. That equality is the authentication, exactly as in `matchDepositNote`.
4045
+ *
4046
+ * For `transfer`, change is `outputUtxos[1]` — the recipient note has to be output 0 because the
4047
+ * delivery carrier is bound to `output_commitments[0]`. A v4 chain note describes output 0 only, so
4048
+ * a cold scan does not learn the change AMOUNT and cannot finish the match on its own. The blinding
4049
+ * is still derived and still recoverable, which turns "permanently unspendable" into "spendable as
4050
+ * soon as the amount is known" — and a sender who knows what they sent knows the amount. Closing
4051
+ * that last gap needs the chain note to describe output 1 as well, which is a format change and is
4052
+ * deliberately not attempted here.
4053
+ *
4054
+ * ── Guardrails preserved ──────────────────────────────────────────────────────────────────────
4055
+ * [M-04] `noteSalt` stays a private input to `chainNoteHash`; it is only ever published inside the
4056
+ * note's own authenticated ciphertext, so nothing an observer can see changes. [H-04-shaped] the
4057
+ * reduction forces non-zero, because a zero blinding is an unspendable note. Callers that pass no
4058
+ * `nk` keep the previous random-blinding behaviour unchanged, so this is additive.
4059
+ */
4060
+
4061
+ /** A change note recovered from `nk` (plus the owner key the spender already holds). */
4062
+ interface RecoveredChangeNote {
4063
+ keypair: UtxoKeypair;
4064
+ blinding: bigint;
4065
+ amount: bigint;
4066
+ mintAddress: PublicKey;
4067
+ /** The commitment the transaction published, reproduced from the derived blinding. */
4068
+ commitment: bigint;
4069
+ /** The salt the chain note carried, which anchored the derivation. */
4070
+ noteSalt: bigint;
4071
+ }
4072
+ /** A fresh 96-bit chain-note salt, from the same fail-closed source `transact` uses. */
4073
+ declare function randomChangeNoteSalt(): bigint;
4074
+ /**
4075
+ * Derive a change note's blinding from `(nk, noteSalt)`.
4076
+ *
4077
+ * Deterministic by design: this is the whole reason a cold scan can rebuild the note. Both the
4078
+ * builder and the scanner call it, so there is exactly one definition of what a change note is.
4079
+ */
4080
+ declare function deriveChangeNoteBlinding(viewingKeyNk: Uint8Array, noteSalt: bigint, outputIndex: number): bigint;
4081
+ /**
4082
+ * Build a change output note whose blinding a cold `(rpc, programId, nk)` scan can re-derive.
4083
+ *
4084
+ * Returns the UTXO AND the salt that anchored it. The SAME salt must reach `transact` as
4085
+ * `options.chainNoteSalt`, because the chain note is what publishes it — a salt that does not reach
4086
+ * the note leaves the change exactly as unrecoverable as before. `partialWithdraw` and `transfer`
4087
+ * do this for you; the pairing is only your concern if you call `transact` directly.
4088
+ */
4089
+ declare function createRecoverableChangeUtxo(amount: bigint, keypair: UtxoKeypair, viewingKeyNk: Uint8Array, mintAddress?: PublicKey, noteSalt?: bigint, outputIndex?: number): Promise<{
4090
+ utxo: Utxo;
4091
+ noteSalt: bigint;
4092
+ }>;
4093
+ interface MatchChangeNoteParams {
4094
+ /** The spending wallet's incoming viewing base. */
4095
+ viewingKeyNk: Uint8Array;
4096
+ /** `noteSalt`, read out of the decrypted chain note. */
4097
+ noteSalt: bigint;
4098
+ /** Candidate note amount — `outAmount0` for a partial withdrawal's change. */
4099
+ amount: bigint;
4100
+ /**
4101
+ * The change note's owner keypair. For a match, only `publicKey` is used (`outPubkey0` from the
4102
+ * chain note is enough); supply the private key too if you intend to spend the result.
4103
+ */
4104
+ keypair: UtxoKeypair;
4105
+ /** Pool mint the commitment was computed under. */
4106
+ mintAddress: PublicKey;
4107
+ /** Which output slot the note occupied — part of the derivation, so it must match. */
4108
+ outputIndex: number;
4109
+ /** Output commitments the transaction actually published, hex or field elements. */
4110
+ outputCommitments: Array<string | bigint>;
4111
+ }
4112
+ /**
4113
+ * Decide whether a published commitment is a change note this `nk` can rebuild, and if so return it
4114
+ * in spendable form. Returns `null` for everything that is not ours.
4115
+ *
4116
+ * The commitment equality is the authentication. Nothing here trusts the chain note's own claim
4117
+ * about what it describes; the note supplies `noteSalt`, `amount` and the owner key, and the derived
4118
+ * blinding has to reproduce a value the transaction published or the candidate is discarded.
4119
+ */
4120
+ declare function matchChangeNote(params: MatchChangeNoteParams): Promise<RecoveredChangeNote | null>;
4121
+
4216
4122
  /**
4217
4123
  * Swap timeout-refund discovery (VK-02).
4218
4124
  *
@@ -4464,6 +4370,25 @@ interface ScanResult {
4464
4370
  * Additive, for the same reason as `deliveredNotes`: note secrets are not compliance rows.
4465
4371
  */
4466
4372
  recoveredDepositNotes: RecoveredDepositNoteRecord[];
4373
+ /**
4374
+ * This wallet's OWN withdrawal/swap change, rebuilt from `nk` alone (VK-01, change shape).
4375
+ *
4376
+ * `partialWithdraw` and `swapWithChange` emit change as output 0, and a v4 chain note carries
4377
+ * `outAmount0`/`outPubkey0`, so `nk` plus the derived blinding is everything the note needs.
4378
+ *
4379
+ * One difference from `recoveredDepositNotes`: a change note's owner key is NOT derived — it is
4380
+ * the keypair of the note that was spent, deliberately, so that holding a viewing key never
4381
+ * confers spend authority. So `keypair.privateKey` comes back as `0n` and the caller must supply
4382
+ * their own before spending. Everything else is complete.
4383
+ */
4384
+ recoveredChangeNotes: RecoveredChangeNoteRecord[];
4385
+ }
4386
+ /** A recovered change note plus the chain coordinates it was recovered from. */
4387
+ interface RecoveredChangeNoteRecord extends RecoveredChangeNote {
4388
+ /** Signature of the withdrawal/swap transaction. */
4389
+ signature: string;
4390
+ /** Millisecond timestamp from the chain note. */
4391
+ timestamp: bigint;
4467
4392
  }
4468
4393
  /** A recovered deposit note plus the chain coordinates it was recovered from. */
4469
4394
  interface RecoveredDepositNoteRecord extends RecoveredDepositNote {
@@ -4749,8 +4674,8 @@ declare class SimpleWallet {
4749
4674
  * @packageDocumentation
4750
4675
  */
4751
4676
 
4752
- declare const VERSION = "0.2.0";
4677
+ declare const VERSION = "0.2.1";
4753
4678
  /** True when scanner supports TransactSwap (tag 1). Check this to verify the correct SDK bundle is loaded. */
4754
4679
  declare const SCANNER_SUPPORTS_TRANSACT_SWAP = true;
4755
4680
 
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 };
4681
+ export { BUILD_ALLOWS_LOCAL_ENDPOINTS, type BuildRecipientDeliveryNotesParams, CHAIN_NOTE_SALT_BITS, CLOAK_PRODUCTION_RELAY_URL, CLOAK_PROGRAM_ID, type ChainNoteTxType, type CircuitVerificationResult, type CloakConfig, CloakError, type CloakKeyPair, type CloakNote, 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, type ExternalFeePayerAdapter, FIXED_FEE_LAMPORTS, type Groth16Proof, InsecureRandomnessError, LAMPORTS_PER_SOL, LocalStorageAdapter, type LogLevel, type Logger, MERKLE_TREE_HEIGHT, MIN_DEPOSIT_LAMPORTS, type MasterKey, type MatchChangeNoteParams, 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, 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, RELAY_ORIGIN_ALLOWLIST, REQUEST_AUTH_MAX_AGE_SECONDS, REQUEST_AUTH_MAX_FUTURE_SKEW_SECONDS, type RecipientDeliveryNote, type RecoveredChangeNote, type RecoveredChangeNoteRecord, type RecoveredDepositNote, type RecoveredDepositNoteRecord, type RecoveredSwapRefund, type RelayAuthPreimage, type RelayAuthSigner, 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, TRANSACT_AUTH_FIELDS, TRANSACT_SWAP_AUTH_FIELDS, 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 WithdrawSubmissionResult, assertDirectSubmissionLanded, assertTransactionCircuitIntegrity, bigintToBytes32$1 as bigintToBytes32, bigintToHex, buildMerkleTree, buildMerkleTreeFromChain, buildMerkleTreeFromRelay, buildRecipientDeliveryNotes, buildRelayAuthPreimage, buildTransactRequestBody, bytesToHex, calculateFee, calculateFeeBigint, calculateRelayFee, canRebuildMerkleTreeFromChain, canonicalJson, chainNoteFromBase64, chainNoteToBase64, classifyRelayError, cleanupStalePendingOperations, clearPendingDeposits, clearPendingWithdrawals, computeChainNoteHash, computeExtDataHash, computeMerkleRoot, computeProofForLatestDeposit, computeProofFromChain, computeSignature, computeSwapRefundCommitment, computeCommitment as computeUtxoCommitment, computeNullifier as computeUtxoNullifier, confirmTransactSettlement, copyNoteToClipboard, createCloakError, createDepositInstruction, createLogger, createRecoverableChangeUtxo, createRecoverableDepositUtxo, createUtxo, createZeroUtxo, decryptCompactChainNote, decryptComplianceMetadataWithMasterKey, decryptTransactionMetadata, deriveChangeNoteBlinding, 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, explainRelayAuthRejection, exportKeys, exportNote, exportWalletKeys, fetchCommitments, fetchRiskQuoteInstruction, fetchRiskQuoteIx, filterNotesByNetwork, filterWithdrawableNotes, findNoteByCommitment, formatAmount, formatComplianceCsv, formatErrorForLogging, formatSol, fullWithdraw, generateCloakKeys, generateCommitmentAsync, generateMasterSeed, generateNoteFromWallet, generateUtxoKeypair, generateViewingKeyPair, getAddressExplorerUrl, getChainNoteRegistryPDA, getCircuitsPath, getDeliveryRegistryPDA, getDistributableAmount, getExplorerUrl, getNkFromUtxoPrivateKey, getNullifierPDA, getPendingOperationsSummary, getPoolAuthorityConfigPDA, getPublicKey, getPublicViewKey, getRecipientAmount, 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, matchChangeNote, matchDepositNote, matchSwapRefundLeaf, openRecipientDeliveryNote, parseAmount, parseDeliveryCarrierMemo, parseError, parseNote, parseRelayErrorResponse, parseRelayErrorSignature, parseTransactionError, partialWithdraw, poseidonHash, preflightCheck, preflightNullifiers, prepareEncryptedOutput, prepareEncryptedOutputForRecipient, proofToBytes, pubkeyToFieldElement, pubkeyToLimbs, randomBytes, randomChangeNoteSalt, 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 };