@cloak.dev/sdk 0.1.8 → 0.2.1

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
@@ -1091,45 +696,130 @@ declare function getViewKey(keys: CloakKeyPair): ViewKey;
1091
696
  */
1092
697
  declare function getRecipientAmount(amountLamports: number): number;
1093
698
 
1094
- interface ViewingKeyPair {
1095
- privateKey: Uint8Array;
1096
- publicKey: Uint8Array;
1097
- }
1098
- /** Zcash-style expanded spend key: ask (auth), nsk (nullifier/nk), ovk (outgoing view) */
1099
- interface ExpandedSpendKey {
1100
- ask: Uint8Array;
1101
- nsk: Uint8Array;
1102
- ovk: Uint8Array;
1103
- }
1104
699
  /**
1105
- * Expand spend key into (ask, nsk, ovk) — Zcash-style.
1106
- * - ask: spend authorization (future)
1107
- * - nsk: base for incoming viewing (nk) — chain note decryption
1108
- * - ovk: outgoing viewing (future)
1109
- */
1110
- declare function expandSpendKey(skSpend: Uint8Array): ExpandedSpendKey;
1111
- /**
1112
- * Derive chain note viewing key from nk (Zcash-style IVK component).
1113
- * Used for trial-decrypting incoming chain notes.
700
+ * Storage Interface
1114
701
  *
1115
- * @param nk 32-byte nk (from expandSpendKey(...).nsk the incoming view base)
1116
- */
1117
- declare function deriveViewingKeyFromNk(nk: Uint8Array): ViewingKeyPair;
1118
- /**
1119
- * Derive diversifier for per-output chain note encryption (Phase 3).
1120
- * d = BLAKE3("cloak_div_v1" || nk || commitmentHex || outputIndex)[0:11]
1121
- */
1122
- declare function deriveDiversifier(nk: Uint8Array, commitmentHex: string, outputIndex: number): Uint8Array;
1123
- /**
1124
- * Derive per-output viewing key pair from nk + diversifier (Phase 3).
1125
- * sk_d = BLAKE3("cloak_sk_d_v1" || nk || d) → clamp → X25519 keypair
702
+ * Defines a pluggable storage interface for notes and keys.
703
+ * Applications can implement their own storage (localStorage, IndexedDB, file system, etc.)
1126
704
  */
1127
- declare function deriveDiversifiedViewingKey(nk: Uint8Array, diversifier: Uint8Array): ViewingKeyPair;
705
+
1128
706
  /**
1129
- * Derive chain note viewing key from spend key (Zcash-style).
1130
- * Expands to (ask, nsk, ovk) and uses nsk (nk) for chain note decryption.
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.
1131
711
  */
1132
- declare function deriveViewingKeyFromSpendKey(skSpend: Uint8Array): ViewingKeyPair;
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.
768
+ */
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
+ }
783
+
784
+ interface ViewingKeyPair {
785
+ privateKey: Uint8Array;
786
+ publicKey: Uint8Array;
787
+ }
788
+ /** Zcash-style expanded spend key: ask (auth), nsk (nullifier/nk), ovk (outgoing view) */
789
+ interface ExpandedSpendKey {
790
+ ask: Uint8Array;
791
+ nsk: Uint8Array;
792
+ ovk: Uint8Array;
793
+ }
794
+ /**
795
+ * Expand spend key into (ask, nsk, ovk) — Zcash-style.
796
+ * - ask: spend authorization (future)
797
+ * - nsk: base for incoming viewing (nk) — chain note decryption
798
+ * - ovk: outgoing viewing (future)
799
+ */
800
+ declare function expandSpendKey(skSpend: Uint8Array): ExpandedSpendKey;
801
+ /**
802
+ * Derive chain note viewing key from nk (Zcash-style IVK component).
803
+ * Used for trial-decrypting incoming chain notes.
804
+ *
805
+ * @param nk 32-byte nk (from expandSpendKey(...).nsk — the incoming view base)
806
+ */
807
+ declare function deriveViewingKeyFromNk(nk: Uint8Array): ViewingKeyPair;
808
+ /**
809
+ * Derive diversifier for per-output chain note encryption (Phase 3).
810
+ * d = BLAKE3("cloak_div_v1" || nk || commitmentHex || outputIndex)[0:11]
811
+ */
812
+ declare function deriveDiversifier(nk: Uint8Array, commitmentHex: string, outputIndex: number): Uint8Array;
813
+ /**
814
+ * Derive per-output viewing key pair from nk + diversifier (Phase 3).
815
+ * sk_d = BLAKE3("cloak_sk_d_v1" || nk || d) → clamp → X25519 keypair
816
+ */
817
+ declare function deriveDiversifiedViewingKey(nk: Uint8Array, diversifier: Uint8Array): ViewingKeyPair;
818
+ /**
819
+ * Derive chain note viewing key from spend key (Zcash-style).
820
+ * Expands to (ask, nsk, ovk) and uses nsk (nk) for chain note decryption.
821
+ */
822
+ declare function deriveViewingKeyFromSpendKey(skSpend: Uint8Array): ViewingKeyPair;
1133
823
  /**
1134
824
  * Derive chain note viewing key from UTXO private key (Zcash-style).
1135
825
  * New UTXO (new keypair) => new viewing key.
@@ -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
  */
@@ -1394,10 +967,27 @@ declare function hexToBytes(hex: string): Uint8Array;
1394
967
  */
1395
968
  declare function bytesToHex(bytes: Uint8Array, prefix?: boolean): string;
1396
969
  /**
1397
- * Generate random bytes using Web Crypto API
970
+ * Thrown when no cryptographically secure randomness source is reachable.
971
+ *
972
+ * `randomBytes` backs note spend keys, blindings, salts and nonces. A predictable
973
+ * value there is unrecoverable — it deanonymizes and can drain the note — so the
974
+ * SDK fails closed instead of degrading to a non-cryptographic generator.
975
+ */
976
+ declare class InsecureRandomnessError extends CloakError {
977
+ constructor(message: string, originalError?: Error);
978
+ }
979
+ /**
980
+ * Generate cryptographically secure random bytes.
981
+ *
982
+ * Source order: `globalThis.crypto.getRandomValues` first (works in every runtime
983
+ * this SDK supports, under both ESM and CJS), then `node:crypto.randomFillSync`.
984
+ * There is deliberately **no** insecure fallback: if neither source is usable this
985
+ * throws {@link InsecureRandomnessError} naming every source that was tried and why
986
+ * it failed.
1398
987
  *
1399
988
  * @param length - Number of bytes to generate
1400
989
  * @returns Random bytes
990
+ * @throws {InsecureRandomnessError} If no cryptographically secure source is available
1401
991
  */
1402
992
  declare function randomBytes(length: number): Uint8Array;
1403
993
  /**
@@ -1429,11 +1019,6 @@ interface Groth16Proof {
1429
1019
  * Format: pi_a (64) + pi_b (128) + pi_c (64) = 256 bytes
1430
1020
  */
1431
1021
  declare function proofToBytes(proof: Groth16Proof): Uint8Array;
1432
- /**
1433
- * Build public inputs bytes for on-chain verification
1434
- * Format: root (32) + nullifier (32) + outputs_hash (32) + public_amount (8) = 104 bytes
1435
- */
1436
- declare function buildPublicInputsBytes(root: bigint, nullifier: bigint, outputsHash: bigint, publicAmount: bigint): Uint8Array;
1437
1022
 
1438
1023
  /**
1439
1024
  * Validate a Solana public key
@@ -1672,6 +1257,67 @@ declare class RelayInternalError extends Error {
1672
1257
  */
1673
1258
  cachedTreeFromChain?: MerkleTree | undefined);
1674
1259
  }
1260
+ /**
1261
+ * Raised when a submission's outcome could NOT be established as "landed" from chain state.
1262
+ *
1263
+ * This is the failure side of settlement verification (see `flows/settlement.ts`). It exists
1264
+ * because the campaign's worst outcome was not a failed transaction — it was an AMBIGUOUS one
1265
+ * whose error text carried no signature (REL-A-10: seven POSTs, the transaction landed, the SDK
1266
+ * threw `RelayInternalError`, and the user had nothing to look up). Losing the signature is what
1267
+ * turns a landed transaction into a support ticket.
1268
+ *
1269
+ * `outcome` is deliberately unambiguous for the caller:
1270
+ *
1271
+ * "landed" the transaction DID land — the input nullifier PDAs exist — but the relay never
1272
+ * returned a usable response, so the SDK cannot hand back commitment indices.
1273
+ * DO NOT RETRY: a retry spends nothing and fails 0x1020. Rescan to recover notes.
1274
+ * "not-landed" provably nothing landed. The inputs are unspent; retrying is safe.
1275
+ * "unknown" the evidence is contradictory or unavailable. Check `signature` on an
1276
+ * independent RPC before doing anything else.
1277
+ * "failed" the transaction is on chain and failed during execution. Inputs are unspent.
1278
+ */
1279
+ declare class SettlementVerificationError extends Error {
1280
+ /** What the chain evidence supports. See the class doc — each value implies a different action. */
1281
+ readonly outcome: "landed" | "not-landed" | "unknown" | "failed";
1282
+ /**
1283
+ * The signature to look up, when one is known. `null` means no counterparty ever gave the SDK
1284
+ * one — which is itself part of the report, not something to paper over.
1285
+ */
1286
+ readonly signature: string | null;
1287
+ /** The verifier's own statement of what was and was not proven. */
1288
+ readonly reason: string;
1289
+ /** True when the input nullifier PDAs were observed on chain. */
1290
+ readonly nullifiersSpent: boolean;
1291
+ /** The underlying relay/RPC error, when the failure started as one. */
1292
+ readonly cause?: unknown | undefined;
1293
+ constructor(message: string,
1294
+ /** What the chain evidence supports. See the class doc — each value implies a different action. */
1295
+ outcome: "landed" | "not-landed" | "unknown" | "failed",
1296
+ /**
1297
+ * The signature to look up, when one is known. `null` means no counterparty ever gave the SDK
1298
+ * one — which is itself part of the report, not something to paper over.
1299
+ */
1300
+ signature: string | null,
1301
+ /** The verifier's own statement of what was and was not proven. */
1302
+ reason: string,
1303
+ /** True when the input nullifier PDAs were observed on chain. */
1304
+ nullifiersSpent?: boolean,
1305
+ /** The underlying relay/RPC error, when the failure started as one. */
1306
+ cause?: unknown | undefined);
1307
+ /** True when retrying this exact spend is safe (nothing was consumed on chain). */
1308
+ get safeToRetry(): boolean;
1309
+ }
1310
+ /**
1311
+ * Pull the signature out of a relay error body.
1312
+ *
1313
+ * The relay's `SubmissionOutcomeUnknown` response is a 503 whose JSON carries
1314
+ * `{"code":"submission_outcome_unknown","retryable":true,"signature":"<sig>"}` (relay
1315
+ * `src/error.rs`). The SDK used to funnel that body into a generic retry and then throw an error
1316
+ * built from a LATER attempt's message, dropping the one field the user needs.
1317
+ */
1318
+ declare function parseRelayErrorSignature(responseText: string): string | null;
1319
+ /** True when a relay error body is the relay's own "I do not know if this landed" report. */
1320
+ declare function isSubmissionOutcomeUnknownResponse(responseText: string): boolean;
1675
1321
  /**
1676
1322
  * Classify a relay error (response body text + HTTP status) into one of the
1677
1323
  * structured error types above, or fall back to RelayInternalError.
@@ -2046,12 +1692,147 @@ interface VerifyUtxosResult {
2046
1692
  declare function verifyUtxos(utxos: Utxo[], connection: Connection, programId: PublicKey, commitment?: "processed" | "confirmed" | "finalized"): Promise<VerifyUtxosResult>;
2047
1693
  /**
2048
1694
  * Pre-flight gate: throw `UtxoAlreadySpentError` if any input is already
2049
- * 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.
2050
1696
  *
2051
1697
  * Browser-safe: uses one batched RPC call.
2052
1698
  */
2053
1699
  declare function preflightNullifiers(utxos: Utxo[], connection: Connection, programId: PublicKey, commitment?: "processed" | "confirmed" | "finalized"): Promise<void>;
2054
1700
 
1701
+ /**
1702
+ * Settlement verification — decide from CHAIN STATE whether a submission landed.
1703
+ *
1704
+ * Why this module exists (adversarial campaign, tier 2):
1705
+ *
1706
+ * X-S-01B A hostile relay answered `/transact` with a well-formed success body carrying a
1707
+ * phantom signature and commitment indices [4242, 4243]. The SDK returned SUCCESS.
1708
+ * Real-RPC `getSignatureStatuses(searchTransactionHistory)` -> null, and BOTH input
1709
+ * nullifier PDAs read `exists=false`. Nothing had landed.
1710
+ * X-S-03a A hostile RPC swallowed `sendTransaction` and fabricated
1711
+ * `{err: null, confirmationStatus: "finalized"}`. `confirmTransaction` was satisfied and
1712
+ * the SDK returned a signature for a transaction that was never forwarded. (A control
1713
+ * deposit landed honestly through the same proxy, so account reads were NOT tampered
1714
+ * with — only the submission and its status were.)
1715
+ *
1716
+ * Both defects have the same shape: the SDK reported success on the strength of what its
1717
+ * counterparty SAID, never on the strength of what the chain SHOWS.
1718
+ *
1719
+ * The ground truth used here is the one the campaign itself used to prove nothing landed: the
1720
+ * input NULLIFIER PDAs. Their addresses are derived locally from the proof's own public inputs
1721
+ * (`["nullifier", pool, nullifier]`), and the program creates one per non-zero input nullifier on
1722
+ * every landed transact — including deposits, whose padding slots still emit real nullifiers
1723
+ * (transaction.circom: "the slot still emits a real nullifier ... and consumed on-chain";
1724
+ * shield-pool/src/instructions/transact.rs skips only all-zero nullifiers).
1725
+ *
1726
+ * Nullifier presence is strictly stronger evidence than a signature status:
1727
+ *
1728
+ * - it is ACCOUNT STATE, so it survives an RPC that has no signature history for the slot
1729
+ * (Surfpool forks, pruned nodes, a load-balanced endpoint that missed the write); and
1730
+ * - the nullifier and the output commitments are fields of the SAME proof's public inputs, so
1731
+ * the only transaction that can create these PDAs is one that also appended exactly our
1732
+ * output commitments. "Nullifiers exist" therefore means "our outputs are in the tree".
1733
+ *
1734
+ * The signature status is still checked, because it is the only signal that can prove a
1735
+ * DEFINITIVE FAILURE (`err != null`) and because a signature the RPC has never heard of is the
1736
+ * fingerprint of a phantom. It is corroboration, never the sole basis for success.
1737
+ *
1738
+ * LIMIT, stated plainly: a single RPC endpoint that lies about ACCOUNT READS as well cannot be
1739
+ * caught by any single-endpoint client, and this module does not pretend to. It closes the two
1740
+ * observed attacks — a lying relay, and an RPC that lies only about submission/status — and it
1741
+ * downgrades every unproven outcome to an explicit, signature-carrying "unknown" instead of
1742
+ * silently reporting success.
1743
+ */
1744
+
1745
+ /**
1746
+ * The RPC surface settlement verification needs, as a structural type rather than a hard
1747
+ * dependency on `Connection`. A real `web3.js` Connection satisfies it; so does a test stub, which
1748
+ * is what lets the phantom-relay / lying-RPC regressions be reproduced without a validator.
1749
+ */
1750
+ interface SettlementConnection {
1751
+ getSignatureStatuses(signatures: string[], config?: {
1752
+ searchTransactionHistory?: boolean;
1753
+ }): Promise<{
1754
+ value: Array<{
1755
+ err: unknown | null;
1756
+ confirmationStatus?: string | null;
1757
+ } | null>;
1758
+ }>;
1759
+ getMultipleAccountsInfo(publicKeys: PublicKey[], commitmentOrConfig?: any): Promise<Array<unknown | null>>;
1760
+ }
1761
+ /**
1762
+ * What the chain says about a submission.
1763
+ *
1764
+ * - `landed` the input nullifier PDAs exist: the proof was consumed, so its output
1765
+ * commitments are in the tree. This is the ONLY status that may be reported as
1766
+ * success.
1767
+ * - `failed` the signature is on chain and carries an execution error. Nothing was applied;
1768
+ * the inputs are still spendable.
1769
+ * - `not-landed` no nullifier PDA exists and the signature (if any) is unknown to the RPC even
1770
+ * with `searchTransactionHistory`. The phantom-signature fingerprint.
1771
+ * - `unknown` the evidence is contradictory or incomplete — most importantly the case where
1772
+ * a status claims confirmation while no nullifier PDA exists. NEVER report this
1773
+ * as success and NEVER silently retry it: the caller must surface the signature.
1774
+ */
1775
+ type SettlementStatus = "landed" | "failed" | "not-landed" | "unknown";
1776
+ interface SettlementVerdict {
1777
+ status: SettlementStatus;
1778
+ /** The signature that was checked, when one was supplied and syntactically usable. */
1779
+ signature: string | null;
1780
+ /** Human-readable statement of what was and was not proven. Safe to put in an error message. */
1781
+ reason: string;
1782
+ /** True when every checkable input nullifier PDA was present on chain. */
1783
+ nullifiersSpent: boolean;
1784
+ /** What `getSignatureStatuses` reported for `signature`. */
1785
+ signatureStatus: "finalized" | "confirmed" | "processed" | "absent" | "error" | "unchecked";
1786
+ }
1787
+ interface ConfirmSettlementParams {
1788
+ connection: SettlementConnection;
1789
+ programId: PublicKey;
1790
+ mint: PublicKey;
1791
+ /** The proof's public input nullifiers. All-zero entries are padding and are skipped. */
1792
+ inputNullifiers: bigint[];
1793
+ /** The signature the counterparty reported, if it reported one. */
1794
+ signature?: string | null;
1795
+ /** Total time to wait for evidence to appear before giving a verdict. */
1796
+ timeoutMs?: number;
1797
+ pollIntervalMs?: number;
1798
+ onProgress?: (status: string) => void;
1799
+ }
1800
+ declare function isPlausibleSignature(value: string | null | undefined): value is string;
1801
+ /**
1802
+ * Derive the nullifier PDA for each non-zero input nullifier. Zero entries are the program's own
1803
+ * padding sentinel (`transact.rs`: "Skip zero nullifiers (padding inputs)") and create no account.
1804
+ */
1805
+ declare function deriveInputNullifierPdas(programId: PublicKey, mint: PublicKey, inputNullifiers: bigint[]): PublicKey[];
1806
+ /**
1807
+ * Establish, from chain state, whether a submission landed.
1808
+ *
1809
+ * Polls until either side of the question is answered or `timeoutMs` elapses. Every RPC error is
1810
+ * absorbed into the verdict rather than thrown: "I could not check" is `unknown`, which the caller
1811
+ * must surface — it is never success.
1812
+ */
1813
+ declare function confirmTransactSettlement(params: ConfirmSettlementParams): Promise<SettlementVerdict>;
1814
+ /**
1815
+ * Gate a DIRECT (self-signed) submission on chain state — X-S-03a.
1816
+ *
1817
+ * `connection.confirmTransaction` is not proof of anything: the campaign's proxy swallowed
1818
+ * `sendTransaction` and answered the follow-up status poll with a fabricated
1819
+ * `{err: null, confirmationStatus: "finalized"}`, and the SDK handed the caller a signature for a
1820
+ * transaction that was never forwarded. A control deposit landed honestly through the same proxy,
1821
+ * so this was not broken plumbing — it was the client believing a status field.
1822
+ *
1823
+ * Throws `SettlementVerificationError` unless the input nullifier PDAs prove the transaction was
1824
+ * applied. The signature is always carried on the error so the caller can look it up.
1825
+ */
1826
+ declare function assertDirectSubmissionLanded(params: {
1827
+ connection: SettlementConnection;
1828
+ programId: PublicKey;
1829
+ mint: PublicKey;
1830
+ nullifiers: Array<Uint8Array | bigint>;
1831
+ signature: string;
1832
+ timeoutMs?: number;
1833
+ onProgress?: (status: string) => void;
1834
+ }): Promise<SettlementVerdict>;
1835
+
2055
1836
  /**
2056
1837
  * Structured Logger for Cloak SDK
2057
1838
  *
@@ -2059,7 +1840,7 @@ declare function preflightNullifiers(utxos: Utxo[], connection: Connection, prog
2059
1840
  * 2026-01-23T00:51:46.489317Z INFO cloak::module: 📥 Message key=value
2060
1841
  *
2061
1842
  * Enable via:
2062
- * - SDK config: new CloakSDK({ debug: true })
1843
+ * - Code: setDebugMode(true)
2063
1844
  * - Environment: CLOAK_DEBUG=1 or DEBUG=cloak:*
2064
1845
  */
2065
1846
  type LogLevel = "DEBUG" | "INFO" | "WARN" | "ERROR";
@@ -2510,6 +2291,29 @@ declare function getNullifierPDA(poolPubkey: PublicKey, nullifier: Uint8Array |
2510
2291
  * @returns [PublicKey, bump] - The swap state PDA and its bump seed
2511
2292
  */
2512
2293
  declare function getSwapStatePDA(poolPubkey: PublicKey, nullifier: Uint8Array | Buffer, programId?: PublicKey): [PublicKey, number];
2294
+ /**
2295
+ * M-07: derive the per-pool PoolAuthorityConfig PDA (holds the mint-scoped
2296
+ * withdraw-authorizer).
2297
+ *
2298
+ * Seeds: ["pool_authority", pool_mint]
2299
+ */
2300
+ declare function getPoolAuthorityConfigPDA(mint?: PublicKey, programId?: PublicKey): [PublicKey, number];
2301
+ /**
2302
+ * Registry PDA the relay's recipient-delivery carrier (CLKD1) touches so the carrier transaction is
2303
+ * enumerable via `getSignaturesForAddress`.
2304
+ *
2305
+ * Seed: ["cloak_delivery_registry"] — `DELIVERY_REGISTRY_SEED` in
2306
+ * `services/relay/src/solana/mod.rs::emit_recipient_delivery_carrier`. Mint-independent, exactly
2307
+ * like the chain-note registry: one registry per program, not per pool.
2308
+ */
2309
+ declare function getDeliveryRegistryPDA(programId?: PublicKey): PublicKey;
2310
+ /**
2311
+ * Registry PDA the relay's compliance chain-note carrier (CLK1) touches.
2312
+ *
2313
+ * Seed: ["cloak_chain_note_registry"] — `CHAIN_NOTE_REGISTRY_SEED` in
2314
+ * `services/relay/src/solana/mod.rs::emit_chain_note_carrier`.
2315
+ */
2316
+ declare function getChainNoteRegistryPDA(programId?: PublicKey): PublicKey;
2513
2317
 
2514
2318
  /**
2515
2319
  * On-chain Merkle proof computation
@@ -2571,6 +2375,86 @@ declare function computeProofForLatestDeposit(connection: Connection, merkleTree
2571
2375
  leafIndex: number;
2572
2376
  }>;
2573
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
+
2574
2458
  /**
2575
2459
  * Relay client utilities for fetching data from the relay service
2576
2460
  */
@@ -2625,7 +2509,12 @@ declare function buildMerkleTreeFromRelay(relayUrl: string, options?: {
2625
2509
  maxRetries?: number;
2626
2510
  waitForIndex?: number;
2627
2511
  }): Promise<MerkleTree>;
2628
- declare function buildMerkleTreeFromChain(connection: Connection, programId: PublicKey, merkleTree: PublicKey, onProgress?: (message: string) => void): Promise<MerkleTree>;
2512
+ declare function buildMerkleTreeFromChain(connection: Connection, programId: PublicKey, merkleTree: PublicKey, onProgress?: (message: string) => void,
2513
+ /**
2514
+ * The pool mint this tree belongs to. Required in practice: without it a `CloseSwapState` refund
2515
+ * leaf cannot be attributed to a tree, and reconstruction fails closed rather than guess.
2516
+ */
2517
+ mint?: PublicKey): Promise<MerkleTree>;
2629
2518
  /**
2630
2519
  * Pre-flight root validation
2631
2520
  *
@@ -2679,261 +2568,204 @@ declare function preflightCheck(relayUrl: string, rootHex: string): Promise<{
2679
2568
  }>;
2680
2569
 
2681
2570
  /**
2682
- * Direct Circom WASM Proof Generation
2571
+ * The SDK's host-environment predicates, in one place (X-S-02C).
2572
+ *
2573
+ * ── The defect this closes ────────────────────────────────────────────────────────────────────
2574
+ * The merkle-from-chain rebuild was guarded by TWO independently written predicates that disagreed
2575
+ * about React Native:
2576
+ *
2577
+ * - the GATE in `flows/transact.ts` — `!IS_BROWSER && isMerkleClass && relayUrl`, where
2578
+ * `IS_BROWSER` was `typeof window !== "undefined" || typeof globalThis.document !== "undefined"`,
2579
+ * with NO React-Native carve-out. React Native defines `window`, so RN read as a browser and the
2580
+ * last-resort chain replay was silently skipped there.
2581
+ * - the BUILDER it guards — `relay/client.ts::buildMerkleTreeFromChain`, whose own
2582
+ * `isBrowser()` short-circuits on `navigator.product === "ReactNative"` and therefore explicitly
2583
+ * PERMITS React Native to rebuild from chain.
2584
+ *
2585
+ * So one half of the same mechanism classified RN as a browser and the other half classified it as
2586
+ * not-a-browser.
2587
+ *
2588
+ * ── The decision: the BUILDER is right, and the gate was wrong ────────────────────────────────
2589
+ * The browser fails closed for a stated reason — a full signature-history scan is too slow and too
2590
+ * unreliable on a browser main thread, and a browser always has a reachable relay whose tree it can
2591
+ * use instead. Neither half of that reasoning holds for React Native: it is not a browser, it has no
2592
+ * DOM, it is not running on a page's main thread, and on a local node it has no relay-tree fallback
2593
+ * at all. Closing the gate against RN removed its ONLY recovery path from a drifted relay tree —
2594
+ * precisely the class of failure the fallback exists for — while leaving the builder it calls happy
2595
+ * to serve it.
2596
+ *
2597
+ * The alternative reading (make the builder refuse RN too, so the two agree by failing closed
2598
+ * everywhere) was rejected: it agrees by breaking the Cloak mobile wallet, whose merkle path depends
2599
+ * on the carve-out, and it discards a documented, deliberate decision in favour of an undocumented
2600
+ * accident. The gate's own comment never mentions React Native — it argues about SSR — which is what
2601
+ * an accident looks like.
2683
2602
  *
2684
- * This module provides direct proof generation using snarkjs and Circom WASM,
2685
- * matching the approach used in services-new/tests/src/proof.ts
2603
+ * ── Two predicates, deliberately, and the difference is not RN ────────────────────────────────
2604
+ * `isBrowser()` is a HARD REFUSAL — "this host cannot do it at all" — so it demands a real DOM
2605
+ * (`window` AND `document`).
2606
+ * `isBrowserLike()` is a CONSERVATIVE OPT-OUT — "don't start something slow here" — so `window` OR
2607
+ * `document` is enough, which keeps an SSR host that polyfills only `document` from being mistaken
2608
+ * for Node and made to attempt a chain replay.
2686
2609
  *
2687
- * This uses pinned S3-hosted circuit artifacts and does not require
2688
- * a backend prover service.
2610
+ * They differ on SSR on purpose. They must NEVER differ on React Native again, which is why both are
2611
+ * built from the single `isReactNative()` below.
2612
+ *
2613
+ * All three are functions, not module-scope constants: a constant is frozen at import time, so any
2614
+ * host that installs its globals after the bundle loads — and any test that wants to pin the three
2615
+ * environments — reads a stale answer.
2689
2616
  */
2690
-
2691
2617
  /**
2692
- * Default URL for fetching circuit artifacts
2693
- * Hosted in S3 and versioned by build.
2618
+ * True on React Native. `navigator.product === "ReactNative"` is the canonical flag and is what
2619
+ * `proving/artifacts.ts` has always used.
2694
2620
  */
2695
- declare const DEFAULT_CIRCUITS_URL = "https://storage.googleapis.com/cloak-circuits/circuits/0.1.0";
2696
- interface WithdrawRegularInputs {
2697
- root: bigint;
2698
- nullifier: bigint;
2699
- outputs_hash: bigint;
2700
- public_amount: bigint;
2701
- amount: bigint;
2702
- leaf_index: bigint;
2703
- sk: [bigint, bigint];
2704
- r: [bigint, bigint];
2705
- pathElements: bigint[];
2706
- pathIndices: number[];
2707
- num_outputs: number;
2708
- out_addr: bigint[][];
2709
- out_amount: bigint[];
2710
- out_flags: number[];
2711
- var_fee: bigint;
2712
- rem: bigint;
2713
- }
2714
- interface WithdrawSwapInputs {
2715
- sk_spend: bigint;
2716
- r: bigint;
2717
- amount: bigint;
2718
- leaf_index: bigint;
2719
- path_elements: bigint[];
2720
- path_indices: number[];
2721
- root: bigint;
2722
- nullifier: bigint;
2723
- outputs_hash: bigint;
2724
- public_amount: bigint;
2725
- input_mint: bigint[];
2726
- output_mint: bigint[];
2727
- recipient_ata: bigint[];
2728
- min_output_amount: bigint;
2729
- var_fee: bigint;
2730
- rem: bigint;
2731
- }
2732
- interface ProofResult {
2733
- proof: Groth16Proof;
2734
- publicSignals: string[];
2735
- proofBytes: Uint8Array;
2736
- publicInputsBytes: Uint8Array;
2737
- }
2621
+ declare function isReactNative(): boolean;
2738
2622
  /**
2739
- * Generate Groth16 proof for regular withdrawal using Circom WASM
2623
+ * True only on a real browser: a DOM host with both `window` and `document`, and not React Native.
2740
2624
  *
2741
- * This matches the approach in services-new/tests/src/proof.ts
2625
+ * Use for HARD refusals work this host genuinely cannot perform.
2626
+ */
2627
+ declare function isBrowser(): boolean;
2628
+ /**
2629
+ * True on a browser OR on a DOM-ish host such as SSR that defines only one of `window`/`document`,
2630
+ * and false on React Native and on Node.
2742
2631
  *
2743
- * @param inputs - Circuit inputs
2744
- * @param circuitsPath - Ignored. Proof generation always uses pinned S3 circuits.
2632
+ * Use for CONSERVATIVE opt-outs expensive work that should not be started speculatively.
2745
2633
  */
2746
- declare function generateWithdrawRegularProof(inputs: WithdrawRegularInputs, circuitsPath: string): Promise<ProofResult>;
2634
+ declare function isBrowserLike(): boolean;
2635
+
2747
2636
  /**
2748
- * Generate Groth16 proof for swap withdrawal using Circom WASM
2637
+ * How long the relay accepts a signed request after its `auth_issued_at`
2638
+ * (`REQUEST_AUTH_MAX_AGE_SECONDS`, `api/request_auth.rs`).
2749
2639
  *
2750
- * This matches the approach in services-new/tests/src/proof.ts
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`).
2751
2648
  *
2752
- * @param inputs - Circuit inputs
2753
- * @param circuitsPath - Ignored. Proof generation always uses pinned S3 circuits.
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.
2754
2652
  */
2755
- declare function generateWithdrawSwapProof(inputs: WithdrawSwapInputs, circuitsPath: string): Promise<ProofResult>;
2653
+ declare const REQUEST_AUTH_MAX_FUTURE_SKEW_SECONDS = 30;
2756
2654
  /**
2757
- * Check if circuits are available from the pinned S3 source.
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.
2758
2658
  */
2759
- declare function areCircuitsAvailable(circuitsPath: string): Promise<boolean>;
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"];
2760
2661
  /**
2761
- * Get default circuits URL.
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.
2762
2687
  */
2763
- declare function getDefaultCircuitsPath(): Promise<string>;
2688
+ declare function canonicalJson(value: unknown): string;
2764
2689
  /**
2765
- * Pinned circuit artifact hashes (SHA-256).
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.
2766
2697
  *
2767
- * These hashes must be updated whenever the trusted circuit artifacts change.
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.
2768
2700
  */
2769
- declare const EXPECTED_CIRCUIT_HASHES: {
2770
- withdraw_regular_wasm: string;
2771
- withdraw_regular_zkey: string;
2772
- withdraw_swap_wasm: string;
2773
- withdraw_swap_zkey: string;
2774
- };
2701
+ interface RelayAuthPreimage {
2702
+ sender: string;
2703
+ auth_issued_at: string;
2704
+ auth_nonce: string;
2705
+ message: Uint8Array;
2706
+ }
2775
2707
  /**
2776
- * Circuit verification result
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.
2777
2717
  */
2778
- interface CircuitVerificationResult {
2779
- /** Whether verification passed */
2780
- valid: boolean;
2781
- /** Which circuit was checked */
2782
- circuit: 'withdraw_regular' | 'withdraw_swap';
2783
- /** Error message if verification failed */
2784
- error?: string;
2785
- computed?: {
2786
- wasm: string;
2787
- zkey: string;
2788
- };
2789
- expected?: {
2790
- wasm: string;
2791
- zkey: string;
2792
- };
2793
- }
2718
+ declare function buildRelayAuthPreimage(endpoint: string, programId: PublicKey, body: Record<string, unknown>, sender: PublicKey, nowSeconds?: number, fields?: readonly string[]): RelayAuthPreimage;
2794
2719
  /**
2795
- * Verify circuit integrity by checking verification key hashes
2796
- *
2797
- * This function fetches the verification key and computes its hash,
2798
- * then compares against the expected hash embedded in the SDK.
2799
- *
2800
- * IMPORTANT: If the hashes don't match, the circuit may produce proofs
2801
- * that will be rejected by the on-chain verifier!
2720
+ * An async message signer standing in for a `Keypair`.
2802
2721
  *
2803
- * @param circuitsPath - Ignored. Verification always uses pinned S3 circuits.
2804
- * @param circuit - Which circuit to verify ('withdraw_regular' or 'withdraw_swap')
2805
- * @returns Verification result
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.
2806
2725
  *
2807
- * @example
2808
- * ```typescript
2809
- * const result = await verifyCircuitIntegrity('https://storage.googleapis.com/cloak-circuits/circuits/0.1.0', 'withdraw_regular');
2810
- * if (!result.valid) {
2811
- * console.error('Circuit verification failed:', result.error);
2812
- * // Don't proceed with proof generation!
2813
- * }
2814
- * ```
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.
2815
2730
  */
2816
- declare function verifyCircuitIntegrity(circuitsPath: string, circuit: 'withdraw_regular' | 'withdraw_swap'): Promise<CircuitVerificationResult>;
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
+ }
2817
2737
  /**
2818
- * Verify all circuits before use
2819
- *
2820
- * Call this at SDK initialization to ensure circuits are valid.
2738
+ * Turn a relay rejection into something the person in front of the screen can act on.
2821
2739
  *
2822
- * @param circuitsPath - Ignored. Verification always uses pinned S3 circuits.
2823
- * @returns Array of verification results (one per circuit)
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.
2824
2744
  */
2825
- declare function verifyAllCircuits(circuitsPath: string): Promise<CircuitVerificationResult[]>;
2745
+ declare function explainRelayAuthRejection(responseText: string): string | null;
2826
2746
 
2827
2747
  /**
2828
- * Pending Operations Manager
2748
+ * The circuit artifacts this SDK build proves against.
2829
2749
  *
2830
- * Utility for persisting pending deposit/withdrawal operations in browser storage.
2831
- * This enables recovery if the browser crashes or user navigates away mid-operation.
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.
2832
2755
  *
2833
- * IMPORTANT: This uses localStorage by default which has security implications.
2834
- * Notes contain sensitive spending keys - consider using more secure storage
2835
- * in production (e.g., encrypted IndexedDB, secure enclave).
2836
- */
2837
-
2838
- /**
2839
- * Pending deposit record
2840
- */
2841
- interface PendingDeposit {
2842
- /** The note (contains spending secrets!) */
2843
- note: CloakNote;
2844
- /** When the deposit was initiated */
2845
- startedAt: number;
2846
- /** Transaction signature if available */
2847
- txSignature?: string;
2848
- /** Status of the deposit */
2849
- status: "pending" | "tx_sent" | "confirmed" | "failed";
2850
- /** Error message if failed */
2851
- error?: string;
2852
- }
2853
- /**
2854
- * Pending withdrawal record
2855
- */
2856
- interface PendingWithdrawal {
2857
- /** The relay request ID (for resumption) */
2858
- requestId: string;
2859
- /** The note commitment being withdrawn */
2860
- commitment: string;
2861
- /** The nullifier being used */
2862
- nullifier: string;
2863
- /** When the withdrawal was initiated */
2864
- startedAt: number;
2865
- /** Status of the withdrawal */
2866
- status: "pending" | "processing" | "completed" | "failed";
2867
- /** Transaction signature if completed */
2868
- txSignature?: string;
2869
- /** Error message if failed */
2870
- error?: string;
2871
- }
2872
- /**
2873
- * Save a pending deposit
2874
- * Call this BEFORE sending the on-chain transaction to ensure note is persisted
2875
- */
2876
- declare function savePendingDeposit(deposit: PendingDeposit): void;
2877
- /**
2878
- * Load all pending deposits
2879
- */
2880
- declare function loadPendingDeposits(): PendingDeposit[];
2881
- /**
2882
- * Update a pending deposit status
2883
- */
2884
- declare function updatePendingDeposit(commitment: string, updates: Partial<PendingDeposit>): void;
2885
- /**
2886
- * Remove a pending deposit (e.g., after successful confirmation)
2887
- */
2888
- declare function removePendingDeposit(commitment: string): void;
2889
- /**
2890
- * Clear all pending deposits
2891
- */
2892
- declare function clearPendingDeposits(): void;
2893
- /**
2894
- * Save a pending withdrawal
2895
- * Call this when you receive the request_id from the relay
2896
- */
2897
- declare function savePendingWithdrawal(withdrawal: PendingWithdrawal): void;
2898
- /**
2899
- * Load all pending withdrawals
2900
- */
2901
- declare function loadPendingWithdrawals(): PendingWithdrawal[];
2902
- /**
2903
- * Update a pending withdrawal status
2904
- */
2905
- declare function updatePendingWithdrawal(requestId: string, updates: Partial<PendingWithdrawal>): void;
2906
- /**
2907
- * Remove a pending withdrawal (e.g., after successful completion)
2908
- */
2909
- declare function removePendingWithdrawal(requestId: string): void;
2910
- /**
2911
- * Clear all pending withdrawals
2912
- */
2913
- declare function clearPendingWithdrawals(): void;
2914
- /**
2915
- * Check if there are any pending operations that need recovery
2916
- * Call this on page load to determine if recovery UI should be shown
2917
- */
2918
- declare function hasPendingOperations(): boolean;
2919
- /**
2920
- * Get summary of pending operations for recovery UI
2921
- */
2922
- declare function getPendingOperationsSummary(): {
2923
- deposits: PendingDeposit[];
2924
- withdrawals: PendingWithdrawal[];
2925
- totalPending: number;
2926
- };
2927
- /**
2928
- * Clean up stale pending operations
2929
- * Call this periodically to remove old failed/completed operations
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.
2930
2759
  *
2931
- * @param maxAgeMs Maximum age in milliseconds before an operation is removed (default: 24 hours)
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.
2932
2767
  */
2933
- declare function cleanupStalePendingOperations(maxAgeMs?: number): {
2934
- removedDeposits: number;
2935
- removedWithdrawals: number;
2936
- };
2768
+ declare const TRANSACTION_CIRCUITS_VERSION = "0.2.0";
2937
2769
 
2938
2770
  /**
2939
2771
  * UTXO Transaction Methods
@@ -2944,23 +2776,161 @@ declare function cleanupStalePendingOperations(maxAgeMs?: number): {
2944
2776
  * - partialWithdraw(): Withdraw with change
2945
2777
  */
2946
2778
 
2947
- declare const DEFAULT_TRANSACTION_CIRCUITS_URL = "https://storage.googleapis.com/cloak-circuits/circuits/0.1.0";
2779
+ /**
2780
+ * Can this host attempt the last-resort "rebuild the merkle tree from chain" recovery?
2781
+ *
2782
+ * X-S-02C: this gate and the builder it guards (`buildMerkleTreeFromChain`, which refuses only on a
2783
+ * real browser and EXPLICITLY permits React Native) used to be written separately and disagreed —
2784
+ * RN defines `window`, so the old inline `typeof window !== "undefined" || typeof document !==
2785
+ * "undefined"` classified RN as a browser and skipped the rebuild, removing RN's only recovery path
2786
+ * from a drifted relay tree while the builder was perfectly willing to serve it. Both now come from
2787
+ * `shared/environment`, so they cannot disagree about React Native again.
2788
+ *
2789
+ * The SSR conservatism is kept: `isBrowserLike()` is true when EITHER `window` or `document` exists,
2790
+ * because an SSR host that polyfills only `document` must not be mistaken for Node and made to run a
2791
+ * full signature-history scan.
2792
+ *
2793
+ * A function, not a module-scope constant: the constant was frozen at import time, so a host that
2794
+ * installs its globals after the bundle loads read a stale answer.
2795
+ */
2796
+ declare function canRebuildMerkleTreeFromChain(): boolean;
2797
+
2798
+ /**
2799
+ * Base the ceremony-frozen `transaction` artifacts are fetched from by default.
2800
+ *
2801
+ * Derived from {@link TRANSACTION_CIRCUITS_BASE_URL} — the same record that pins the
2802
+ * digests — so the version in the URL and the digests checked against it cannot
2803
+ * drift apart. It is `null` while this SDK build pins no location whose bytes
2804
+ * were verified to hash to those digests; that makes an unconfigured SDK a
2805
+ * `tsc` error at the call site (`setCircuitsPath(DEFAULT_TRANSACTION_CIRCUITS_URL)`
2806
+ * does not type-check against `string`) and an immediate, explanatory throw at
2807
+ * runtime, instead of a digest mismatch ~22 MB into proof generation.
2808
+ */
2809
+ declare const DEFAULT_TRANSACTION_CIRCUITS_URL: string | null;
2810
+
2948
2811
  /**
2949
2812
  * Set circuits base path: local directory containing `transaction_js/` and `transaction_final.zkey`,
2950
- * or an `http(s)` base URL to those artifacts (Node will download once per process to a temp dir).
2813
+ * or an `http(s)` base URL to those artifacts (loaded into memory once per process).
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
+ *
2830
+ * No cache invalidation is needed here: verified artifact buffers are memoised
2831
+ * per base inside `proving/artifacts.ts`, so a new base loads and re-verifies its
2832
+ * own bytes.
2833
+ *
2834
+ * @throws when `next` names a location this build may not read circuit artifacts from.
2951
2835
  */
2952
2836
  declare function setCircuitsPath(next: string): void;
2953
2837
  /**
2954
- * Get the current circuits path
2838
+ * Get the current circuits path, or `null` when none is configured and this SDK
2839
+ * build pins no verified default (see `DEFAULT_TRANSACTION_CIRCUITS_URL`).
2840
+ */
2841
+ declare function getCircuitsPath(): string | null;
2842
+ /**
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.
2853
+ *
2854
+ * Use this instead of writing an artifact URL out by hand — a hand-written URL
2855
+ * is exactly how the base's version segment came to disagree with the digests
2856
+ * this SDK checks against. Throws an explanatory error (naming the expected
2857
+ * bundle version and both expected digests) when nothing resolves, and refuses a
2858
+ * base this build may not read from.
2955
2859
  */
2956
- declare function getCircuitsPath(): string;
2957
- declare function computeChainNoteHash(publicAmount: bigint, extDataHash: bigint, noteTimestamp: bigint, noteCommitment: bigint): Promise<bigint>;
2860
+ declare function resolveCircuitsBase(explicit?: string): string;
2958
2861
  /**
2959
- * Compute extDataHash for binding proof to external parameters
2862
+ * Chain note v4.
2960
2863
  *
2961
- * extDataHash = Poseidon(recipient, relayerFee, relayer)
2864
+ * v4 binds noteSemantics = Poseidon(outAmount[0], outPubkey[0], noteIsSendToSelfKey0) into the note
2865
+ * tail, matching the ceremony circuit. The tail is therefore a 4-input hash, not 3.
2866
+ *
2867
+ * noteIsSendToSelfKey0 is 1 only when publicAmount == 0 AND output 0 went to the spender's own key,
2868
+ * exactly as the circuit computes it.
2869
+ */
2870
+ declare function computeChainNoteHash(publicAmount: bigint, extDataHash: bigint, noteTimestamp: bigint, noteCommitment: bigint, noteSalt: bigint, outAmount0: bigint, outPubkey0: bigint, noteIsSendToSelfKey0: bigint): Promise<bigint>;
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.
2962
2879
  */
2963
- declare function computeExtDataHash(recipient: PublicKey | null, relayerFee: bigint, relayer: PublicKey | null): Promise<bigint>;
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
+ }
2964
2934
  /**
2965
2935
  * Options for transact operation
2966
2936
  */
@@ -2978,8 +2948,19 @@ interface TransactOptions {
2978
2948
  /** Relayer address for fee payment */
2979
2949
  relayer?: PublicKey;
2980
2950
  /**
2981
- * Relay URL override.
2982
- * Defaults to `https://api.cloak.ag` when omitted.
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.
2983
2964
  */
2984
2965
  relayUrl?: string;
2985
2966
  /** Keypair of the depositor (signs the deposit transaction) - for programmatic use */
@@ -2998,6 +2979,16 @@ interface TransactOptions {
2998
2979
  walletPublicKey?: PublicKey;
2999
2980
  /** Maximum retries on RootNotFound error (default: 5) */
3000
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;
3001
2992
  /** Delay between retries in ms (default: 3000) */
3002
2993
  retryDelayMs?: number;
3003
2994
  /**
@@ -3010,6 +3001,12 @@ interface TransactOptions {
3010
3001
  * Used for deposits when riskOracleQueue is set. The backend must return a signed
3011
3002
  * quote instruction for the depositor wallet so the program can verify at index 0.
3012
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.
3013
3010
  */
3014
3011
  riskQuoteUrl?: string;
3015
3012
  /**
@@ -3065,6 +3062,13 @@ interface TransactOptions {
3065
3062
  * Each entry must be base64-encoded note bytes.
3066
3063
  */
3067
3064
  encryptedNotes?: string[];
3065
+ /**
3066
+ * Omit the on-chain encrypted chain-note envelope entirely. The chainNoteHash public input
3067
+ * is unaffected (proof still binds it); only the optional ciphertext blob the program emits
3068
+ * for wallet auto-scan is dropped. Use when the caller tracks UTXOs out-of-band and needs the
3069
+ * smaller packet — e.g. V3 + SPL deposits that would otherwise overflow the 1232-byte limit.
3070
+ */
3071
+ disableChainNotes?: boolean;
3068
3072
  /**
3069
3073
  * Optional nk (32 bytes, hex or bytes) for diversified chain note encryption (Phase 3).
3070
3074
  * When provided, 2 per-output encrypted notes are embedded on-chain.
@@ -3076,6 +3080,32 @@ interface TransactOptions {
3076
3080
  * passing nk at every call site — e.g. pass a getter from your key manager.
3077
3081
  */
3078
3082
  getChainNoteViewingKeyNk?: () => Promise<string | Uint8Array | null>;
3083
+ /**
3084
+ * Pin the chain note's 96-bit `noteSalt` instead of drawing a fresh one (VK-01).
3085
+ *
3086
+ * Pass the `noteSalt` returned by {@link createRecoverableDepositUtxo}. That helper derives the
3087
+ * deposit note's keypair and blinding as `PRF(nk, noteSalt)`, and the chain note is the ONLY place
3088
+ * the salt is published — so a cold `(rpc, programId, nk)` scan can only rebuild the note if the
3089
+ * salt that anchored it is the salt that reaches the note. Leaving this unset draws a random salt,
3090
+ * which is correct for every non-derived flow.
3091
+ *
3092
+ * Requires an explicit `chainNoteViewingKeyNk` / `getChainNoteViewingKeyNk`; `transact` refuses
3093
+ * otherwise rather than pairing the salt with an nk inferred from the note's own key.
3094
+ */
3095
+ chainNoteSalt?: bigint;
3096
+ /**
3097
+ * The RECIPIENT's 32-byte X25519 public viewing key, for a shield-to-shield send.
3098
+ *
3099
+ * Supply `deriveViewingKeyFromNk(recipientNk).publicKey` (hex or bytes). When present, the SDK
3100
+ * seals `{amount, blinding}` of output 0 to this key and ships it as `recipient_delivery_notes`,
3101
+ * which the relay publishes as a CLKD1 carrier the recipient can find with their `nk` alone.
3102
+ *
3103
+ * When absent, the send still succeeds but the recipient CANNOT discover the note from chain —
3104
+ * it must be handed over out of band. That was the shipped behaviour and is what campaign rows
3105
+ * S2S-08 / VK-01 measured. Ignored on deposits and withdrawals: the relay rejects the field
3106
+ * outright on anything that is not a send.
3107
+ */
3108
+ recipientViewingPublicKey?: Uint8Array | string;
3079
3109
  /**
3080
3110
  * Cached Merkle tree from a previous transaction.
3081
3111
  * When provided, the SDK skips fetching commitments from the relay and uses this
@@ -3098,7 +3128,7 @@ interface TransactOptions {
3098
3128
  useUniqueNullifiers?: boolean;
3099
3129
  /**
3100
3130
  * Optional DEX allow-list for swaps (Jupiter `dexes`).
3101
- * Example: ["Orca V2", "Raydium CLMM"]
3131
+ * Example: ["Meteora DLMM", "Raydium CLMM"]
3102
3132
  */
3103
3133
  swapDexes?: string[];
3104
3134
  /**
@@ -3138,8 +3168,24 @@ interface TransactOptions {
3138
3168
  * Use when relay may be behind the chain (e.g. commitment_sync lag) to avoid ProofInvalid.
3139
3169
  */
3140
3170
  useChainRootForProof?: boolean;
3171
+ /**
3172
+ * Optional external fee-payer adapter (e.g. Kora) for deposits only. When set, the depositor no
3173
+ * longer needs to hold SOL: the adapter's payer covers the network fee (and, via
3174
+ * `buildTopUpInstructions`, any on-chain rent) and is reimbursed in an SPL token within the same
3175
+ * transaction. Omit for unchanged default behavior.
3176
+ */
3177
+ externalFeePayer?: ExternalFeePayerAdapter;
3141
3178
  }
3142
- /** Switchboard-style response: pre-built instruction. */
3179
+ /**
3180
+ * Switchboard-style response: a pre-built instruction.
3181
+ *
3182
+ * @deprecated No longer accepted. `fetchRiskQuote` rejects this shape outright:
3183
+ * a relay that can name the program id, the account metas and the data of an
3184
+ * instruction the user's wallet signs is a signing oracle. The relay only ever
3185
+ * returns `{ signature, message, signer_pubkey }`, which the SDK verifies and
3186
+ * rebuilds locally as an account-free Ed25519 instruction. The type is kept as
3187
+ * an export purely so consumers importing it still compile.
3188
+ */
3143
3189
  interface RiskQuoteInstructionResponse {
3144
3190
  instruction: {
3145
3191
  programId: string;
@@ -3151,17 +3197,130 @@ interface RiskQuoteInstructionResponse {
3151
3197
  data: string;
3152
3198
  };
3153
3199
  }
3154
- /**
3155
- * Fetch the risk quote instruction from a backend (e.g. relay /risk-quote).
3156
- * The backend should call Switchboard's fetchQuoteIx for the given wallet
3157
- * (Range Risk API) and return the serialized instruction.
3158
- * See: https://www.range.org/blog/integrate-range-onchain-risk-verifier-into-your-solana-program
3159
- */
3160
- declare function fetchRiskQuoteInstruction(riskQuoteUrl: string, wallet: PublicKey, options?: {
3200
+ declare function fetchRiskQuoteInstruction(riskQuoteUrl: string, wallet: PublicKey, options: {
3201
+ poolMint: PublicKey;
3161
3202
  recipient?: PublicKey;
3162
3203
  amount?: bigint;
3163
3204
  token?: PublicKey;
3205
+ commitment?: string | Uint8Array;
3206
+ context?: "deposit" | "send" | "withdraw";
3207
+ nullifier0?: string;
3208
+ nullifier1?: string;
3164
3209
  }): Promise<TransactionInstruction>;
3210
+ /** Everything the `/transact` relay body is built from. */
3211
+ interface TransactRequestBodyParams {
3212
+ proofB64: string;
3213
+ publicInputsB64: string;
3214
+ mint: PublicKey;
3215
+ recipient?: PublicKey | null;
3216
+ relayer?: PublicKey | null;
3217
+ relayerFee: bigint;
3218
+ maxFee: bigint;
3219
+ encryptedNotes?: string[];
3220
+ riskQuote?: {
3221
+ signature: string;
3222
+ message: string;
3223
+ signer_pubkey: string;
3224
+ };
3225
+ /** Present only on shield-to-shield sends, where the relay enforces sanctions on the sender. */
3226
+ sender?: PublicKey | null;
3227
+ /** Signed public amount; zero is a shield-to-shield send. */
3228
+ externalAmount: bigint;
3229
+ /** Base64 CLKD1 envelopes, from `buildRecipientDeliveryNotes`. Omitted when the rail is off. */
3230
+ recipientDeliveryNotes?: string[];
3231
+ }
3232
+ /**
3233
+ * Assemble the `/transact` body.
3234
+ *
3235
+ * Extracted so the wire shape is testable without a proof. The field set is a contract with
3236
+ * `services/relay/src/api/transact.rs`, and `recipient_delivery_notes` in particular is part of the
3237
+ * relay's signed field view (`TRANSACT_AUTH_FIELDS`) — so a body that carries it must be signed
3238
+ * with it present, and a body that omits it must be signed with an explicit null. `buildAuthRequest`
3239
+ * handles that, but only if the field is genuinely absent rather than set to `undefined`-ish values.
3240
+ */
3241
+ declare function buildTransactRequestBody(params: TransactRequestBodyParams): Record<string, unknown>;
3242
+ /** Everything settlement verification needs to check THIS proof against chain state. */
3243
+ interface SettlementContext {
3244
+ connection: SettlementConnection;
3245
+ programId: PublicKey;
3246
+ mint: PublicKey;
3247
+ /** The proof's public input nullifiers — the ground truth for "did this land". */
3248
+ inputNullifiers: bigint[];
3249
+ }
3250
+ type RelaySubmissionResult = {
3251
+ kind: "submitted";
3252
+ signature: string;
3253
+ commitmentIndices?: [number, number];
3254
+ viewingKeyRegistered?: boolean;
3255
+ settlement: SettlementVerdict;
3256
+ }
3257
+ /** The relay rejected the proof's root; the caller must rebuild the tree and re-prove. */
3258
+ | {
3259
+ kind: "stale-root";
3260
+ error: Error;
3261
+ } | {
3262
+ kind: "failed";
3263
+ error: Error;
3264
+ };
3265
+
3266
+ interface SubmitTransactToRelayArgs {
3267
+ relayUrl: string;
3268
+ /** The exact body to POST. Auth fields are added ONCE, in place, and then never changed. */
3269
+ requestBody: Record<string, unknown>;
3270
+ programId: PublicKey;
3271
+ depositorKeypair?: Keypair;
3272
+ /**
3273
+ * Wallet-adapter alternative to `depositorKeypair` for request authentication, used only when
3274
+ * no keypair is supplied. Lets a browser or mobile caller — which holds no secret key — submit
3275
+ * through this same function instead of hand-rolling the scheme.
3276
+ *
3277
+ * COMPLIANCE: `walletPublicKey` becomes the authenticated `sender`, which is the key screened
3278
+ * for sanctions on shield-to-shield sends. It must be the end user's real wallet; an ephemeral
3279
+ * or server-side key here is a compliance regression, not a shortcut.
3280
+ */
3281
+ relayAuthSigner?: RelayAuthSigner;
3282
+ settlement: SettlementContext;
3283
+ /** True while the caller still has a re-prove budget for a stale root. */
3284
+ canRetryStaleRoot: boolean;
3285
+ maxNetworkRetries?: number;
3286
+ requestTimeoutMs?: number;
3287
+ /** Injection seam for tests; defaults to global fetch. */
3288
+ fetchImpl?: typeof fetch;
3289
+ onProgress?: (status: string) => void;
3290
+ /** Total window allowed for the on-chain settlement check after a reported success. */
3291
+ settlementTimeoutMs?: number;
3292
+ /** Shorter window used to ask "did it land anyway?" after a terminal relay failure. */
3293
+ failureProbeTimeoutMs?: number;
3294
+ }
3295
+ /**
3296
+ * POST one logical `/transact` request and REPORT ONLY WHAT THE CHAIN CONFIRMS.
3297
+ *
3298
+ * Extracted from `transact()` so the two campaign defects it fixes are reproducible without a
3299
+ * validator: a stub `fetchImpl` plays the hostile relay, a stub `SettlementConnection` plays the
3300
+ * RPC. Its control flow is the caller's old inline network-retry loop, unchanged except where
3301
+ * noted below.
3302
+ *
3303
+ * THREE fixes live here, all of them the same root cause — trusting the counterparty:
3304
+ *
3305
+ * 1. REL-A-1 (root cause of REL-A-10): the request is signed ONCE, before the retry loop, and
3306
+ * every retry re-POSTs the byte-identical body. The relay's recovery contract is keyed on
3307
+ * `(auth_nonce, endpoint, sender, request_digest)` — `request_auth.rs::authenticate_relay_request`
3308
+ * — and `handle_transact` answers an exact replay from its durable row: `Completed` replays the
3309
+ * stored response, `Prepared` reconciles the stored signature against chain, `Processing`
3310
+ * returns a retryable 503. The shipped client re-signed with a FRESH `randomUUID()` nonce inside
3311
+ * the loop, so every retry was a NEW request that could only collide with its own predecessor's
3312
+ * nullifier reservation (503) — measured: 7 POSTs, 7 nonces, byte-identical business fields, and
3313
+ * the recovery path unreachable. Re-signing here is not a fallback: a nonce that has aged past
3314
+ * the relay's 300s freshness window is still accepted for an EXACT replay whose row exists, and
3315
+ * when no row exists nothing was mutated, so the 401 is both correct and safe.
3316
+ * 2. X-S-01B: a reported success is verified against chain before it is returned. The relay's word
3317
+ * plus its commitment indices are not evidence; the input nullifier PDAs are.
3318
+ * 3. REL-A-10 / (b): the signature the relay puts in a `submission_outcome_unknown` body is
3319
+ * captured across attempts, and every terminal failure that could plausibly have been submitted
3320
+ * is resolved against chain into a `SettlementVerificationError` that states the outcome and
3321
+ * carries the signature.
3322
+ */
3323
+ declare function submitTransactToRelay(args: SubmitTransactToRelayArgs): Promise<RelaySubmissionResult>;
3165
3324
  declare function transact(params: TransactParams, options: TransactOptions): Promise<TransactResult>;
3166
3325
  /**
3167
3326
  * Execute a shield-to-shield transfer
@@ -3226,14 +3385,33 @@ interface UtxoSwapResult extends TransactResult {
3226
3385
  nullifier: string;
3227
3386
  /** Relay request ID for swap execution status */
3228
3387
  requestId?: string;
3388
+ /**
3389
+ * V3-04 refund-fallback secret. **Persist this** to recover the swap principal if the swap times
3390
+ * out before TX2. On a timeout close the program builds the amount-bound note
3391
+ * R_fb = Poseidon(amountAfterFee, publicKey, blinding, WSOL)
3392
+ * where `amountAfterFee` is the principal it locked (read authoritatively from the on-chain
3393
+ * `SwapState.sol_amount`, = gross swap amount minus the on-chain swap fee). `ClaimRefund` then spends
3394
+ * `R_fb` with `privateKey` (a plain ZK membership proof of the refund tree). All values are hex.
3395
+ *
3396
+ * VK-02: when `derivedFromNk` is true this secret is ALSO recoverable from the wallet's `nk` plus
3397
+ * the swap's public first input nullifier — see `matchSwapRefundLeaf`. Persisting it is still the
3398
+ * fast path; losing it is no longer terminal. When false (no `nk` was supplied to the swap) this
3399
+ * object is the only copy that will ever exist.
3400
+ */
3401
+ refund: {
3402
+ privateKey: string;
3403
+ publicKey: string;
3404
+ blinding: string;
3405
+ derivedFromNk: boolean;
3406
+ };
3229
3407
  }
3230
3408
  /**
3231
3409
  * Execute a UTXO swap withdrawal
3232
3410
  *
3233
3411
  * This spends input UTXOs and creates a SwapState PDA for swapping SOL to SPL tokens.
3234
- * After this transaction, the relay can execute:
3235
- * 1. PrepareSwapSol - Wrap SOL to wSOL
3236
- * 2. ExecuteSwapViaOrca - Execute the swap on Orca
3412
+ * The swap is then completed on-chain in two follow-up instructions:
3413
+ * 1. PrepareSwapSol - Wrap the SOL held by the SwapState PDA into wSOL
3414
+ * 2. ExecuteSwap - Route the wSOL through Jupiter and deliver the output token to `recipientAta`
3237
3415
  *
3238
3416
  * @param params Swap parameters
3239
3417
  * @param options Transaction options
@@ -3255,49 +3433,743 @@ declare function swapUtxo(params: UtxoSwapParams, options: TransactOptions): Pro
3255
3433
  declare function swapWithChange(inputUtxos: Utxo[], swapAmount: bigint, outputMint: PublicKey, recipientAta: PublicKey, minOutputAmount: bigint, options: TransactOptions, recipientWallet?: PublicKey): Promise<UtxoSwapResult>;
3256
3434
 
3257
3435
  /**
3258
- * Compact deterministic chain note format (current format only).
3436
+ * Circuit artifact loading and integrity verification.
3437
+ *
3438
+ * Artifacts come from the pinned bundle in `config/circuits`, verified
3439
+ * by digest; no backend prover service is required. Proof generation itself
3440
+ * lives with the flows that own it (`flows/transact.ts`), which passes the
3441
+ * verified buffers this module returns straight to `snarkjs.groth16.fullProve`.
3442
+ *
3443
+ * The bytes are canonical HERE and copied out: every hand-off mints a fresh buffer from an
3444
+ * intrinsic captured at module load, and hashes a twin of that buffer, so the digest covers what is
3445
+ * used and not merely what was once loaded, and the buffer that is used was allocated by something
3446
+ * a consumer cannot redefine. See the note on `_verifiedArtifacts` for the break that made the copy
3447
+ * necessary (F7), and THE PRIMORDIALS for the one that made the copy's PROVENANCE necessary (F9).
3259
3448
  *
3260
- * Envelope: [version 1][ciphertext (timestamp + tag)]
3449
+ * Only the ceremony-frozen `transaction` circuit exists here. The deployed
3450
+ * program verifies proofs against that circuit's verifying key and nothing
3451
+ * else, so the pre-ceremony `withdraw_regular` / `withdraw_swap` circuits —
3452
+ * and every code path that loaded them — were removed rather than kept as
3453
+ * compatibility the program would reject anyway.
3261
3454
  */
3262
- type ChainNoteTxType = "deposit" | "withdraw" | "transfer" | "swap" | "unknown";
3263
- interface CompactChainNote {
3264
- timestamp: bigint;
3265
- commitment: string;
3266
- }
3455
+
3267
3456
  /**
3268
- * Encrypt a compact deterministic chain note.
3269
- * Payload is minimal to keep relay withdrawals under Solana packet size limits.
3270
- * Envelope: [version 1][ciphertext (timestamp + tag)]
3457
+ * Base URL of the only bundle this SDK pins: the ceremony `transaction` bundle.
3458
+ *
3459
+ * Identical to `DEFAULT_TRANSACTION_CIRCUITS_URL` in `flows/transact`; kept
3460
+ * under this name so existing imports keep resolving. Derived from
3461
+ * {@link TRANSACTION_CIRCUITS_BASE_URL}, so the version segment is the same one
3462
+ * the pinned digests were declared under — do not write this URL out by hand
3463
+ * anywhere; change the bundle instead.
3464
+ *
3465
+ * `string | null` — `null` when this build pins no location whose bytes were
3466
+ * verified against the bundle's digests.
3271
3467
  */
3272
- declare function encryptCompactChainNote(timestamp: bigint, nk: Uint8Array, commitmentHex: string): Promise<Uint8Array>;
3468
+ declare const DEFAULT_CIRCUITS_URL: string;
3273
3469
  /**
3274
- * Decrypt a compact deterministic chain note with candidate output commitments.
3275
- * Tries each commitment-derived key until AES-GCM authentication succeeds.
3470
+ * Pinned circuit artifact hashes (SHA-256), flattened from the release table in
3471
+ * `proving/circuits.ts`.
3472
+ *
3473
+ * A read-only VIEW of {@link PINNED_TRANSACTION_WASM_DIGEST} / {@link PINNED_TRANSACTION_ZKEY_DIGEST},
3474
+ * kept because it is part of the published surface and integrators print it in start-up
3475
+ * diagnostics. FROZEN: it is not the source of truth and must not be able to pretend it is. Writing
3476
+ * to it throws in strict mode (every ES module is strict) and is a silent no-op in sloppy CJS
3477
+ * scope — either way the digests the check uses are unchanged.
3276
3478
  */
3277
- declare function decryptCompactChainNote(noteBytes: Uint8Array, nk: Uint8Array, candidateCommitments: string[]): Promise<CompactChainNote>;
3278
- declare function chainNoteToBase64(noteBytes: Uint8Array): string;
3279
- declare function chainNoteFromBase64(base64: string): Uint8Array;
3280
-
3479
+ declare const EXPECTED_CIRCUIT_HASHES: Readonly<{
3480
+ transaction_wasm: string;
3481
+ transaction_zkey: string;
3482
+ }>;
3281
3483
  /**
3282
- * Transaction Scanner
3283
- *
3284
- * Scans on-chain transactions for the Cloak shield pool program,
3285
- * extracts chain note envelopes from instruction data, and
3286
- * trial-decrypts them with the caller's viewing key.
3287
- *
3288
- * This module is fully autonomous — it talks directly to a Solana
3289
- * RPC node and does not depend on the relay.
3484
+ * Circuit verification result
3290
3485
  */
3291
-
3292
- /** A single decoded & verified transaction record. */
3293
- interface ScannedTransaction {
3294
- /** Transaction type (deposit / withdraw / transfer / swap) */
3295
- txType: ChainNoteTxType;
3296
- /** Absolute amount in lamports (gross — the full amount leaving the pool) */
3297
- amount: bigint;
3298
- /** Protocol fee in lamports (non-zero only for withdrawals/swaps) */
3299
- fee: bigint;
3300
- /** Net amount received by the recipient (amount - fee). For deposits this equals amount. */
3486
+ interface CircuitVerificationResult {
3487
+ /** Whether verification passed */
3488
+ valid: boolean;
3489
+ /** Error message if verification failed */
3490
+ error?: string;
3491
+ computed?: {
3492
+ wasm: string;
3493
+ zkey: string;
3494
+ };
3495
+ expected?: {
3496
+ wasm: string;
3497
+ zkey: string;
3498
+ };
3499
+ }
3500
+ /**
3501
+ * Circuit artifact bytes together with the digests computed over those bytes.
3502
+ *
3503
+ * The buffers are a PRIVATE COPY, minted for this call from an intrinsic captured at module load
3504
+ * and hashed via a throwaway twin after they were copied. Nothing outside this module was handed
3505
+ * the object or a view onto it, so `digests` describes `wasm`/`zkey` as of the moment they were
3506
+ * handed over rather than as of some earlier load. Mutate them if you like; the SDK's canonical
3507
+ * bytes are elsewhere and the next call re-copies and re-hashes.
3508
+ *
3509
+ * The guarantee stops at the hand-off, and stops honestly. Once these buffers are inside
3510
+ * `snarkjs.groth16.fullProve` what snarkjs does with them is snarkjs's business; what this module
3511
+ * promises is that the bytes it handed over are the bytes whose SHA-256 it reported.
3512
+ */
3513
+ interface VerifiedCircuitArtifacts {
3514
+ /** `<circuit>_js/<circuit>.wasm` bytes. A fresh copy, not shared with any other caller. */
3515
+ wasm: Uint8Array;
3516
+ /** `<circuit>_final.zkey` bytes. A fresh copy, not shared with any other caller. */
3517
+ zkey: Uint8Array;
3518
+ /** SHA-256 (lowercase hex) of the buffers in this object, computed over these very buffers. */
3519
+ digests: {
3520
+ wasm: string;
3521
+ zkey: string;
3522
+ };
3523
+ }
3524
+ /**
3525
+ * Load circuit artifacts and return the very bytes whose digest was checked.
3526
+ *
3527
+ * This is the only supported way to obtain proving artifacts: the caller passes
3528
+ * the returned buffers straight to `snarkjs.groth16.fullProve`, which accepts a
3529
+ * `Uint8Array` for both the wasm and the zkey. Passing snarkjs a URL or a file
3530
+ * path instead would re-read the artifact independently of the digest check, so
3531
+ * a CDN (or a concurrent writer on disk) could serve good bytes to the check and
3532
+ * different bytes to the prover.
3533
+ *
3534
+ * Fails closed: any digest mismatch, unreachable artifact, or environment that
3535
+ * cannot produce bytes (a browser pointed at a local directory) throws rather
3536
+ * than falling back to an unverified source.
3537
+ *
3538
+ * Returns a FRESH COPY on every call, hashed after it was copied — see
3539
+ * {@link copyAndVerify} and the note on {@link _verifiedArtifacts}. Two calls never share a buffer,
3540
+ * and the digests in the returned object are the digests of the buffers in that same object, taken
3541
+ * at that call. Mutating what you are given affects nothing but your own copy.
3542
+ *
3543
+ * The environment cannot switch any of this off. It used to be skippable with
3544
+ * `CLOAK_SKIP_CIRCUIT_INTEGRITY_CHECK=1`, and that escape hatch was the worst hole in the package:
3545
+ * the variable resolves in the CONSUMER's process, so one deploy variable — or one
3546
+ * `--define:process.env.CLOAK_SKIP_CIRCUIT_INTEGRITY_CHECK='"1"'` in a consumer's bundler, which
3547
+ * compiles the guard down to a constant `true` — made this function accept whatever bytes the base
3548
+ * served. Paired with a repointable base that meant attacker wasm, and attacker wasm sees the spend
3549
+ * key. A check an environment variable disables is not a check. If a local build genuinely needs
3550
+ * different artifacts it needs different DIGESTS, which is a source edit and a rebuild, exactly
3551
+ * like the endpoint pin.
3552
+ */
3553
+ declare function loadVerifiedCircuitArtifacts(circuitsPath: string | null): Promise<VerifiedCircuitArtifacts>;
3554
+ /**
3555
+ * Report whether a circuit's artifacts match the digests pinned in this SDK.
3556
+ *
3557
+ * This is a *reporting* helper (used by `verifyAllCircuits` for start-up checks
3558
+ * and diagnostics). It is NOT what gates proving: a check that only inspects the
3559
+ * source cannot say anything about the bytes a later, independent read returns.
3560
+ * Proof paths must call {@link loadVerifiedCircuitArtifacts} and hand the bytes
3561
+ * it returns to snarkjs.
3562
+ *
3563
+ * Reports digests, never bytes. It can read the canonical memo to avoid a second download, but the
3564
+ * result object carries only hex strings, so calling this cannot obtain a handle on the bytes a
3565
+ * later proof will run over — which is the other half of the F7 fix, and the reason this function
3566
+ * and `loadVerifiedCircuitArtifacts` can no longer disagree about the same cache entry.
3567
+ *
3568
+ * IMPORTANT: If the hashes don't match, the circuit may produce proofs
3569
+ * that will be rejected by the on-chain verifier!
3570
+ *
3571
+ * @param circuitsPath - Base directory or URL holding the circuit's artifacts;
3572
+ * honoured verbatim.
3573
+ * @param circuit - Which circuit to verify
3574
+ * @returns Verification result
3575
+ *
3576
+ * @example
3577
+ * ```typescript
3578
+ * const result = await verifyCircuitIntegrity(getCircuitsPath(), 'transaction');
3579
+ * if (!result.valid) {
3580
+ * console.error('Circuit verification failed:', result.error);
3581
+ * // Don't proceed with proof generation!
3582
+ * }
3583
+ * ```
3584
+ */
3585
+ declare function verifyCircuitIntegrity(circuitsPath: string | null, prefetched?: {
3586
+ wasm: Uint8Array;
3587
+ zkey: Uint8Array;
3588
+ }): Promise<CircuitVerificationResult>;
3589
+ /**
3590
+ * Assert the ceremony-frozen `transaction` circuit artifacts are the pinned ones.
3591
+ *
3592
+ * Throws (fail-closed) when the digests do not match. Proving against an
3593
+ * unpinned zkey silently produces proofs the on-chain verifying key rejects,
3594
+ * so failing here is strictly better than failing on-chain.
3595
+ *
3596
+ * @param circuitsPath - Base directory or URL holding `transaction_js/transaction.wasm`
3597
+ * and `transaction_final.zkey`.
3598
+ * @param prefetched - Already-downloaded artifact bytes, to avoid a second fetch.
3599
+ */
3600
+ declare function assertTransactionCircuitIntegrity(circuitsPath: string | null, prefetched?: {
3601
+ wasm: Uint8Array;
3602
+ zkey: Uint8Array;
3603
+ }): Promise<void>;
3604
+ /**
3605
+ * Verify every circuit this SDK pins — which is exactly one: the ceremony
3606
+ * `transaction` circuit.
3607
+ *
3608
+ * Call this at SDK initialization to ensure circuits are valid.
3609
+ *
3610
+ * @param circuitsPath - Base for the ceremony-frozen `transaction` circuit.
3611
+ * Pass `getCircuitsPath()` when the caller has reconfigured it;
3612
+ * `null` reports the "no base configured" state rather than throwing.
3613
+ * @param transactionCircuitsPath - Same base; takes precedence when given. Kept so
3614
+ * two-argument callers from before the legacy withdraw
3615
+ * circuits were removed keep compiling, with unchanged
3616
+ * behaviour for the `transaction` entry.
3617
+ * @returns Array of verification results (one per circuit)
3618
+ */
3619
+ declare function verifyAllCircuits(circuitsPath: string | null, transactionCircuitsPath?: string | null): Promise<CircuitVerificationResult[]>;
3620
+
3621
+ /**
3622
+ * Pending Operations Manager
3623
+ *
3624
+ * Utility for persisting pending deposit/withdrawal operations in browser storage.
3625
+ * This enables recovery if the browser crashes or user navigates away mid-operation.
3626
+ *
3627
+ * IMPORTANT: This uses localStorage by default which has security implications.
3628
+ * Notes contain sensitive spending keys - consider using more secure storage
3629
+ * in production (e.g., encrypted IndexedDB, secure enclave).
3630
+ */
3631
+
3632
+ /**
3633
+ * Pending deposit record
3634
+ */
3635
+ interface PendingDeposit {
3636
+ /** The note (contains spending secrets!) */
3637
+ note: CloakNote;
3638
+ /** When the deposit was initiated */
3639
+ startedAt: number;
3640
+ /** Transaction signature if available */
3641
+ txSignature?: string;
3642
+ /** Status of the deposit */
3643
+ status: "pending" | "tx_sent" | "confirmed" | "failed";
3644
+ /** Error message if failed */
3645
+ error?: string;
3646
+ }
3647
+ /**
3648
+ * Pending withdrawal record
3649
+ */
3650
+ interface PendingWithdrawal {
3651
+ /** The relay request ID (for resumption) */
3652
+ requestId: string;
3653
+ /** The note commitment being withdrawn */
3654
+ commitment: string;
3655
+ /** The nullifier being used */
3656
+ nullifier: string;
3657
+ /** When the withdrawal was initiated */
3658
+ startedAt: number;
3659
+ /** Status of the withdrawal */
3660
+ status: "pending" | "processing" | "completed" | "failed";
3661
+ /** Transaction signature if completed */
3662
+ txSignature?: string;
3663
+ /** Error message if failed */
3664
+ error?: string;
3665
+ }
3666
+ /**
3667
+ * Save a pending deposit
3668
+ * Call this BEFORE sending the on-chain transaction to ensure note is persisted
3669
+ */
3670
+ declare function savePendingDeposit(deposit: PendingDeposit): void;
3671
+ /**
3672
+ * Load all pending deposits
3673
+ */
3674
+ declare function loadPendingDeposits(): PendingDeposit[];
3675
+ /**
3676
+ * Update a pending deposit status
3677
+ */
3678
+ declare function updatePendingDeposit(commitment: string, updates: Partial<PendingDeposit>): void;
3679
+ /**
3680
+ * Remove a pending deposit (e.g., after successful confirmation)
3681
+ */
3682
+ declare function removePendingDeposit(commitment: string): void;
3683
+ /**
3684
+ * Clear all pending deposits
3685
+ */
3686
+ declare function clearPendingDeposits(): void;
3687
+ /**
3688
+ * Save a pending withdrawal
3689
+ * Call this when you receive the request_id from the relay
3690
+ */
3691
+ declare function savePendingWithdrawal(withdrawal: PendingWithdrawal): void;
3692
+ /**
3693
+ * Load all pending withdrawals
3694
+ */
3695
+ declare function loadPendingWithdrawals(): PendingWithdrawal[];
3696
+ /**
3697
+ * Update a pending withdrawal status
3698
+ */
3699
+ declare function updatePendingWithdrawal(requestId: string, updates: Partial<PendingWithdrawal>): void;
3700
+ /**
3701
+ * Remove a pending withdrawal (e.g., after successful completion)
3702
+ */
3703
+ declare function removePendingWithdrawal(requestId: string): void;
3704
+ /**
3705
+ * Clear all pending withdrawals
3706
+ */
3707
+ declare function clearPendingWithdrawals(): void;
3708
+ /**
3709
+ * Check if there are any pending operations that need recovery
3710
+ * Call this on page load to determine if recovery UI should be shown
3711
+ */
3712
+ declare function hasPendingOperations(): boolean;
3713
+ /**
3714
+ * Get summary of pending operations for recovery UI
3715
+ */
3716
+ declare function getPendingOperationsSummary(): {
3717
+ deposits: PendingDeposit[];
3718
+ withdrawals: PendingWithdrawal[];
3719
+ totalPending: number;
3720
+ };
3721
+ /**
3722
+ * Clean up stale pending operations
3723
+ * Call this periodically to remove old failed/completed operations
3724
+ *
3725
+ * @param maxAgeMs Maximum age in milliseconds before an operation is removed (default: 24 hours)
3726
+ */
3727
+ declare function cleanupStalePendingOperations(maxAgeMs?: number): {
3728
+ removedDeposits: number;
3729
+ removedWithdrawals: number;
3730
+ };
3731
+
3732
+ /**
3733
+ * Recipient-addressed delivery envelope (CLKD1).
3734
+ *
3735
+ * ── The defect this closes ────────────────────────────────────────────────────────────────────
3736
+ * A shield-to-shield send creates an output note OWNED BY THE RECIPIENT. The only discovery
3737
+ * artefact the SDK used to publish for it was the CLK1 compliance chain note, which is encrypted
3738
+ * under the SENDER's `nk` and keyed (HKDF salt) by the output commitment. That note is openable by
3739
+ * the sender and by nobody else — so the recipient's money sat on chain, valid and spendable, with
3740
+ * its owner unable to see it (campaign rows S2S-08 / VK-01).
3741
+ *
3742
+ * This envelope is the recipient's half. It is encrypted to the RECIPIENT's X25519 public viewing
3743
+ * key — the one derived by `deriveViewingKeyFromNk(nk)` — so a cold scan holding nothing but
3744
+ * `(rpc, programId, nk)` can open it. That triple is exactly the cold-scan contract VK-01 tested.
3745
+ *
3746
+ * ── Wire format — dictated by the relay, not by us ────────────────────────────────────────────
3747
+ * `services/relay/src/api/transact.rs:176-205` accepts EXACTLY ONE base64 envelope of EXACTLY
3748
+ * 112 bytes and only on a shield-to-shield send (`public_amount == 0`); anything else is a 400 and
3749
+ * no carrier is written. `services/relay/src/solana/mod.rs:2338` then publishes
3750
+ *
3751
+ * "CLKD1" || hex(output_commitment[0]) || hex(envelope)
3752
+ *
3753
+ * as an SPL Memo in a transaction that touches the PDA at seed `b"cloak_delivery_registry"`, which
3754
+ * is what makes it enumerable via `getSignaturesForAddress`.
3755
+ *
3756
+ * The 112 bytes are:
3757
+ *
3758
+ * [0 ..32) ephemeral X25519 public key
3759
+ * [32 ..56) 24-byte XSalsa20-Poly1305 nonce
3760
+ * [56..112) 56-byte ciphertext = 40-byte plaintext + 16-byte Poly1305 tag
3761
+ *
3762
+ * and the 40-byte plaintext is `amount(u64 LE) || blinding(u256 BE)` — precisely what a recipient
3763
+ * needs, alongside their own keypair and the pool mint, to recompute the commitment and spend it.
3764
+ * Amount is LE to match every other u64 the SDK writes (`chain-note.ts`, public inputs); blinding
3765
+ * is BE to match `bigintToBytes32` and the on-chain field-element convention.
3766
+ *
3767
+ * ── Crypto ────────────────────────────────────────────────────────────────────────────────────
3768
+ * X25519 ECDH + XSalsa20-Poly1305, i.e. `nacl.box`, reusing the exact construction already in
3769
+ * `notes/keypair.ts` (`nacl.box.before` + `nacl.secretbox`). No new primitive is introduced here.
3770
+ */
3771
+
3772
+ /** Ephemeral X25519 public key, at offset 0. */
3773
+ declare const RECIPIENT_DELIVERY_EPHEMERAL_PK_LEN = 32;
3774
+ /** XSalsa20-Poly1305 nonce, immediately after the ephemeral key. */
3775
+ declare const RECIPIENT_DELIVERY_NONCE_LEN = 24;
3776
+ /** amount(8) + blinding(32) — everything the owner needs to rebuild and spend the note. */
3777
+ declare const RECIPIENT_DELIVERY_PLAINTEXT_LEN = 40;
3778
+ /** Poly1305 authentication tag. */
3779
+ declare const RECIPIENT_DELIVERY_TAG_LEN = 16;
3780
+ /** Sealed payload = plaintext + tag. */
3781
+ declare const RECIPIENT_DELIVERY_CIPHERTEXT_LEN: number;
3782
+ /**
3783
+ * Total envelope size. The relay rejects any other length outright
3784
+ * (`RECIPIENT_DELIVERY_NOTE_BYTES` in `services/relay/src/api/transact.rs`), so this constant is a
3785
+ * contract with a shipped binary, not a preference.
3786
+ */
3787
+ declare const RECIPIENT_DELIVERY_NOTE_BYTES: number;
3788
+ /** PDA seed the relay derives the carrier's registry account from. */
3789
+ declare const DELIVERY_REGISTRY_SEED = "cloak_delivery_registry";
3790
+ /** ASCII tag prefixing the carrier memo payload. */
3791
+ declare const DELIVERY_MEMO_TAG = "CLKD1";
3792
+ /** The spendable contents of a delivery envelope. */
3793
+ interface RecipientDeliveryNote {
3794
+ /** Note amount in the pool mint's smallest unit. */
3795
+ amount: bigint;
3796
+ /** Note blinding factor (BN254 field element). */
3797
+ blinding: bigint;
3798
+ }
3799
+ /**
3800
+ * Seal `{amount, blinding}` to a recipient's X25519 public viewing key.
3801
+ *
3802
+ * Every input is length-checked here rather than at the relay: a malformed envelope costs a
3803
+ * confirmed transaction's worth of latency before the 400 comes back, and the send has already
3804
+ * generated its proof by then.
3805
+ */
3806
+ declare function encodeRecipientDeliveryNote(note: RecipientDeliveryNote, recipientViewingPublicKey: Uint8Array): Uint8Array;
3807
+ /**
3808
+ * Trial-open an envelope with a viewing secret. Returns `null` — never throws — when the envelope
3809
+ * is not ours, because a scanner runs this against every carrier in the registry.
3810
+ */
3811
+ declare function openRecipientDeliveryNote(envelope: Uint8Array, viewingSecretKey: Uint8Array): RecipientDeliveryNote | null;
3812
+ declare function recipientDeliveryNoteToBase64(envelope: Uint8Array): string;
3813
+ /** Parameters the send path already has in hand when it assembles the relay body. */
3814
+ interface BuildRecipientDeliveryNotesParams {
3815
+ /** Signed public amount. Zero — and only zero — is a shield-to-shield send. */
3816
+ externalAmount: bigint;
3817
+ /** External withdrawal recipient, if any. A send has none. */
3818
+ recipient?: PublicKey | null;
3819
+ /** The recipient's 32-byte X25519 public viewing key (`deriveViewingKeyFromNk(nk).publicKey`). */
3820
+ recipientViewingPublicKey?: Uint8Array | string | null;
3821
+ /**
3822
+ * The output note being delivered. MUST be output 0: the relay binds the carrier to
3823
+ * `public_inputs.output_commitments[0]` (`services/relay/src/api/transact.rs:1015`).
3824
+ */
3825
+ note?: Pick<Utxo, "amount" | "blinding"> | null;
3826
+ /** UTXO public key that owns `note` (i.e. `paddedOutputs[0].keypair.publicKey`). */
3827
+ noteOwnerPublicKey?: bigint;
3828
+ /** UTXO public key doing the spending (i.e. `paddedInputs[0].keypair.publicKey`). */
3829
+ spenderPublicKey?: bigint;
3830
+ }
3831
+ /**
3832
+ * Build the `recipient_delivery_notes` field for `/transact`, or `undefined` when the rail does not
3833
+ * apply. Returning `undefined` (rather than an empty array) matters: the relay's signed field view
3834
+ * treats omitted and null identically, and an empty array on a withdrawal would still be a 400.
3835
+ */
3836
+ declare function buildRecipientDeliveryNotes(params: BuildRecipientDeliveryNotesParams): string[] | undefined;
3837
+ /** Render the carrier memo byte-for-byte as `emit_recipient_delivery_carrier` does. */
3838
+ declare function encodeDeliveryCarrierMemo(outputCommitment: bigint | Uint8Array, envelope: Uint8Array): Uint8Array;
3839
+ interface ParsedDeliveryCarrier {
3840
+ /** Lowercase 64-char hex of the output commitment the carrier declares. */
3841
+ commitment: string;
3842
+ /** The raw 112-byte envelope. */
3843
+ note: Uint8Array;
3844
+ }
3845
+ /**
3846
+ * Parse one SPL Memo payload. Returns `null` for anything that is not a well-formed CLKD1 carrier —
3847
+ * the memo program accepts arbitrary UTF-8 from anyone, so this must fail closed on exact lengths
3848
+ * (I-12: exact `!==` gates, never `<`).
3849
+ */
3850
+ declare function parseDeliveryCarrierMemo(data: Uint8Array): ParsedDeliveryCarrier | null;
3851
+
3852
+ /**
3853
+ * Recoverable deposit notes (VK-01, deposit shape).
3854
+ *
3855
+ * ── The defect this closes ────────────────────────────────────────────────────────────────────
3856
+ * A cold scan holding exactly `(rpc, programId, nk)` — the contract the SDK advertises for viewing
3857
+ * keys — found the WITHDRAWAL change note and did NOT find the DEPOSIT. The control that makes the
3858
+ * miss attributable is the withdrawal: the same scanner, the same 300-signature window, the same
3859
+ * key, one shape found and one not.
3860
+ *
3861
+ * Two things were missing, and only one of them was visible from the failing arm:
3862
+ *
3863
+ * 1. `outPubkey0`. The deposit's chain note is v3 (timestamp + noteSalt), and `chainNoteHash` binds
3864
+ * `noteSemantics = Poseidon(outAmount0, outPubkey0, isSendToSelfKey0)`. `outPubkey0` is the
3865
+ * output note's OWNER key, which is not derivable from `nk` — the key hierarchy runs
3866
+ * `skSpend → nk` through BLAKE3 and does not run backwards. So the scanner could decrypt the
3867
+ * note and then had to drop it, because the hash it recomputed could never match. That is why
3868
+ * the widened arm needed an undocumented `ownUtxoPublicKey`: it was feeding the scanner the one
3869
+ * value the advertised contract does not carry.
3870
+ * 2. The BLINDING. Even with `ownUtxoPublicKey` the deposit is only VISIBLE, not RECOVERABLE — the
3871
+ * blinding came from `randomFieldElement()` inside `createUtxo` and is written nowhere. A note
3872
+ * you can see and cannot spend is not a recovered note.
3873
+ *
3874
+ * ── Why derivation, and not another envelope ──────────────────────────────────────────────────
3875
+ * The shielded-send shape was fixed with a CLKD1 delivery envelope because a send's output is owned
3876
+ * by SOMEONE ELSE: the only way to reach them is to encrypt to their key. A deposit's output is
3877
+ * SELF-owned. There is nobody to deliver to, and an envelope would cost 112 bytes on the one
3878
+ * transaction in the protocol that is wallet-signed and already tight against the 1232-byte packet
3879
+ * limit (a v4 chain note's extra 41 bytes is measured at 1282 and is exactly why deposits stay v3).
3880
+ *
3881
+ * So make the existing chain-note path recoverable instead. Both missing values become a PRF of the
3882
+ * wallet's own `nk` and the note salt the chain note ALREADY carries:
3883
+ *
3884
+ * seed = BLAKE3("cloak_deposit_note_v1" || nk || noteSalt(32B BE) || info)
3885
+ * privateKey = seed("sk") reduced into the field
3886
+ * blinding = seed("blinding") reduced into the field
3887
+ * publicKey = PoseidonEx(privateKey, KEYPAIR_) [L-02, matches keypair.circom]
3888
+ *
3889
+ * A cold scanner decrypts the chain note with `nk` (AES-GCM, HKDF-salted by the output commitment),
3890
+ * reads `noteSalt` out of the plaintext, replays those three lines, recomputes
3891
+ * `Poseidon(amount, publicKey, blinding, mint)` and requires it to equal a commitment the
3892
+ * transaction actually published. That equality is the authentication: it is not a heuristic, and a
3893
+ * wrong `nk` cannot produce it. Zero extra bytes on chain, no new envelope, no relay change.
3894
+ *
3895
+ * This is the same shape already blessed for swap timeout refunds in `notes/swap-refund.ts`
3896
+ * (PRF(nk, nullifier0)), for the same reason: unrecoverable randomness becomes recoverable
3897
+ * randomness without changing anything an observer can see.
3898
+ *
3899
+ * ── The consequence to be explicit about ──────────────────────────────────────────────────────
3900
+ * The deposit note is owned by a PER-DEPOSIT key rather than by the wallet's single long-lived UTXO
3901
+ * keypair. It is still fully spendable — the wallet re-derives `privateKey` from `nk` whenever it
3902
+ * needs it — and it is strictly better for privacy, because `outPubkey0` no longer links a wallet's
3903
+ * deposits to each other. But it means the caller MUST persist (or be able to re-derive) `nk`, which
3904
+ * a Cloak wallet already does, and it means `transact` must be given an explicit `nk` rather than
3905
+ * inferring one from the output note's own key. `transact` enforces that rather than trusting it.
3906
+ *
3907
+ * ── Guardrails preserved ──────────────────────────────────────────────────────────────────────
3908
+ * [L-02] the public key comes from the capacity-tagged `derivePublicKey`. [H-04-shaped] a zero
3909
+ * public key or blinding would be an unspendable note, so the reduction forces non-zero and the
3910
+ * derivation refuses to return a degenerate pair. [M-04] `noteSalt` stays a private input to
3911
+ * `chainNoteHash`; it is only ever published inside the note's own authenticated ciphertext.
3912
+ */
3913
+
3914
+ /**
3915
+ * The chain note salt is 96 bits — the circuit constrains it with `Num2Bits(96)` and `transact`
3916
+ * generates exactly 12 bytes. A salt outside that range would produce a proof the circuit rejects,
3917
+ * so it is refused here rather than at proof time.
3918
+ */
3919
+ declare const CHAIN_NOTE_SALT_BITS = 96;
3920
+ /** The secrets a deposit note is built from — and the ones a cold scan re-derives. */
3921
+ interface DepositNoteSecrets {
3922
+ keypair: UtxoKeypair;
3923
+ blinding: bigint;
3924
+ }
3925
+ /** A deposit note recovered from `nk` alone, in spendable form. */
3926
+ interface RecoveredDepositNote extends DepositNoteSecrets {
3927
+ amount: bigint;
3928
+ mintAddress: PublicKey;
3929
+ /** The commitment the transaction published, reproduced from the derived secrets. */
3930
+ commitment: bigint;
3931
+ /** The salt the chain note carried, which anchored the derivation. */
3932
+ noteSalt: bigint;
3933
+ }
3934
+ /** A fresh 96-bit chain-note salt, from the same fail-closed source `transact` uses. */
3935
+ declare function randomDepositNoteSalt(): bigint;
3936
+ /**
3937
+ * Derive a deposit note's keypair and blinding from `(nk, noteSalt)`.
3938
+ *
3939
+ * Deterministic by design: this is the whole reason a cold scan can rebuild the note. Both the
3940
+ * builder and the scanner call it, so there is exactly one definition of what a deposit note is.
3941
+ */
3942
+ declare function deriveDepositNoteSecrets(viewingKeyNk: Uint8Array, noteSalt: bigint): Promise<DepositNoteSecrets>;
3943
+ /**
3944
+ * Build a deposit output note that a cold `(rpc, programId, nk)` scan can recover.
3945
+ *
3946
+ * Returns the UTXO to pass as `outputUtxos[0]` AND the salt that anchored it. The SAME salt must be
3947
+ * handed to `transact` as `options.chainNoteSalt`, because the chain note is what publishes it — a
3948
+ * salt that does not reach the note leaves the deposit exactly as undiscoverable as before.
3949
+ *
3950
+ * ```ts
3951
+ * const { utxo, noteSalt } = await createRecoverableDepositUtxo(amount, nk, mint);
3952
+ * await transact({ ..., outputUtxos: [utxo], externalAmount: amount },
3953
+ * { chainNoteViewingKeyNk: nk, chainNoteSalt: noteSalt, ... });
3954
+ * ```
3955
+ */
3956
+ declare function createRecoverableDepositUtxo(amount: bigint, viewingKeyNk: Uint8Array, mintAddress?: PublicKey, noteSalt?: bigint): Promise<{
3957
+ utxo: Utxo;
3958
+ noteSalt: bigint;
3959
+ }>;
3960
+ interface MatchDepositNoteParams {
3961
+ /** The scanning wallet's incoming viewing base. */
3962
+ viewingKeyNk: Uint8Array;
3963
+ /** `noteSalt`, read out of the decrypted chain note. */
3964
+ noteSalt: bigint;
3965
+ /** Candidate note amount — for a shield with no inputs this is the public deposit amount. */
3966
+ amount: bigint;
3967
+ /** Pool mint the commitment was computed under. */
3968
+ mintAddress: PublicKey;
3969
+ /** Output commitments the transaction actually published, hex or field elements. */
3970
+ outputCommitments: Array<string | bigint>;
3971
+ }
3972
+ /**
3973
+ * Decide whether a deposit's published commitment is one this `nk` can rebuild, and if so return
3974
+ * the note in spendable form. Returns `null` for everything that is not ours.
3975
+ *
3976
+ * The commitment equality is the authentication. Nothing here trusts the chain note's own claim
3977
+ * about what it describes; the note only supplies `noteSalt`, and the derived secrets have to
3978
+ * reproduce a value the transaction published or the candidate is discarded.
3979
+ */
3980
+ declare function matchDepositNote(params: MatchDepositNoteParams): Promise<RecoveredDepositNote | null>;
3981
+
3982
+ /**
3983
+ * Swap timeout-refund discovery (VK-02).
3984
+ *
3985
+ * ── The defect this closes ────────────────────────────────────────────────────────────────────
3986
+ * When a private swap exhausts its retry budget, `CloseSwapState` appends
3987
+ *
3988
+ * R_fb = Poseidon(amount_after_fee, refund_pubkey, refund_blinding, field(WSOL))
3989
+ *
3990
+ * to the main pool tree. `swapUtxo` generated `refund_pubkey` / `refund_blinding` from raw
3991
+ * randomness and returned them only in the in-memory `UtxoSwapResult.refund`. Lose that object —
3992
+ * a crashed tab, a different device, a scan from cold key material — and the leaf is real, funded,
3993
+ * and permanently unrecoverable: campaign row VK-02 observed R_fb under BOTH the refund note's own
3994
+ * viewing key and the swap creator's, and found nothing under either.
3995
+ *
3996
+ * ── The fix, and why it is SDK-only ───────────────────────────────────────────────────────────
3997
+ * Derive the refund authorization as a PRF of the owner's `nk` and the swap's own first input
3998
+ * nullifier:
3999
+ *
4000
+ * seed = BLAKE3("cloak_swap_refund_v1" || nk || nullifier0)
4001
+ *
4002
+ * `nullifier0` is published in the TransactSwap public inputs, so a cold scanner enumerating
4003
+ * program transactions can replay this derivation for every swap it sees, recompute R_fb, and
4004
+ * match it against the commitment `CloseSwapState` declared. `nk` is secret, so no third party can
4005
+ * predict, front-run or link the refund key — the on-chain artefacts are unchanged in shape and
4006
+ * every existing binding still holds.
4007
+ *
4008
+ * This changes NOTHING on chain or in the relay. `refund_pubkey` / `refund_blinding` remain
4009
+ * caller-supplied values bound into `computeSwapExtDataHash`; only their provenance changes, from
4010
+ * "unrecoverable randomness" to "recoverable randomness".
4011
+ *
4012
+ * ── Guardrails preserved ──────────────────────────────────────────────────────────────────────
4013
+ * [H-04 / DD-01] the derivation is rejected unless publicKey and blinding are both non-zero, and
4014
+ * it is unique per swap because `nullifier0` is unique per spend. [L-02] the public key comes from
4015
+ * the capacity-tagged `derivePublicKey`, matching `keypair.circom`.
4016
+ *
4017
+ * ── FORWARD-LOOKING ONLY ──────────────────────────────────────────────────────────────────────
4018
+ * Everything here — including `discoverSwapRefunds`, the RPC walker at the bottom of this file —
4019
+ * recovers only refunds whose authorization was DERIVED. Swaps already on chain whose refund keypair
4020
+ * and blinding came from raw randomness stay unrecoverable, permanently, by any key-only scan. There
4021
+ * is nothing to replay: those secrets existed only in the caller's in-memory `UtxoSwapResult.refund`.
4022
+ * Do not let this note get softened — a wallet that reports "no stranded refunds" on the strength of
4023
+ * an empty scan would be making a claim this code cannot support.
4024
+ */
4025
+
4026
+ /** A refund authorization: what `swapUtxo` binds into the swap ext-data hash. */
4027
+ interface SwapRefundAuthorization {
4028
+ privateKey: bigint;
4029
+ publicKey: bigint;
4030
+ blinding: bigint;
4031
+ }
4032
+ /** A refund leaf matched back to its owner, ready to spend. */
4033
+ interface RecoveredSwapRefund {
4034
+ keypair: UtxoKeypair;
4035
+ blinding: bigint;
4036
+ amount: bigint;
4037
+ /** R_fb as a field element, identical to the commitment `CloseSwapState` appended. */
4038
+ commitment: bigint;
4039
+ }
4040
+ /**
4041
+ * Derive a swap's refund authorization from the owner's `nk` and the swap's first input nullifier.
4042
+ *
4043
+ * Deterministic by design: this is what makes the refund leaf recoverable from key material alone.
4044
+ */
4045
+ declare function deriveSwapRefundAuthorization(viewingKeyNk: Uint8Array, inputNullifier: Uint8Array | bigint): Promise<SwapRefundAuthorization>;
4046
+ /**
4047
+ * R_fb, exactly as `CloseSwapState` computes it. The swap principal is always WSOL-locked, so the
4048
+ * mint term is the native-SOL sentinel.
4049
+ */
4050
+ declare function computeSwapRefundCommitment(amountAfterFee: bigint, refundPublicKey: bigint, refundBlinding: bigint): Promise<bigint>;
4051
+ interface MatchSwapRefundLeafParams {
4052
+ /** The scanning wallet's incoming viewing base. */
4053
+ viewingKeyNk: Uint8Array;
4054
+ /** First input nullifier of the candidate swap, read from its TransactSwap public inputs. */
4055
+ inputNullifier: Uint8Array | bigint;
4056
+ /** Principal returned to the pool, from the `cloak/refund_leaf/v1` event. */
4057
+ amountAfterFee: bigint;
4058
+ /** R_fb as appended on chain. */
4059
+ commitment: bigint | Uint8Array;
4060
+ }
4061
+ /**
4062
+ * Decide whether a public refund leaf belongs to the holder of `viewingKeyNk`, and if so return it
4063
+ * in spendable form. Returns `null` for every leaf that is not ours — a scanner runs this against
4064
+ * every close it can see.
4065
+ */
4066
+ declare function matchSwapRefundLeaf(params: MatchSwapRefundLeafParams): Promise<RecoveredSwapRefund | null>;
4067
+ /** One recovered refund leaf, with the chain coordinates that produced it. */
4068
+ interface DiscoveredSwapRefund extends RecoveredSwapRefund {
4069
+ /** Signature of the `CloseSwapState` transaction that appended the leaf. */
4070
+ signature: string;
4071
+ /** Leaf index, straight from the `cloak/refund_leaf/v1` event. */
4072
+ leafIndex: bigint;
4073
+ /** First input nullifier of the swap this refund belongs to (the PRF's second input). */
4074
+ inputNullifier: Uint8Array;
4075
+ /** Signature of the `TransactSwap` that opened the swap, when it was inside the scanned window. */
4076
+ swapSignature?: string;
4077
+ }
4078
+ interface DiscoverSwapRefundsOptions {
4079
+ /** Maximum program signatures to walk. Omit or 0 to walk the whole history. */
4080
+ limit?: number;
4081
+ /** Stop when this signature is reached (exclusive) — the cursor from a previous run. */
4082
+ untilSignature?: string;
4083
+ /** `getTransaction` concurrency (default 50). */
4084
+ batchSize?: number;
4085
+ /** Progress/status callback. */
4086
+ onStatus?: (status: string) => void;
4087
+ /**
4088
+ * Pool mint whose `swap_state` PDAs to derive. Swap input is wSOL-locked, so the default is the
4089
+ * native-SOL sentinel and there is no reason to change it outside a test.
4090
+ */
4091
+ poolMint?: PublicKey;
4092
+ }
4093
+ /**
4094
+ * Find every swap timeout-refund leaf on chain that belongs to the holder of `viewingKeyNk`, and
4095
+ * return each in spendable form (`keypair`, `blinding`, `amount`, `commitment`).
4096
+ *
4097
+ * Key material only — no relay, no local note store, no prior knowledge of the swap. This is the
4098
+ * recovery path for the exact situation VK-02 described: `CloseSwapState` appended a funded leaf and
4099
+ * its owner could not see it under any key they held.
4100
+ *
4101
+ * FORWARD-LOOKING ONLY. This finds refunds whose authorization was DERIVED as PRF(nk, nullifier0).
4102
+ * A swap whose refund keypair and blinding were drawn from raw randomness — every swap submitted
4103
+ * before that derivation shipped, and any swap built without an `nk` — has no derivation to replay,
4104
+ * and no key-only scan can ever recover it. Those secrets existed solely in the caller's in-memory
4105
+ * `UtxoSwapResult.refund`. An empty result is therefore NOT proof that a wallet has no stranded
4106
+ * refund; it means none of the leaves in the scanned window were derivable from this `nk`.
4107
+ *
4108
+ * @param connection Solana RPC connection.
4109
+ * @param programId Shield-pool program id.
4110
+ * @param viewingKeyNk 32-byte `nk` — the same value chain notes are decrypted with.
4111
+ */
4112
+ declare function discoverSwapRefunds(connection: Connection, programId: PublicKey, viewingKeyNk: Uint8Array, options?: DiscoverSwapRefundsOptions): Promise<DiscoveredSwapRefund[]>;
4113
+
4114
+ /**
4115
+ * Compact deterministic chain note format.
4116
+ *
4117
+ * Envelope: [version 1][ciphertext (plaintext + AES-GCM tag)]
4118
+ *
4119
+ * Plaintext layout by version:
4120
+ * - v3: [timestamp: u64 LE (8)][noteSalt: u256 BE (32)]
4121
+ * - v2: [timestamp: u64 LE (8)]
4122
+ */
4123
+ type ChainNoteTxType = "deposit" | "withdraw" | "transfer" | "swap" | "unknown";
4124
+ interface CompactChainNote {
4125
+ timestamp: bigint;
4126
+ commitment: string;
4127
+ noteSalt?: bigint;
4128
+ /** v4 only: the three terms that make up `noteSemantics`. */
4129
+ outAmount0?: bigint;
4130
+ outPubkey0?: bigint;
4131
+ isSendToSelfKey0?: bigint;
4132
+ }
4133
+ /**
4134
+ * Encrypt a compact deterministic chain note.
4135
+ *
4136
+ * v3 carries noteSalt so the recipient can recompute and verify the public
4137
+ * chainNoteHash without making the output commitment linkable by observers.
4138
+ */
4139
+ declare function encryptCompactChainNote(timestamp: bigint, nk: Uint8Array, commitmentHex: string, noteSalt: bigint, semantics?: {
4140
+ outAmount0: bigint;
4141
+ outPubkey0: bigint;
4142
+ isSendToSelfKey0: bigint;
4143
+ }): Promise<Uint8Array>;
4144
+ /**
4145
+ * Decrypt a compact deterministic chain note with candidate output commitments.
4146
+ * Tries each commitment-derived key until AES-GCM authentication succeeds.
4147
+ * Accepts current v3 notes and legacy v2 notes.
4148
+ */
4149
+ declare function decryptCompactChainNote(noteBytes: Uint8Array, nk: Uint8Array, candidateCommitments: string[]): Promise<CompactChainNote>;
4150
+ declare function chainNoteToBase64(noteBytes: Uint8Array): string;
4151
+ declare function chainNoteFromBase64(base64: string): Uint8Array;
4152
+
4153
+ /**
4154
+ * Transaction Scanner
4155
+ *
4156
+ * Scans on-chain transactions for the Cloak shield pool program,
4157
+ * extracts chain note envelopes from instruction data, and
4158
+ * trial-decrypts them with the caller's viewing key.
4159
+ *
4160
+ * This module is fully autonomous — it talks directly to a Solana
4161
+ * RPC node and does not depend on the relay.
4162
+ */
4163
+
4164
+ /** A single decoded & verified transaction record. */
4165
+ interface ScannedTransaction {
4166
+ /** Transaction type (deposit / withdraw / transfer / swap) */
4167
+ txType: ChainNoteTxType;
4168
+ /** Absolute amount in lamports (gross — the full amount leaving the pool) */
4169
+ amount: bigint;
4170
+ /** Protocol fee in lamports (non-zero only for withdrawals/swaps) */
4171
+ fee: bigint;
4172
+ /** Net amount received by the recipient (amount - fee). For deposits this equals amount. */
3301
4173
  netAmount: bigint;
3302
4174
  /** Millisecond timestamp embedded in the chain note */
3303
4175
  timestamp: bigint;
@@ -3341,6 +4213,30 @@ interface ScanResult {
3341
4213
  lastSignature?: string;
3342
4214
  /** Number of RPC getTransaction calls actually made (for diagnostics). */
3343
4215
  rpcCallsMade: number;
4216
+ /**
4217
+ * Notes delivered TO this wallet by other people's shield-to-shield sends, recovered from the
4218
+ * CLKD1 registry. These are SPENDABLE note secrets, not history records — they are intentionally
4219
+ * kept out of `transactions`/`summary` so the compliance report shape is unchanged.
4220
+ */
4221
+ deliveredNotes: DeliveredNote[];
4222
+ /**
4223
+ * This wallet's OWN deposits, rebuilt in SPENDABLE form from `nk` alone (VK-01).
4224
+ *
4225
+ * `transactions` records that a deposit happened and for how much. These carry the keypair and
4226
+ * blinding as well, which is the difference between seeing a note and being able to spend it.
4227
+ * Only deposits built with {@link createRecoverableDepositUtxo} appear here; a deposit whose
4228
+ * blinding came from raw randomness has nothing to rebuild and shows up as a history row only.
4229
+ *
4230
+ * Additive, for the same reason as `deliveredNotes`: note secrets are not compliance rows.
4231
+ */
4232
+ recoveredDepositNotes: RecoveredDepositNoteRecord[];
4233
+ }
4234
+ /** A recovered deposit note plus the chain coordinates it was recovered from. */
4235
+ interface RecoveredDepositNoteRecord extends RecoveredDepositNote {
4236
+ /** Signature of the deposit transaction. */
4237
+ signature: string;
4238
+ /** Millisecond timestamp from the chain note. */
4239
+ timestamp: bigint;
3344
4240
  }
3345
4241
  /** Options for `scanTransactions`. */
3346
4242
  interface ScanOptions {
@@ -3376,15 +4272,74 @@ interface ScanOptions {
3376
4272
  * matching the wallet's associated token account against the on-chain recipient ATA.
3377
4273
  */
3378
4274
  walletPublicKey?: string;
4275
+ /**
4276
+ * Sweep the CLKD1 recipient-delivery registry for notes sent TO this wallet (default: true).
4277
+ * Costs one extra `getSignaturesForAddress` page plus one `getTransaction` per carrier.
4278
+ */
4279
+ includeRecipientDeliveries?: boolean;
4280
+ /**
4281
+ * This wallet's UTXO public key. Supply it to authenticate each delivery carrier's declared
4282
+ * commitment (see `ScanRecipientDeliveryOptions.ownerUtxoPublicKey`). Without it the commitment
4283
+ * is reported unverified.
4284
+ */
4285
+ ownerUtxoPublicKey?: bigint;
4286
+ /** Candidate pool mints for delivery-carrier commitment verification. Defaults to native SOL. */
4287
+ deliveryMints?: PublicKey[];
4288
+ }
4289
+ /** A note delivered TO the scanning wallet by someone else's shield-to-shield send. */
4290
+ interface DeliveredNote {
4291
+ /** Output commitment (lowercase hex) the carrier declares. */
4292
+ commitment: string;
4293
+ /** Note amount, decrypted from the envelope. */
4294
+ amount: bigint;
4295
+ /** Note blinding, decrypted from the envelope. */
4296
+ blinding: bigint;
4297
+ /** Carrier transaction signature (the discovery record, NOT the source transaction). */
4298
+ carrierSignature: string;
4299
+ /** Carrier block time in seconds, when the RPC supplied one. */
4300
+ blockTime?: number;
4301
+ /**
4302
+ * Pool mint the note lives in, resolved by recomputing the commitment. Present only when
4303
+ * `ownerUtxoPublicKey` was supplied — without it the declared commitment cannot be checked.
4304
+ */
4305
+ mint?: string;
4306
+ /**
4307
+ * True when the declared commitment was recomputed from the decrypted note and matched. The
4308
+ * memo's commitment field is unauthenticated; only this recomputation binds it.
4309
+ */
4310
+ commitmentVerified: boolean;
4311
+ }
4312
+ interface ScanRecipientDeliveryOptions {
4313
+ connection: Connection;
4314
+ programId: PublicKey;
4315
+ /** nk (32 bytes). The X25519 opening key is `deriveViewingKeyFromNk(nk).privateKey`. */
4316
+ viewingKeyNk: Uint8Array;
4317
+ /**
4318
+ * The scanning wallet's UTXO public key. Supply it to authenticate the carrier's declared
4319
+ * commitment: the memo's commitment field is written by the relay and is not covered by the
4320
+ * envelope's Poly1305 tag, so a carrier can claim any commitment it likes. With this set, a
4321
+ * carrier survives only if `Poseidon(amount, ownerPubkey, blinding, mint)` reproduces it.
4322
+ */
4323
+ ownerUtxoPublicKey?: bigint;
4324
+ /** Candidate pool mints to try when verifying. Defaults to the native-SOL sentinel. */
4325
+ mints?: PublicKey[];
4326
+ onStatus?: (status: string) => void;
4327
+ debug?: boolean;
3379
4328
  }
3380
4329
  /**
3381
- * Scan on-chain transactions for the Cloak program, extract chain
3382
- * notes, trial-decrypt with the provided viewing key, verify integrity,
3383
- * and return a sorted list of the caller's transactions.
4330
+ * Sweep the recipient-delivery registry (CLKD1) and trial-open every carrier with the caller's
4331
+ * viewing key.
3384
4332
  *
3385
- * This is fully self-contained it only needs a Solana RPC connection
3386
- * and the user's viewing key (private).
4333
+ * This is the READ half of the fix for S2S-08 / VK-01. The pre-existing sweep
4334
+ * (`scanSwapNoteCarriers`) reads the CLK1 registry, which carries the SENDER-encrypted compliance
4335
+ * note — which is why a cold scan found the withdrawal change note and nothing that was sent TO the
4336
+ * scanning wallet. This registry carries the recipient-encrypted envelope, and is the only place a
4337
+ * recipient's own note is discoverable from `(rpc, programId, nk)` alone.
3387
4338
  */
4339
+ declare function scanRecipientDeliveryNotes(opts: ScanRecipientDeliveryOptions): Promise<{
4340
+ notes: DeliveredNote[];
4341
+ rpcCalls: number;
4342
+ }>;
3388
4343
  declare function scanTransactions(opts: ScanOptions): Promise<ScanResult>;
3389
4344
  /** JSON-serializable compliance report (numbers instead of bigint). Used for cache and display. */
3390
4345
  interface ComplianceReport {
@@ -3560,8 +4515,8 @@ declare class SimpleWallet {
3560
4515
  * @packageDocumentation
3561
4516
  */
3562
4517
 
3563
- declare const VERSION = "0.1.5";
4518
+ declare const VERSION = "0.2.1";
3564
4519
  /** True when scanner supports TransactSwap (tag 1). Check this to verify the correct SDK bundle is loaded. */
3565
4520
  declare const SCANNER_SUPPORTS_TRANSACT_SWAP = true;
3566
4521
 
3567
- export { CLOAK_PROGRAM_ID, type ChainNoteTxType, type CircuitVerificationResult, type CloakConfig, CloakError, type CloakKeyPair, type CloakNote, CloakSDK, type CommitmentEntry, type CommitmentsResponse, type CompactChainNote, type ComplianceReport, type ComplianceTxType, DEFAULT_CIRCUITS_URL, DEFAULT_TRANSACTION_CIRCUITS_URL, type DepositInstructionParams, type DepositOptions, type DepositResult, type DepositStatus, EXPECTED_CIRCUIT_HASHES, type EncryptedMetadataBundle, type EncryptedNote$1 as EncryptedNote, type ErrorCategory, type ExpandedSpendKey, FIXED_FEE_LAMPORTS, type Groth16Proof, LAMPORTS_PER_SOL, LocalStorageAdapter, type LogLevel, type Logger, MERKLE_TREE_HEIGHT, MIN_DEPOSIT_LAMPORTS, type MasterKey, type MaxLengthArray, MemoryStorageAdapter, type MerkleProof, type MerkleRootResponse, MerkleTree, NATIVE_SOL_MINT, type Network, type NoteData, type OnchainMerkleProof, type PendingDeposit, type PendingWithdrawal, type ProofResult, RelayInternalError, RelayService, type RiskQuoteInstructionResponse, RootNotFoundError, SCANNER_SUPPORTS_TRANSACT_SWAP, SIGN_IN_MESSAGE, SanctionsQuoteError, type ScanOptions, type ScanResult, type ScanSummary, type ScannedTransaction, ShieldPoolErrors, type ShieldPoolPDAs, SimpleWallet, type SpendKey, type StorageAdapter, type SwapOptions, type SwapParams, type SwapResult, type TransactOptions, type TransactParams, type TransactResult, type TransactionMetadata, type Transfer, type TransferOptions, type TransferResult, type TxStatus, type UserFriendlyError, type Utxo, UtxoAlreadySpentError, type EncryptedNote as UtxoEncryptedNote, type UtxoKeypair, type UtxoSwapParams, type UtxoSwapResult, UtxoWallet, VARIABLE_FEE_DENOMINATOR, VARIABLE_FEE_NUMERATOR, VARIABLE_FEE_RATE, VERSION, type VerifyUtxosResult, type ViewKey, type ViewingKeyPair, type WalletAdapter, type WalletUtxo, type WithdrawOptions, type WithdrawRegularInputs, type WithdrawSubmissionResult, type WithdrawSwapInputs, areCircuitsAvailable, bigintToBytes32$1 as bigintToBytes32, bigintToHex, buildMerkleTree, buildMerkleTreeFromChain, buildMerkleTreeFromRelay, buildPublicInputsBytes, bytesToHex, calculateFee, calculateFeeBigint, calculateRelayFee, chainNoteFromBase64, chainNoteToBase64, classifyRelayError, cleanupStalePendingOperations, clearPendingDeposits, clearPendingWithdrawals, computeChainNoteHash, computeCommitment$1 as computeCommitment, computeExtDataHash, computeMerkleRoot, computeNullifier$1 as computeNullifier, computeNullifierAsync, computeNullifierSync, computeOutputsHash, computeOutputsHashAsync, computeOutputsHashSync, computeProofForLatestDeposit, computeProofFromChain, computeSignature, computeSwapOutputsHash, computeSwapOutputsHashAsync, computeSwapOutputsHashSync, computeCommitment as computeUtxoCommitment, computeNullifier as computeUtxoNullifier, copyNoteToClipboard, createCloakError, createDepositInstruction, createLogger, createUtxo, createZeroUtxo, decryptCompactChainNote, decryptComplianceMetadataWithMasterKey, decryptTransactionMetadata, deriveDiversifiedViewingKey, deriveDiversifier, derivePublicKey, deriveSpendKey, deriveUserCompliancePublicKey, deriveUserComplianceScalar, deriveUtxoKeypairFromSpendKey, deriveViewKey, deriveViewingKeyFromNk, deriveViewingKeyFromSpendKey, deriveViewingKeyFromUtxoPrivateKey, deserializeUtxo, detectNetworkFromRpcUrl, downloadNote, encodeNoteSimple, encryptCompactChainNote, encryptNoteForRecipient, encryptTransactionMetadata, encryptTransactionMetadataBundle, expandSpendKey, exportKeys, exportNote, exportWalletKeys, fetchCommitments, fetchRiskQuoteInstruction, fetchRiskQuoteIx, filterNotesByNetwork, filterWithdrawableNotes, findNoteByCommitment, formatAmount, formatComplianceCsv, formatErrorForLogging, formatSol, fullWithdraw, generateCloakKeys, generateCommitment, generateCommitmentAsync, generateMasterSeed, generateNote, generateNoteFromWallet, generateUtxoKeypair, generateViewingKeyPair, generateWithdrawRegularProof, generateWithdrawSwapProof, getAddressExplorerUrl, getCircuitsPath, getDefaultCircuitsPath, getDistributableAmount, getExplorerUrl, getNkFromUtxoPrivateKey, getNullifierPDA, getPendingOperationsSummary, getPublicKey, getPublicViewKey, getRecipientAmount, getRpcUrlForNetwork, getShieldPoolPDAs, getSwapStatePDA, getViewKey, hasPendingOperations, hexToBigint$1 as hexToBigint, hexToBytes, importKeys, importWalletKeys, isDebugEnabled, isRootNotFoundError, isValidHex, isValidRpcUrl, isValidSolanaAddress, isWithdrawAmountSufficient, isWithdrawable, keypairToAdapter, loadPendingDeposits, loadPendingWithdrawals, parseAmount, parseError, parseNote, parseRelayErrorResponse, parseTransactionError, partialWithdraw, poseidonHash, preflightCheck, preflightNullifiers, prepareEncryptedOutput, prepareEncryptedOutputForRecipient, proofToBytes, pubkeyToFieldElement, pubkeyToLimbs, randomBytes, randomFieldElement, readMerkleTreeState, registerViewingKey, removePendingDeposit, removePendingWithdrawal, savePendingDeposit, savePendingWithdrawal, scanNotesForWallet, scanTransactions, sdkLogger, selectUtxos, sendTransaction, serializeNote, serializeUtxo, setCircuitsPath, setDebugMode, signTransaction, splitTo2Limbs, sumUtxoAmounts, swapUtxo, swapWithChange, toComplianceReport, transact, transfer, truncate, tryDecryptNote, updateNoteWithDeposit, updatePendingDeposit, updatePendingWithdrawal, bigintToBytes32 as utxoBigintToBytes32, utxoEquals, hexToBigint as utxoHexToBigint, validateDepositParams, validateNote, validateOutputsSum, validateRoot, validateTransfers, validateWalletConnected, validateWithdrawableNote, verifyAllCircuits, verifyCircuitIntegrity, verifyUtxos, waitForRoot, withTiming };
4522
+ 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 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 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, 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, 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, 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 };