Skip to main content

breez_sdk_spark/models/
mod.rs

1pub(crate) mod adaptors;
2pub mod payment_observer;
3pub use payment_observer::*;
4
5// Re-export public conversion types from the conversion module
6pub use crate::token_conversion::{
7    AmountAdjustmentReason, ConversionEstimate, ConversionInfo, ConversionOptions,
8    ConversionPurpose, ConversionStatus, ConversionType, FetchConversionLimitsRequest,
9    FetchConversionLimitsResponse, SwapDegradation,
10};
11
12use core::fmt;
13use lnurl_models::RecoverLnurlPayResponse;
14use serde::{Deserialize, Serialize};
15use serde_json::Value;
16use std::{
17    collections::{HashMap, HashSet},
18    fmt::Display,
19    str::FromStr,
20};
21
22use crate::{
23    BitcoinAddressDetails, BitcoinChainService, BitcoinNetwork, Bolt11InvoiceDetails,
24    ExternalInputParser, FiatCurrency, LnurlPayRequestDetails, LnurlWithdrawRequestDetails, Rate,
25    SdkError, SparkInvoiceDetails, SuccessAction, SuccessActionProcessed,
26    cross_chain::{CrossChainFeeMode, CrossChainProviderContext, CrossChainRoutePair},
27    error::DepositClaimError,
28};
29
30/// A list of external input parsers that are used by default.
31/// To opt-out, set `use_default_external_input_parsers` in [Config] to false.
32pub const DEFAULT_EXTERNAL_INPUT_PARSERS: &[(&str, &str, &str)] = &[
33    (
34        "picknpay",
35        "(.*)(za.co.electrum.picknpay)(.*)",
36        "https://cryptoqr.net/.well-known/lnurlp/<input>",
37    ),
38    (
39        "bootleggers",
40        r"(.*)(wigroup\.co|yoyogroup\.co)(.*)",
41        "https://cryptoqr.net/.well-known/lnurlw/<input>",
42    ),
43];
44
45/// Represents the seed for wallet generation, either as a mnemonic phrase with an optional
46/// passphrase or as raw entropy bytes.
47#[derive(Debug, Clone)]
48#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
49pub enum Seed {
50    /// A BIP-39 mnemonic phrase with an optional passphrase.
51    Mnemonic {
52        /// The mnemonic phrase. 12 or 24 words.
53        mnemonic: String,
54        /// An optional passphrase for the mnemonic.
55        passphrase: Option<String>,
56    },
57    /// Raw entropy bytes.
58    Entropy(Vec<u8>),
59}
60
61impl Seed {
62    pub fn to_bytes(&self) -> Result<Vec<u8>, SdkError> {
63        match self {
64            Seed::Mnemonic {
65                mnemonic,
66                passphrase,
67            } => {
68                let mnemonic = bip39::Mnemonic::parse(mnemonic)
69                    .map_err(|e| SdkError::Generic(e.to_string()))?;
70
71                Ok(mnemonic
72                    .to_seed(passphrase.as_deref().unwrap_or(""))
73                    .to_vec())
74            }
75            Seed::Entropy(entropy) => Ok(entropy.clone()),
76        }
77    }
78}
79
80#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
81pub struct ConnectRequest {
82    pub config: Config,
83    pub seed: Seed,
84    pub storage_dir: String,
85}
86
87/// Request object for connecting to the Spark network using an external signer.
88///
89/// This allows using a custom signer implementation instead of providing a seed directly.
90#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
91#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
92pub struct ConnectWithSignerRequest {
93    pub config: Config,
94    /// External signer for non-Spark SDK signing (LNURL-auth, sync, message
95    /// signing, ECIES).
96    pub breez_signer: std::sync::Arc<dyn crate::signer::ExternalBreezSigner>,
97    /// External high-level Spark signer for the Spark wallet flows.
98    pub spark_signer: std::sync::Arc<dyn crate::signer::ExternalSparkSigner>,
99    pub storage_dir: String,
100}
101
102/// Request object for connecting to the Spark network using a signing-only
103/// external signer.
104///
105/// Use this instead of [`ConnectWithSignerRequest`] for a signer that can't
106/// perform the SDK's local ECIES/HMAC operations (for example a
107/// policy-restricted enclave). The SDK keeps session tokens in plaintext and
108/// disables the features that rely on ECIES/HMAC.
109#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
110#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
111pub struct ConnectWithSigningOnlySignerRequest {
112    pub config: Config,
113    /// Signing-only external signer for non-Spark SDK signing.
114    pub breez_signer: std::sync::Arc<dyn crate::signer::ExternalSigningSigner>,
115    /// External high-level Spark signer for the Spark wallet flows.
116    pub spark_signer: std::sync::Arc<dyn crate::signer::ExternalSparkSigner>,
117    pub storage_dir: String,
118}
119
120/// The type of payment
121#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
122#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
123pub enum PaymentType {
124    /// Payment sent from this wallet
125    Send,
126    /// Payment received to this wallet
127    Receive,
128}
129
130impl fmt::Display for PaymentType {
131    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132        match self {
133            PaymentType::Send => write!(f, "send"),
134            PaymentType::Receive => write!(f, "receive"),
135        }
136    }
137}
138
139impl FromStr for PaymentType {
140    type Err = String;
141
142    fn from_str(s: &str) -> Result<Self, Self::Err> {
143        Ok(match s.to_lowercase().as_str() {
144            "receive" => PaymentType::Receive,
145            "send" => PaymentType::Send,
146            _ => return Err(format!("invalid payment type '{s}'")),
147        })
148    }
149}
150
151/// The status of a payment
152#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
153#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
154pub enum PaymentStatus {
155    /// Payment is completed successfully
156    Completed,
157    /// Payment is in progress
158    Pending,
159    /// Payment has failed
160    Failed,
161}
162
163impl PaymentStatus {
164    /// Returns true if the payment status is final (either Completed or Failed)
165    pub fn is_final(&self) -> bool {
166        matches!(self, PaymentStatus::Completed | PaymentStatus::Failed)
167    }
168}
169
170impl fmt::Display for PaymentStatus {
171    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172        match self {
173            PaymentStatus::Completed => write!(f, "completed"),
174            PaymentStatus::Pending => write!(f, "pending"),
175            PaymentStatus::Failed => write!(f, "failed"),
176        }
177    }
178}
179
180impl FromStr for PaymentStatus {
181    type Err = String;
182
183    fn from_str(s: &str) -> Result<Self, Self::Err> {
184        Ok(match s.to_lowercase().as_str() {
185            "completed" => PaymentStatus::Completed,
186            "pending" => PaymentStatus::Pending,
187            "failed" => PaymentStatus::Failed,
188            _ => return Err(format!("Invalid payment status '{s}'")),
189        })
190    }
191}
192
193#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
194#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
195pub enum PaymentMethod {
196    Lightning,
197    Spark,
198    Token,
199    Deposit,
200    Withdraw,
201    Unknown,
202}
203
204impl Display for PaymentMethod {
205    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
206        match self {
207            PaymentMethod::Lightning => write!(f, "lightning"),
208            PaymentMethod::Spark => write!(f, "spark"),
209            PaymentMethod::Token => write!(f, "token"),
210            PaymentMethod::Deposit => write!(f, "deposit"),
211            PaymentMethod::Withdraw => write!(f, "withdraw"),
212            PaymentMethod::Unknown => write!(f, "unknown"),
213        }
214    }
215}
216
217impl FromStr for PaymentMethod {
218    type Err = ();
219
220    fn from_str(s: &str) -> Result<Self, Self::Err> {
221        match s {
222            "lightning" => Ok(PaymentMethod::Lightning),
223            "spark" => Ok(PaymentMethod::Spark),
224            "token" => Ok(PaymentMethod::Token),
225            "deposit" => Ok(PaymentMethod::Deposit),
226            "withdraw" => Ok(PaymentMethod::Withdraw),
227            "unknown" => Ok(PaymentMethod::Unknown),
228            _ => Err(()),
229        }
230    }
231}
232
233/// Represents a payment (sent or received)
234#[derive(Debug, Clone, Serialize, Deserialize)]
235#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
236pub struct Payment {
237    /// Unique identifier for the payment
238    pub id: String,
239    /// Type of payment (send or receive)
240    pub payment_type: PaymentType,
241    /// Status of the payment
242    pub status: PaymentStatus,
243    /// Amount in satoshis or token base units
244    pub amount: u128,
245    /// Fee paid in satoshis or token base units
246    pub fees: u128,
247    /// Timestamp of when the payment was created
248    pub timestamp: u64,
249    /// Method of payment. Sometimes the payment details is empty so this field
250    /// is used to determine the payment method.
251    pub method: PaymentMethod,
252    /// Details of the payment
253    pub details: Option<PaymentDetails>,
254    /// If set, this payment involved a conversion before the payment
255    pub conversion_details: Option<ConversionDetails>,
256}
257
258impl Payment {
259    /// Returns `true` if this payment is a child of a conversion operation.
260    ///
261    /// Conversion operations (stable balance, ongoing sends) create internal child
262    /// payments (send sats→Flashnet, receive tokens). These are identified by having
263    /// `conversion_info` set in their payment details.
264    pub fn is_conversion_child(&self) -> bool {
265        matches!(
266            &self.details,
267            Some(
268                PaymentDetails::Spark {
269                    conversion_info: Some(_),
270                    ..
271                } | PaymentDetails::Token {
272                    conversion_info: Some(_),
273                    ..
274                }
275            )
276        )
277    }
278}
279
280/// Outlines the steps involved in one or more conversions on a payment.
281///
282/// Built progressively: `status` is available immediately from payment metadata,
283/// while `conversions` are enriched later from child payments and conversion info.
284#[derive(Debug, Clone, Serialize, Deserialize)]
285#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
286pub struct ConversionDetails {
287    /// Overall status of the conversion (persisted in storage)
288    pub status: ConversionStatus,
289    /// Ordered list of conversion steps. For sends: [AMM, cross-chain].
290    /// For receives: [cross-chain, AMM]. Rebuilt on retrieval, not persisted.
291    #[serde(default)]
292    pub conversions: Vec<Conversion>,
293}
294
295/// The provider that performed a conversion.
296#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
297#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
298pub enum ConversionProvider {
299    /// AMM (Flashnet pool) conversion between token and BTC on Spark
300    Amm,
301    /// Orchestra cross-chain conversion
302    Orchestra,
303    /// Boltz reverse-swap cross-chain conversion
304    Boltz,
305}
306
307/// The chain or network that a [`ConversionSide`] lives on.
308#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
309#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
310pub enum ConversionChain {
311    /// Spark layer-2 network.
312    Spark,
313    /// Bitcoin Lightning Network.
314    Lightning,
315    /// An external chain reached via a cross-chain provider.
316    External {
317        /// Human-readable chain name (e.g. `"base"`, `"solana"`, `"arbitrum"`).
318        name: String,
319        /// Stable chain identifier (e.g. EVM `chainId` as a decimal string,
320        /// or a chain-native identifier). `None` when the provider does not
321        /// expose one for this route.
322        chain_id: Option<String>,
323    },
324}
325
326/// The asset on a [`ConversionSide`] — groups the ticker, stable identifier,
327/// and decimals that always travel together.
328#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
329#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
330pub struct ConversionAsset {
331    /// Ticker (e.g. `"BTC"`, `"USDB"`, `"USDC"`, `"USDT"`). Tickers alone
332    /// are ambiguous across chains — pair with [`Self::identifier`] for a
333    /// hard match.
334    pub ticker: String,
335    /// Stable identifier: a Spark token identifier for Spark tokens, or a
336    /// contract/mint address for cross-chain assets. `None` for BTC/sats.
337    pub identifier: Option<String>,
338    /// Number of decimals for the asset.
339    /// `0` for BTC/sats sides (amount is already in the smallest unit,
340    /// so no scaling is needed); non-zero for token assets (e.g. `6` for
341    /// USDC/USDT/USDB).
342    pub decimals: u32,
343}
344
345/// One side (source or destination) of a conversion.
346#[derive(Debug, Clone, Serialize, Deserialize)]
347#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
348pub struct ConversionSide {
349    /// The chain or network for this side.
350    pub chain: ConversionChain,
351    /// The asset being converted on this side.
352    pub asset: ConversionAsset,
353    /// Amount in base units (satoshis or token base units)
354    pub amount: u128,
355    /// Fee in the same base units
356    pub fee: u128,
357}
358
359/// A single conversion in a payment's conversion chain.
360#[derive(Debug, Clone, Serialize, Deserialize)]
361#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
362pub struct Conversion {
363    /// The provider that performed this conversion
364    pub provider: ConversionProvider,
365    /// Status of this specific conversion step
366    pub status: ConversionStatus,
367    /// Source side of the conversion
368    pub from: ConversionSide,
369    /// Destination side of the conversion
370    pub to: ConversionSide,
371    /// Reason the conversion amount was adjusted, if applicable (AMM only)
372    #[serde(default)]
373    pub amount_adjustment: Option<AmountAdjustmentReason>,
374}
375
376#[cfg(feature = "uniffi")]
377uniffi::custom_type!(u128, String, {
378    remote,
379    try_lift: |val| val.parse::<u128>().map_err(uniffi::deps::anyhow::Error::msg),
380    lower: |obj| obj.to_string(),
381});
382
383// TODO: fix large enum variant lint - may be done by boxing lnurl_pay_info but that requires
384//  some changes to the wasm bindgen macro
385#[allow(clippy::large_enum_variant)]
386#[derive(Debug, Clone, Serialize, Deserialize)]
387#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
388pub enum PaymentDetails {
389    Spark {
390        /// The invoice details if the payment fulfilled a spark invoice
391        invoice_details: Option<SparkInvoicePaymentDetails>,
392        /// The HTLC transfer details if the payment fulfilled an HTLC transfer
393        htlc_details: Option<SparkHtlcDetails>,
394        /// The information for a conversion
395        conversion_info: Option<ConversionInfo>,
396    },
397    Token {
398        metadata: TokenMetadata,
399        tx_hash: String,
400        tx_type: TokenTransactionType,
401        /// The invoice details if the payment fulfilled a spark invoice
402        invoice_details: Option<SparkInvoicePaymentDetails>,
403        /// The information for a conversion
404        conversion_info: Option<ConversionInfo>,
405    },
406    Lightning {
407        /// Represents the invoice description
408        description: Option<String>,
409        /// Represents the Bolt11/Bolt12 invoice associated with a payment
410        /// In the case of a Send payment, this is the invoice paid by the user
411        /// In the case of a Receive payment, this is the invoice paid to the user
412        invoice: String,
413
414        /// The invoice destination/payee pubkey
415        destination_pubkey: String,
416
417        /// The HTLC transfer details
418        htlc_details: SparkHtlcDetails,
419
420        /// Lnurl payment information if this was an lnurl payment.
421        lnurl_pay_info: Option<LnurlPayInfo>,
422
423        /// Lnurl withdrawal information if this was an lnurl payment.
424        lnurl_withdraw_info: Option<LnurlWithdrawInfo>,
425
426        /// Lnurl receive information if this was a received lnurl payment.
427        lnurl_receive_metadata: Option<LnurlReceiveMetadata>,
428
429        /// The information for a conversion — populated when this Lightning
430        /// payment is the source leg of a cross-chain conversion (e.g. a
431        /// Boltz reverse swap paying a hold invoice).
432        conversion_info: Option<ConversionInfo>,
433    },
434    Withdraw {
435        tx_id: String,
436    },
437    Deposit {
438        tx_id: String,
439        vout: u32,
440    },
441}
442
443#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
444#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
445pub enum TokenTransactionType {
446    Transfer,
447    Mint,
448    Burn,
449}
450
451impl fmt::Display for TokenTransactionType {
452    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
453        match self {
454            TokenTransactionType::Transfer => write!(f, "transfer"),
455            TokenTransactionType::Mint => write!(f, "mint"),
456            TokenTransactionType::Burn => write!(f, "burn"),
457        }
458    }
459}
460
461impl FromStr for TokenTransactionType {
462    type Err = String;
463
464    fn from_str(s: &str) -> Result<Self, Self::Err> {
465        match s.to_lowercase().as_str() {
466            "transfer" => Ok(TokenTransactionType::Transfer),
467            "mint" => Ok(TokenTransactionType::Mint),
468            "burn" => Ok(TokenTransactionType::Burn),
469            _ => Err(format!("Invalid token transaction type '{s}'")),
470        }
471    }
472}
473
474#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
475#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
476pub struct SparkInvoicePaymentDetails {
477    /// Represents the spark invoice description
478    pub description: Option<String>,
479    /// The raw spark invoice string
480    pub invoice: String,
481}
482
483#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
484#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
485pub struct SparkHtlcDetails {
486    /// The payment hash of the HTLC
487    pub payment_hash: String,
488    /// The preimage of the HTLC. Empty until receiver has released it.
489    pub preimage: Option<String>,
490    /// The expiry time of the HTLC as a unix timestamp in seconds
491    pub expiry_time: u64,
492    /// The HTLC status
493    pub status: SparkHtlcStatus,
494}
495
496#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
497#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
498pub enum SparkHtlcStatus {
499    /// The HTLC is waiting for the preimage to be shared by the receiver
500    WaitingForPreimage,
501    /// The HTLC preimage has been shared and the transfer can be or has been claimed by the receiver
502    PreimageShared,
503    /// The HTLC has been returned to the sender due to expiry
504    Returned,
505}
506
507impl fmt::Display for SparkHtlcStatus {
508    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
509        match self {
510            SparkHtlcStatus::WaitingForPreimage => write!(f, "WaitingForPreimage"),
511            SparkHtlcStatus::PreimageShared => write!(f, "PreimageShared"),
512            SparkHtlcStatus::Returned => write!(f, "Returned"),
513        }
514    }
515}
516
517impl FromStr for SparkHtlcStatus {
518    type Err = String;
519
520    fn from_str(s: &str) -> Result<Self, Self::Err> {
521        match s {
522            "WaitingForPreimage" => Ok(SparkHtlcStatus::WaitingForPreimage),
523            "PreimageShared" => Ok(SparkHtlcStatus::PreimageShared),
524            "Returned" => Ok(SparkHtlcStatus::Returned),
525            _ => Err("Invalid Spark HTLC status".to_string()),
526        }
527    }
528}
529
530#[derive(Debug, Clone, Copy, PartialEq, Eq)]
531#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
532pub enum Network {
533    Mainnet,
534    Regtest,
535}
536
537impl std::fmt::Display for Network {
538    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
539        match self {
540            Network::Mainnet => write!(f, "Mainnet"),
541            Network::Regtest => write!(f, "Regtest"),
542        }
543    }
544}
545
546impl From<Network> for BitcoinNetwork {
547    fn from(network: Network) -> Self {
548        match network {
549            Network::Mainnet => BitcoinNetwork::Bitcoin,
550            Network::Regtest => BitcoinNetwork::Regtest,
551        }
552    }
553}
554
555impl From<Network> for breez_sdk_common::network::BitcoinNetwork {
556    fn from(network: Network) -> Self {
557        match network {
558            Network::Mainnet => breez_sdk_common::network::BitcoinNetwork::Bitcoin,
559            Network::Regtest => breez_sdk_common::network::BitcoinNetwork::Regtest,
560        }
561    }
562}
563
564impl From<Network> for bitcoin::Network {
565    fn from(network: Network) -> Self {
566        match network {
567            Network::Mainnet => bitcoin::Network::Bitcoin,
568            Network::Regtest => bitcoin::Network::Regtest,
569        }
570    }
571}
572
573impl FromStr for Network {
574    type Err = String;
575
576    fn from_str(s: &str) -> Result<Self, Self::Err> {
577        match s {
578            "mainnet" => Ok(Network::Mainnet),
579            "regtest" => Ok(Network::Regtest),
580            _ => Err("Invalid network".to_string()),
581        }
582    }
583}
584
585#[derive(Debug, Clone)]
586#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
587#[allow(clippy::struct_excessive_bools)]
588pub struct Config {
589    pub api_key: Option<String>,
590    pub network: Network,
591    pub sync_interval_secs: u32,
592
593    // The maximum fee that can be paid for a static deposit claim
594    // If not set then any fee is allowed
595    pub max_deposit_claim_fee: Option<MaxFee>,
596
597    /// Maximum instant (0-conf) static deposit claim fee, as basis points of the
598    /// deposit value (100 bps = 1%), capping the SSP spread for the instant
599    /// credit. Opt-in: while unset, no 0-conf claim is attempted. Small deposits,
600    /// whose spread is proportionally larger, fall through to the normal claim.
601    pub max_instant_deposit_claim_fee_bps: Option<u32>,
602
603    /// The domain used for receiving through lnurl-pay and lightning address.
604    pub lnurl_domain: Option<String>,
605
606    /// When this is set to `true` we will prefer to use spark payments over
607    /// lightning when sending and receiving. This has the benefit of lower fees
608    /// but is at the cost of privacy.
609    pub prefer_spark_over_lightning: bool,
610
611    /// Whether the data needed to exit a payment unilaterally, without the Spark
612    /// operators, is collected as funds arrive. Collection runs in the background,
613    /// and a sync waits for a collection pass before returning, so syncing is how
614    /// to make that happen at a moment of your choosing. A leaf the operators
615    /// cannot complete stays un-exitable until a later attempt succeeds.
616    ///
617    /// Leave this on unless bandwidth matters more than being able to recover funds
618    /// when the operators are unreachable. With it off, chains are only collected
619    /// when an exit is prepared, which needs the operators reachable at that
620    /// moment: a leaf cannot be exited without them until one is collected.
621    ///
622    /// Default value is true.
623    pub exit_chain_auto_fetch_enabled: bool,
624
625    /// A set of external input parsers that are used by [`BreezSdk::parse`](crate::sdk::BreezSdk::parse) when the input
626    /// is not recognized. See [`ExternalInputParser`] for more details on how to configure
627    /// external parsing.
628    pub external_input_parsers: Option<Vec<ExternalInputParser>>,
629    /// The SDK includes some default external input parsers
630    /// ([`DEFAULT_EXTERNAL_INPUT_PARSERS`]).
631    /// Set this to false in order to prevent their use.
632    pub use_default_external_input_parsers: bool,
633
634    /// Url to use for the real-time sync server. Defaults to the Breez real-time sync server.
635    pub real_time_sync_server_url: Option<String>,
636
637    /// Whether the Spark private mode is enabled by default.
638    ///
639    /// If set to true, the Spark private mode will be enabled on the first
640    /// initialization of the SDK. If set to false, no changes will be made
641    /// to the Spark private mode.
642    ///
643    /// This default is only auto-applied when `background_tasks_enabled` is
644    /// `true`. When `background_tasks_enabled` is `false`, the SDK does not
645    /// touch the Spark private mode on startup; call `update_user_settings`
646    /// with `spark_private_mode_enabled` set as needed on a one-time setup
647    /// pass.
648    pub private_enabled_default: bool,
649
650    /// Configuration for leaf optimization.
651    ///
652    /// Leaf optimization controls the denominations of leaves that are held in the wallet.
653    /// Fewer, bigger leaves allow for more funds to be exited unilaterally.
654    /// More leaves allow payments to be made without needing a swap, reducing payment latency.
655    pub leaf_optimization_config: LeafOptimizationConfig,
656
657    /// Configuration for token-output optimization.
658    ///
659    /// Token-output optimization controls automatic consolidation of a token's
660    /// available outputs. Keeping the output set small reduces transaction size,
661    /// while keeping enough distinct outputs preserves concurrency for parallel
662    /// sends.
663    pub token_optimization_config: TokenOptimizationConfig,
664
665    /// Configuration for automatic conversion of Bitcoin to stable tokens.
666    ///
667    /// When set, received sats will be automatically converted to the specified token
668    /// once the balance exceeds the threshold.
669    pub stable_balance_config: Option<StableBalanceConfig>,
670
671    /// Maximum number of concurrent transfer claims.
672    ///
673    /// Default is 4. Increase for server environments with high incoming payment volume.
674    pub max_concurrent_claims: u32,
675
676    /// Optional custom Spark environment configuration.
677    ///
678    /// When set, overrides the default Spark operator pool, service provider,
679    /// threshold, and token settings. Use this to connect to alternative Spark
680    /// deployments (e.g. dev/staging environments).
681    pub spark_config: Option<SparkConfig>,
682
683    /// Master switch for per-instance background services.
684    ///
685    /// When `true` (default), the SDK runs its standard background work:
686    /// periodic sync, lightning-address recovery, private-mode initialization,
687    /// the leaf and token-output optimizers, the Spark server-event
688    /// subscription, and the real-time sync client (when
689    /// `real_time_sync_server_url` is set).
690    ///
691    /// When `false`, **no background service is started**, regardless of any
692    /// other setting on this config. This is intended for multi-tenant server
693    /// deployments where the host application orchestrates sync and claims
694    /// explicitly and receives events via webhooks. Use
695    /// `default_server_config` to get this preset.
696    ///
697    /// Explicit operations (`sync_wallet`, `claim_deposit`,
698    /// `list_unclaimed_deposits`, `refund_deposit`,
699    /// `refund_pending_conversions`, leaf/token optimization, etc.) work
700    /// regardless of this flag.
701    ///
702    /// When `false`, the SDK rejects builds where fields whose backing
703    /// service is gated off are still in their active shape:
704    /// `stable_balance_config` must be `None`, `real_time_sync_server_url`
705    /// must be `None`, and `optimization_config.auto_enabled` must be `false`.
706    /// `default_server_config` already sets these compatible values.
707    pub background_tasks_enabled: bool,
708
709    /// Configuration for cross-chain sends via Orchestra and Boltz.
710    ///
711    /// `Some(_)` enables cross-chain sends (sats to USDT on external chains).
712    /// `None` (default) disables them entirely. Opt in by setting this to
713    /// [`CrossChainConfig::default`] (or a customized value): the providers
714    /// run background work (e.g. web sockets), so enabling is left to the
715    /// caller. Cross-chain sends are only supported on mainnet.
716    pub cross_chain_config: Option<CrossChainConfig>,
717}
718
719/// Configuration for cross-chain sends.
720///
721/// The presence of this struct on [`Config::cross_chain_config`] enables
722/// cross-chain providers; `None` disables them.
723#[derive(Debug, Clone, Default)]
724#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
725pub struct CrossChainConfig {
726    /// Default maximum slippage in basis points used when
727    /// [`PaymentRequest::CrossChain::max_slippage_bps`] is not set on the
728    /// prepare request. Must be in `10..=500`. Falls back to 100 bps (1%)
729    /// when this field is `None`.
730    #[cfg_attr(feature = "uniffi", uniffi(default = None))]
731    pub default_slippage_bps: Option<u32>,
732    /// Default target-overpay pad in basis points applied to the user's
733    /// destination amount on `FeesExcluded` conversion sends. Bumps the
734    /// target upward before quoting so the recipient lands at or above the
735    /// requested amount despite provider slippage. Must be in `0..=500`.
736    /// Falls back to 15 bps when `None`.
737    #[cfg_attr(feature = "uniffi", uniffi(default = None))]
738    pub default_target_overpay_bps: Option<u32>,
739}
740
741/// Configuration for leaf optimization.
742#[derive(Debug, Clone)]
743#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
744pub struct LeafOptimizationConfig {
745    /// Whether automatic leaf optimization is enabled.
746    ///
747    /// If set to true, the SDK will automatically optimize the leaf set when it changes.
748    /// Otherwise, the manual optimization API must be used to optimize the leaf set.
749    ///
750    /// Default value is true.
751    pub auto_enabled: bool,
752    /// The desired multiplicity for the leaf set.
753    ///
754    /// Setting this to 0 will optimize for maximizing unilateral exit.
755    /// Higher values will optimize for minimizing transfer swaps, with higher values
756    /// being more aggressive and allowing better TPS rates.
757    ///
758    /// For end-user wallets, values of 1-5 are recommended. Values above 5 are
759    /// intended for high-throughput server environments and are not recommended
760    /// for end-user wallets due to significantly higher unilateral exit costs.
761    ///
762    /// Default value is 1.
763    pub multiplicity: u8,
764}
765
766/// Configuration for token-output optimization.
767#[derive(Debug, Clone)]
768#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
769pub struct TokenOptimizationConfig {
770    /// Whether automatic token-output consolidation is enabled.
771    ///
772    /// If set to true, the SDK will periodically consolidate a token's outputs
773    /// once their count exceeds [`Self::min_outputs_threshold`]. Otherwise, no
774    /// automatic consolidation is performed.
775    ///
776    /// Default value is true.
777    pub auto_enabled: bool,
778    /// Number of token outputs to produce when token-output auto-consolidation
779    /// fires.
780    ///
781    /// Instead of collapsing a token's outputs into a single output (which
782    /// serializes subsequent payments), the SDK splits the consolidated balance
783    /// across this many outputs of roughly equal value. Higher values preserve
784    /// concurrency for parallel sends at the cost of a slightly larger output
785    /// set.
786    ///
787    /// Must be >= 1 and strictly less than [`Self::min_outputs_threshold`].
788    ///
789    /// Default value is 5.
790    pub target_output_count: u32,
791    /// Output count that triggers per-token auto-consolidation.
792    ///
793    /// Auto-consolidation triggers for a token when its available output count
794    /// strictly exceeds this threshold.
795    ///
796    /// Must be greater than 1.
797    ///
798    /// Default value is 50.
799    pub min_outputs_threshold: u32,
800}
801
802/// A stable token that can be used for automatic balance conversion.
803#[derive(Debug, Clone)]
804#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
805pub struct StableBalanceToken {
806    /// Integrator-defined display label for the token, e.g. "USD".
807    ///
808    /// This is a short, human-readable name set by the integrator for display purposes.
809    /// It is **not** a canonical Spark token ticker — it has no protocol-level meaning.
810    /// Labels must be unique within the [`StableBalanceConfig::tokens`] list.
811    pub label: String,
812
813    /// The full token identifier string used for conversions.
814    pub token_identifier: String,
815}
816
817/// Configuration for automatic conversion of Bitcoin to stable tokens.
818///
819/// When configured, the SDK automatically monitors the Bitcoin balance after each
820/// wallet sync. When the balance exceeds the configured threshold plus the reserved
821/// amount, the SDK automatically converts the excess balance (above the reserve)
822/// to the active stable token.
823///
824/// When the balance is held in a stable token, Bitcoin payments can still be sent.
825/// The SDK automatically detects when there's not enough Bitcoin balance to cover a
826/// payment and auto-populates the token-to-Bitcoin conversion options to facilitate
827/// the payment.
828///
829/// The active token can be changed at runtime via [`UpdateUserSettingsRequest`].
830#[derive(Debug, Clone)]
831#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
832pub struct StableBalanceConfig {
833    /// Available tokens that can be used for stable balance.
834    pub tokens: Vec<StableBalanceToken>,
835
836    /// The label of the token to activate by default.
837    ///
838    /// If `None`, stable balance starts deactivated. The user can activate it
839    /// at runtime via [`UpdateUserSettingsRequest`]. If a user setting is cached
840    /// locally, it takes precedence over this default.
841    #[cfg_attr(feature = "uniffi", uniffi(default = None))]
842    pub default_active_label: Option<String>,
843
844    /// The minimum sats balance that triggers auto-conversion.
845    ///
846    /// If not provided, uses the minimum from conversion limits.
847    /// If provided but less than the conversion limit minimum, the limit minimum is used.
848    #[cfg_attr(feature = "uniffi", uniffi(default = None))]
849    pub threshold_sats: Option<u64>,
850
851    /// Maximum slippage in basis points (1/100 of a percent).
852    ///
853    /// Defaults to 10 bps (0.1%) if not set.
854    #[cfg_attr(feature = "uniffi", uniffi(default = None))]
855    pub max_slippage_bps: Option<u32>,
856}
857
858/// Specifies how to update the active stable balance token.
859#[derive(Debug, Clone)]
860#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
861pub enum StableBalanceActiveLabel {
862    /// Activate stable balance with the given label.
863    Set { label: String },
864    /// Deactivate stable balance.
865    Unset,
866}
867
868/// Configuration for a custom Spark environment.
869///
870/// When set on [`Config`], overrides the default Spark operator pool,
871/// service provider, threshold, and token settings. This allows connecting
872/// to alternative Spark deployments (e.g. dev/staging environments).
873#[derive(Debug, Clone)]
874#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
875pub struct SparkConfig {
876    /// Hex-encoded identifier of the coordinator operator.
877    pub coordinator_identifier: String,
878    /// The FROST signing threshold (e.g. 2 of 3).
879    pub threshold: u32,
880    /// The set of signing operators.
881    pub signing_operators: Vec<SparkSigningOperator>,
882    /// Service provider (SSP) configuration.
883    pub ssp_config: SparkSspConfig,
884    /// Expected bond amount in sats for token withdrawals.
885    pub expected_withdraw_bond_sats: u64,
886    /// Expected relative block locktime for token withdrawals.
887    pub expected_withdraw_relative_block_locktime: u64,
888    /// Cap on the inputs a single token transaction may spend. A send needing
889    /// more first consolidates the wallet's token outputs. Unset uses the SDK
890    /// default (500).
891    pub max_token_transaction_inputs: Option<u32>,
892}
893
894/// A Spark signing operator.
895#[derive(Debug, Clone)]
896#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
897pub struct SparkSigningOperator {
898    /// Sequential operator ID (0-indexed).
899    pub id: u32,
900    /// Hex-encoded 32-byte FROST identifier.
901    pub identifier: String,
902    /// gRPC address of the operator (e.g. `https://0.spark.lightspark.com`).
903    pub address: String,
904    /// Hex-encoded compressed public key of the operator.
905    pub identity_public_key: String,
906    /// Optional PEM-encoded CA certificate for TLS verification.
907    /// When set, the SDK uses this CA to verify the operator's TLS certificate
908    /// instead of the system/default roots. Useful for local development with
909    /// self-signed certificates.
910    pub ca_cert_pem: Option<String>,
911}
912
913/// Configuration for the Spark Service Provider (SSP).
914#[derive(Debug, Clone)]
915#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
916pub struct SparkSspConfig {
917    /// Base URL of the SSP GraphQL API.
918    pub base_url: String,
919    /// Hex-encoded compressed public key of the SSP.
920    pub identity_public_key: String,
921    /// Optional GraphQL schema endpoint path (e.g. "graphql/spark/rc").
922    /// Defaults to the hardcoded schema endpoint if not set.
923    pub schema_endpoint: Option<String>,
924}
925
926impl Config {
927    /// Validates the configuration.
928    ///
929    /// Returns an error if any configuration values are invalid.
930    pub fn validate(&self) -> Result<(), SdkError> {
931        if self.max_concurrent_claims == 0 {
932            return Err(SdkError::InvalidInput(
933                "max_concurrent_claims must be greater than 0".to_string(),
934            ));
935        }
936
937        if let Some(sb) = &self.stable_balance_config {
938            if sb.tokens.is_empty() {
939                return Err(SdkError::InvalidInput(
940                    "tokens must not be empty".to_string(),
941                ));
942            }
943
944            let mut seen_labels = HashSet::new();
945            let mut seen_identifiers = HashSet::new();
946            for token in &sb.tokens {
947                if token.label.is_empty() {
948                    return Err(SdkError::InvalidInput(
949                        "token label must not be empty".to_string(),
950                    ));
951                }
952                if token.token_identifier.is_empty() {
953                    return Err(SdkError::InvalidInput(
954                        "token_identifier must not be empty".to_string(),
955                    ));
956                }
957                if !seen_labels.insert(&token.label) {
958                    return Err(SdkError::InvalidInput(format!(
959                        "tokens contains duplicate label: {}",
960                        token.label
961                    )));
962                }
963                if !seen_identifiers.insert(&token.token_identifier) {
964                    return Err(SdkError::InvalidInput(format!(
965                        "tokens contains duplicate token_identifier: {}",
966                        token.token_identifier
967                    )));
968                }
969            }
970
971            if let Some(bps) = sb.max_slippage_bps
972                && bps > 10000
973            {
974                return Err(SdkError::InvalidInput(
975                    "max_slippage_bps must be <= 10000".to_string(),
976                ));
977            }
978
979            if let Some(default_label) = &sb.default_active_label
980                && !seen_labels.contains(default_label)
981            {
982                return Err(SdkError::InvalidInput(format!(
983                    "default_active_label '{default_label}' not found in tokens list"
984                )));
985            }
986        }
987
988        let token_opt = &self.token_optimization_config;
989        if token_opt.min_outputs_threshold <= 1 {
990            return Err(SdkError::InvalidInput(
991                "token optimization minimum outputs threshold must be greater than 1".to_string(),
992            ));
993        }
994        if token_opt.target_output_count < 1 {
995            return Err(SdkError::InvalidInput(
996                "token optimization target output count must be at least 1".to_string(),
997            ));
998        }
999        if token_opt.target_output_count >= token_opt.min_outputs_threshold {
1000            return Err(SdkError::InvalidInput(
1001                "token optimization target output count must be less than the minimum outputs threshold".to_string(),
1002            ));
1003        }
1004
1005        if let Some(cc) = &self.cross_chain_config {
1006            if self.network != Network::Mainnet {
1007                return Err(SdkError::InvalidInput(format!(
1008                    "Cross-chain sends are only available on Mainnet, not on {}.",
1009                    self.network,
1010                )));
1011            }
1012            if let Some(bps) = cc.default_slippage_bps
1013                && !(crate::cross_chain::MIN_CROSS_CHAIN_SLIPPAGE_BPS
1014                    ..=crate::cross_chain::MAX_CROSS_CHAIN_SLIPPAGE_BPS)
1015                    .contains(&bps)
1016            {
1017                return Err(SdkError::InvalidInput(format!(
1018                    "Default cross-chain slippage must be between {} and {} basis points, but got {bps}.",
1019                    crate::cross_chain::MIN_CROSS_CHAIN_SLIPPAGE_BPS,
1020                    crate::cross_chain::MAX_CROSS_CHAIN_SLIPPAGE_BPS,
1021                )));
1022            }
1023            if let Some(bps) = cc.default_target_overpay_bps
1024                && !(crate::cross_chain::MIN_TARGET_OVERPAY_BPS
1025                    ..=crate::cross_chain::MAX_TARGET_OVERPAY_BPS)
1026                    .contains(&bps)
1027            {
1028                return Err(SdkError::InvalidInput(format!(
1029                    "Default cross-chain target-overpay must be between {} and {} basis points, but got {bps}.",
1030                    crate::cross_chain::MIN_TARGET_OVERPAY_BPS,
1031                    crate::cross_chain::MAX_TARGET_OVERPAY_BPS,
1032                )));
1033            }
1034        }
1035
1036        Ok(())
1037    }
1038
1039    pub(crate) fn get_all_external_input_parsers(&self) -> Vec<ExternalInputParser> {
1040        let mut external_input_parsers = Vec::new();
1041        if self.use_default_external_input_parsers {
1042            let default_parsers = DEFAULT_EXTERNAL_INPUT_PARSERS
1043                .iter()
1044                .map(|(id, regex, url)| ExternalInputParser {
1045                    provider_id: (*id).to_string(),
1046                    input_regex: (*regex).to_string(),
1047                    parser_url: (*url).to_string(),
1048                })
1049                .collect::<Vec<_>>();
1050            external_input_parsers.extend(default_parsers);
1051        }
1052        external_input_parsers.extend(self.external_input_parsers.clone().unwrap_or_default());
1053
1054        external_input_parsers
1055    }
1056}
1057
1058#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1059#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1060pub enum MaxFee {
1061    // Fixed fee amount in sats
1062    Fixed { amount: u64 },
1063    // Relative fee rate in satoshis per vbyte
1064    Rate { sat_per_vbyte: u64 },
1065    // Fastest network recommended fee at the time of claim, with a leeway in satoshis per vbyte
1066    NetworkRecommended { leeway_sat_per_vbyte: u64 },
1067}
1068
1069impl MaxFee {
1070    pub(crate) async fn to_fee(&self, client: &dyn BitcoinChainService) -> Result<Fee, SdkError> {
1071        match self {
1072            MaxFee::Fixed { amount } => Ok(Fee::Fixed { amount: *amount }),
1073            MaxFee::Rate { sat_per_vbyte } => Ok(Fee::Rate {
1074                sat_per_vbyte: *sat_per_vbyte,
1075            }),
1076            MaxFee::NetworkRecommended {
1077                leeway_sat_per_vbyte,
1078            } => {
1079                let recommended_fees = client.recommended_fees().await?;
1080                let max_fee_rate = recommended_fees
1081                    .fastest_fee
1082                    .saturating_add(*leeway_sat_per_vbyte);
1083                Ok(Fee::Rate {
1084                    sat_per_vbyte: max_fee_rate,
1085                })
1086            }
1087        }
1088    }
1089}
1090
1091#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1092#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1093pub enum Fee {
1094    // Fixed fee amount in sats
1095    Fixed { amount: u64 },
1096    // Relative fee rate in satoshis per vbyte
1097    Rate { sat_per_vbyte: u64 },
1098}
1099
1100impl Fee {
1101    pub fn to_sats(&self, vbytes: u64) -> u64 {
1102        match self {
1103            Fee::Fixed { amount } => *amount,
1104            Fee::Rate { sat_per_vbyte } => sat_per_vbyte.saturating_mul(vbytes),
1105        }
1106    }
1107}
1108
1109/// Why an instant (0-conf) claim was declined.
1110#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1111#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1112pub enum InstantClaimDeclineReason {
1113    /// The SSP offered no 0-conf fulfillment plan for the deposit.
1114    NoPlan,
1115    /// The SSP spread exceeded the ceiling (`max_bps`). The instant claim can be
1116    /// retried with a higher ceiling. `quoted_bps` / `quoted_sats` are the spread
1117    /// the SSP quoted at the time.
1118    FeeExceeded {
1119        max_bps: u32,
1120        quoted_bps: u32,
1121        quoted_sats: u64,
1122    },
1123    /// The claim submission failed with an unknown outcome.
1124    SubmissionFailed,
1125}
1126
1127/// State of an instant (0-conf) claim attempt on a deposit.
1128#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1129#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1130pub enum InstantClaimStatus {
1131    /// The instant claim was declined. The deposit falls through to the normal
1132    /// claim once it matures; the background sync may re-attempt an instant claim
1133    /// only when the reason permits (see [`InstantClaimDeclineReason`]).
1134    Declined { reason: InstantClaimDeclineReason },
1135    /// An instant claim was submitted and is settling. The deposit must not be
1136    /// re-claimed (instant or normal) until the claim settles and it is reconciled
1137    /// out. Carries the SSP claim id.
1138    Submitted { claim_id: String },
1139}
1140
1141#[derive(Debug, Clone, Serialize, Deserialize)]
1142#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1143pub struct DepositInfo {
1144    /// Transaction id of the on-chain output the deposit came from.
1145    pub txid: String,
1146    /// Index of that output within its transaction.
1147    pub vout: u32,
1148    /// Deposit value in satoshis.
1149    pub amount_sats: u64,
1150    /// Whether the deposit has enough confirmations to be claimed.
1151    pub is_mature: bool,
1152    /// Raw refund transaction, once one has been created.
1153    pub refund_tx: Option<String>,
1154    /// Transaction id of the refund, once one has been created.
1155    pub refund_tx_id: Option<String>,
1156    /// Why the last claim attempt failed. Unset while none has failed.
1157    pub claim_error: Option<DepositClaimError>,
1158    /// Unset when no instant claim has been attempted.
1159    pub instant_claim_status: Option<InstantClaimStatus>,
1160}
1161
1162#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1163pub struct ClaimDepositRequest {
1164    pub txid: String,
1165    pub vout: u32,
1166    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1167    pub max_fee: Option<MaxFee>,
1168    /// Set to request an instant (0-conf) claim instead of waiting for the
1169    /// deposit to mature, bounding the SSP spread at this many basis points of
1170    /// the deposit value (100 bps = 1%). When set, the call takes the instant
1171    /// path and `max_fee` is ignored.
1172    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1173    pub max_instant_fee_bps: Option<u32>,
1174}
1175
1176#[derive(Debug, Clone, Serialize)]
1177#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1178pub struct ClaimDepositResponse {
1179    /// The settled claim payment. Present for a standard claim, which completes
1180    /// synchronously. Absent for an instant claim, whose transfer settles
1181    /// asynchronously: watch for the payment via events or `list_payments`.
1182    pub payment: Option<Payment>,
1183}
1184
1185#[derive(Debug, Clone, Serialize)]
1186#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1187pub struct RefundDepositRequest {
1188    pub txid: String,
1189    pub vout: u32,
1190    pub destination_address: String,
1191    pub fee: Fee,
1192}
1193
1194#[derive(Debug, Clone, Serialize)]
1195#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1196pub struct RefundDepositResponse {
1197    pub tx_id: String,
1198    pub tx_hex: String,
1199}
1200
1201#[derive(Debug, Clone, Serialize)]
1202#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1203pub struct ListUnclaimedDepositsRequest {}
1204
1205#[derive(Debug, Clone, Serialize)]
1206#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1207pub struct ListUnclaimedDepositsResponse {
1208    pub deposits: Vec<DepositInfo>,
1209}
1210
1211/// The available providers for buying Bitcoin
1212/// Request to buy Bitcoin using an external provider.
1213///
1214/// Each variant carries only the parameters relevant to that provider.
1215#[derive(Debug, Clone)]
1216#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1217pub enum BuyBitcoinRequest {
1218    /// `MoonPay`: Fiat-to-Bitcoin via credit card, Apple Pay, etc.
1219    /// Uses an on-chain deposit address.
1220    Moonpay {
1221        /// Lock the purchase to a specific amount in satoshis.
1222        locked_amount_sat: Option<u64>,
1223        /// Custom redirect URL after purchase completion.
1224        redirect_url: Option<String>,
1225    },
1226    /// `CashApp`: Pay via the Lightning Network.
1227    /// Generates a bolt11 invoice for the given amount and returns a
1228    /// `cash.app` deep link. Only available on mainnet.
1229    ///
1230    /// The amount is required. With an amountless invoice, Cash App only
1231    /// lets the payer fund from their existing Cash App BTC balance. With
1232    /// a fixed-amount invoice, Cash App opens up funding via fiat balance
1233    /// and debit card.
1234    CashApp {
1235        /// Amount in satoshis for the Lightning invoice. Must be non-zero.
1236        amount_sats: u64,
1237    },
1238}
1239
1240impl Default for BuyBitcoinRequest {
1241    fn default() -> Self {
1242        Self::Moonpay {
1243            locked_amount_sat: None,
1244            redirect_url: None,
1245        }
1246    }
1247}
1248
1249/// Response containing a URL to complete the Bitcoin purchase
1250#[derive(Debug, Clone, Serialize)]
1251#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1252pub struct BuyBitcoinResponse {
1253    /// The URL to open in a browser to complete the purchase
1254    pub url: String,
1255}
1256
1257/// Response from refunding pending conversions.
1258#[derive(Debug, Clone, Serialize, Default)]
1259#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1260pub struct RefundPendingConversionsResponse {
1261    /// Conversions successfully refunded this pass.
1262    pub refunded: u32,
1263    /// Conversions not clawed back this pass: held back by a safety window, or
1264    /// found to have executed after all. Only the former are retried.
1265    pub skipped: u32,
1266    /// Conversions whose clawback did not complete this pass (rejected or
1267    /// errored; funds not returned). The next pass will retry them.
1268    pub failed: u32,
1269}
1270
1271/// Request for a payment link that sends USDC/USDT to an external-chain
1272/// recipient, funded by Cash App over Lightning.
1273///
1274/// The user pays the returned URL, and the cross-chain provider delivers the
1275/// stablecoin to `address`. No funds move through the Spark wallet. Only
1276/// available on mainnet.
1277#[derive(Debug, Clone)]
1278#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1279pub struct PreparePaymentLinkRequest {
1280    /// Recipient address on the destination chain (e.g. an EVM `0x...` address).
1281    pub address: String,
1282    /// The destination route from calling `get_cross_chain_routes()` with the
1283    /// `CrossChainRouteFilter::PaymentLink` filter. Selects the destination chain
1284    /// + asset (e.g. USDC on Base).
1285    pub route: CrossChainRoutePair,
1286    /// Amount in the destination asset's base units, per the route's
1287    /// `decimals`. These routes deliver USD-pegged stablecoins, so at parity
1288    /// this is the USD value: `1_000_000` is 1 USDC (6 decimals), about $1.
1289    ///
1290    /// With the default fee policy the recipient receives this net amount.
1291    /// With `FeesIncluded` it is the amount the payer deposits.
1292    pub amount: u128,
1293    /// Whether fees are added on top of `amount` (`FeesExcluded`, the default)
1294    /// or deducted from it (`FeesIncluded`).
1295    pub fee_policy: Option<FeePolicy>,
1296    /// Maximum slippage tolerance in basis points. Falls back to the SDK
1297    /// default when unset.
1298    pub max_slippage_bps: Option<u32>,
1299}
1300
1301/// Response to a [`PreparePaymentLinkRequest`]. Mirrors `BuyBitcoinResponse`
1302/// (a payable `url`) plus the quote so the caller can display the expected
1303/// delivery and fees.
1304#[derive(Debug, Clone, Serialize)]
1305#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1306pub struct PreparePaymentLinkResponse {
1307    /// The URL to open in a browser; paying it delivers the stablecoin.
1308    pub url: String,
1309    /// Sats the payer deposits through the fiat rail.
1310    pub amount_sats: u64,
1311    /// Estimated amount delivered to the recipient, in `asset` base units.
1312    pub estimated_out: u128,
1313    /// The destination stablecoin symbol (e.g. `USDC`). `estimated_out` is
1314    /// denominated in it.
1315    pub asset: String,
1316    /// Provider service fee, in `service_fee_asset` base units.
1317    pub service_fee_amount: u128,
1318    /// Denomination of `service_fee_amount`. `None` means sats: Boltz
1319    /// denominates its fee in sats, Orchestra in the stablecoin.
1320    pub service_fee_asset: Option<String>,
1321    /// RFC3339 timestamp after which the quote is no longer valid.
1322    pub expires_at: String,
1323}
1324
1325impl std::fmt::Display for MaxFee {
1326    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1327        match self {
1328            MaxFee::Fixed { amount } => write!(f, "Fixed: {amount}"),
1329            MaxFee::Rate { sat_per_vbyte } => write!(f, "Rate: {sat_per_vbyte}"),
1330            MaxFee::NetworkRecommended {
1331                leeway_sat_per_vbyte,
1332            } => write!(f, "NetworkRecommended: {leeway_sat_per_vbyte}"),
1333        }
1334    }
1335}
1336
1337#[derive(Debug, Clone)]
1338#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1339pub struct Credentials {
1340    pub username: String,
1341    pub password: String,
1342}
1343
1344/// Request to get the balance of the wallet
1345#[derive(Debug, Clone)]
1346#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1347pub struct GetInfoRequest {
1348    /// When `Some(true)`, and `background_tasks_enabled` is `true`, the call
1349    /// waits for the initial Full sync to complete before returning.
1350    ///
1351    /// When `background_tasks_enabled` is `false`, setting this to `Some(true)`
1352    /// is rejected with an invalid-input error. There is no background sync to
1353    /// wait on; call `sync_wallet` explicitly first if you need fresh state.
1354    pub ensure_synced: Option<bool>,
1355}
1356
1357/// Response containing the balance of the wallet
1358#[derive(Debug, Clone, Serialize)]
1359#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1360pub struct GetInfoResponse {
1361    /// The identity public key of the wallet as a hex string
1362    pub identity_pubkey: String,
1363    /// The balance in satoshis
1364    pub balance_sats: u64,
1365    /// The balances of the tokens in the wallet keyed by the token identifier
1366    pub token_balances: HashMap<String, TokenBalance>,
1367}
1368
1369#[derive(Debug, Clone, Serialize, Deserialize)]
1370#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1371pub struct TokenBalance {
1372    pub balance: u128,
1373    pub token_metadata: TokenMetadata,
1374}
1375
1376#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1377#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1378pub struct TokenMetadata {
1379    pub identifier: String,
1380    /// Hex representation of the issuer public key
1381    pub issuer_public_key: String,
1382    pub name: String,
1383    pub ticker: String,
1384    /// Number of decimals the token uses
1385    pub decimals: u32,
1386    pub max_supply: u128,
1387    pub is_freezable: bool,
1388}
1389
1390/// Request to sync the wallet with the Spark network
1391#[derive(Debug, Clone)]
1392#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1393pub struct SyncWalletRequest {}
1394
1395/// Response from synchronizing the wallet
1396#[derive(Debug, Clone, Serialize)]
1397#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1398pub struct SyncWalletResponse {}
1399
1400#[derive(Debug, Clone, Serialize)]
1401#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1402pub enum ReceivePaymentMethod {
1403    SparkAddress,
1404    SparkInvoice {
1405        /// Amount to receive. Denominated in sats if token identifier is empty, otherwise in the token base units
1406        amount: Option<u128>,
1407        /// The presence of this field indicates that the payment is for a token
1408        /// If empty, it is a Bitcoin payment
1409        token_identifier: Option<String>,
1410        /// The expiry time of the invoice as a unix timestamp in seconds
1411        expiry_time: Option<u64>,
1412        /// A description to embed in the invoice.
1413        description: Option<String>,
1414        /// If set, the invoice may only be fulfilled by a payer with this public key
1415        sender_public_key: Option<String>,
1416    },
1417    BitcoinAddress {
1418        /// If true, rotate to a new deposit address. Previous ones remain valid.
1419        /// If false or absent, return the existing address (creating one if none
1420        /// exists yet).
1421        new_address: Option<bool>,
1422    },
1423    Bolt11Invoice {
1424        description: String,
1425        amount_sats: Option<u64>,
1426        /// The expiry of the invoice as a duration in seconds
1427        expiry_secs: Option<u32>,
1428        /// If set, creates a HODL invoice with this payment hash (hex-encoded).
1429        /// The payer's HTLC will be held until the preimage is provided via
1430        /// `claim_htlc_payment` or the HTLC expires.
1431        payment_hash: Option<String>,
1432        /// Spark identity public key that will receive the payment.
1433        /// If absent, the connected wallet's identity public key is used.
1434        receiver_identity_public_key: Option<String>,
1435    },
1436}
1437
1438#[derive(Debug, Clone, Serialize)]
1439#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1440pub enum SendPaymentMethod {
1441    BitcoinAddress {
1442        address: BitcoinAddressDetails,
1443        fee_quote: SendOnchainFeeQuote,
1444    },
1445    Bolt11Invoice {
1446        invoice_details: Bolt11InvoiceDetails,
1447        spark_transfer_fee_sats: Option<u64>,
1448        lightning_fee_sats: u64,
1449    }, // should be replaced with the parsed invoice
1450    SparkAddress {
1451        address: String,
1452        /// Fee to pay for the transaction
1453        /// Denominated in sats if token identifier is empty, otherwise in the token base units
1454        fee: u128,
1455        /// The presence of this field indicates that the payment is for a token
1456        /// If empty, it is a Bitcoin payment
1457        token_identifier: Option<String>,
1458    },
1459    SparkInvoice {
1460        spark_invoice_details: SparkInvoiceDetails,
1461        /// Fee to pay for the transaction
1462        /// Denominated in sats if token identifier is empty, otherwise in the token base units
1463        fee: u128,
1464        /// The presence of this field indicates that the payment is for a token
1465        /// If empty, it is a Bitcoin payment
1466        token_identifier: Option<String>,
1467    },
1468    /// A cross-chain send via a bridge/swap provider.
1469    CrossChainAddress {
1470        /// The route selected for this cross-chain send (includes provider, chain, asset).
1471        route: CrossChainRoutePair,
1472        /// Raw destination address (e.g. `0xabc...`).
1473        recipient_address: String,
1474        /// Amount routed to the provider, in the route's source-asset units
1475        /// (Boltz invoice sats; Orchestra deposit sats/token). On the
1476        /// token-conversion path (both `FeesIncluded` and `FeesExcluded`)
1477        /// the dispatcher overrides this with the wallet-side token debit
1478        /// when the source token and destination asset form a USD-stable pair.
1479        amount_in: u128,
1480        /// `amount_in` expressed in the cross-chain (destination) asset's
1481        /// base units, via the same rate the SDK used at prepare time.
1482        asset_amount_in: u128,
1483        /// Estimated recipient amount in cross-chain asset base units.
1484        estimated_out: u128,
1485        /// Prepare-time total user-visible fee in cross-chain asset base units.
1486        /// Covers provider spread + bridge/gas + DEX slippage. On the
1487        /// token-conversion path it also rolls in the LN routing budget; on
1488        /// the direct path that budget lives separately in
1489        /// `source_transfer_fee_sats`.
1490        fee_amount: u128,
1491        /// Provider's own service fee/spread in its native denomination.
1492        service_fee_amount: u128,
1493        /// Asset which service fee is denominated in. Unset means BTC sats.
1494        service_fee_asset: Option<String>,
1495        /// Sats budget for moving the amount in from the wallet to the provider.
1496        source_transfer_fee_sats: u64,
1497        /// Fee mode the prepare ran under; the send stage matches.
1498        fee_mode: CrossChainFeeMode,
1499        /// ISO8601 timestamp after which the quote is no longer valid.
1500        expires_at: String,
1501        /// Provider-internal state, produced when preparing and consumed
1502        /// when sending.
1503        provider_context: CrossChainProviderContext,
1504    },
1505}
1506
1507#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1508#[derive(Debug, Clone, Serialize, Deserialize)]
1509pub struct SendOnchainFeeQuote {
1510    pub id: String,
1511    pub expires_at: u64,
1512    pub speed_fast: SendOnchainSpeedFeeQuote,
1513    pub speed_medium: SendOnchainSpeedFeeQuote,
1514    pub speed_slow: SendOnchainSpeedFeeQuote,
1515}
1516
1517#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1518#[derive(Debug, Clone, Serialize, Deserialize)]
1519pub struct SendOnchainSpeedFeeQuote {
1520    pub user_fee_sat: u64,
1521    pub l1_broadcast_fee_sat: u64,
1522}
1523
1524impl SendOnchainSpeedFeeQuote {
1525    pub fn total_fee_sat(&self) -> u64 {
1526        self.user_fee_sat.saturating_add(self.l1_broadcast_fee_sat)
1527    }
1528}
1529
1530#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1531pub struct ReceivePaymentRequest {
1532    pub payment_method: ReceivePaymentMethod,
1533}
1534
1535#[derive(Debug, Clone, Serialize)]
1536#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1537pub struct ReceivePaymentResponse {
1538    pub payment_request: String,
1539    /// Fee to pay to receive the payment
1540    /// Denominated in sats or token base units
1541    pub fee: u128,
1542}
1543
1544#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1545pub struct PrepareLnurlPayRequest {
1546    /// The amount to send. Denominated in satoshis, or in token base units
1547    /// when `token_identifier` is set.
1548    pub amount: u128,
1549    pub pay_request: LnurlPayRequestDetails,
1550    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1551    pub comment: Option<String>,
1552    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1553    pub validate_success_action_url: Option<bool>,
1554    /// The token identifier when sending a token amount with conversion.
1555    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1556    pub token_identifier: Option<String>,
1557    /// If provided, the payment will include a token conversion step before sending the payment
1558    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1559    pub conversion_options: Option<ConversionOptions>,
1560    /// How fees are handled. See [`FeePolicy`]. Defaults to `FeesExcluded`.
1561    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1562    pub fee_policy: Option<FeePolicy>,
1563}
1564
1565#[derive(Debug, Clone)]
1566#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1567pub struct PrepareLnurlPayResponse {
1568    /// The amount for the payment, always denominated in sats, even when a
1569    /// `token_identifier` and conversion are present.
1570    /// When a conversion is present, the token input amount is available in
1571    /// `conversion_estimate.amount_in`.
1572    pub amount_sats: u64,
1573    pub comment: Option<String>,
1574    pub pay_request: LnurlPayRequestDetails,
1575    /// The fee in satoshis. For `FeesIncluded` operations, this represents the total fee
1576    /// (including potential overpayment).
1577    pub fee_sats: u64,
1578    pub invoice_details: Bolt11InvoiceDetails,
1579    pub success_action: Option<SuccessAction>,
1580    /// When set, the payment will include a token conversion step before sending the payment
1581    pub conversion_estimate: Option<ConversionEstimate>,
1582    /// The fee policy actually applied. May differ from the request — e.g.,
1583    /// LNURL sends with `token_identifier` set + conversion are always
1584    /// `FeesIncluded` (explicit `FeesExcluded` is rejected).
1585    pub fee_policy: FeePolicy,
1586}
1587
1588#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1589pub struct LnurlPayRequest {
1590    pub prepare_response: PrepareLnurlPayResponse,
1591    /// If set, providing the same idempotency key for multiple requests will ensure that only one
1592    /// payment is made. If an idempotency key is re-used, the same payment will be returned.
1593    /// The idempotency key must be a valid UUID.
1594    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1595    pub idempotency_key: Option<String>,
1596}
1597
1598#[derive(Debug, Serialize)]
1599#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1600pub struct LnurlPayResponse {
1601    pub payment: Payment,
1602    pub success_action: Option<SuccessActionProcessed>,
1603}
1604
1605#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1606pub struct BuildUnsignedLnurlPayPackageRequest {
1607    pub prepare_response: PrepareLnurlPayResponse,
1608}
1609
1610#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1611pub struct PublishSignedLnurlPayPackageRequest {
1612    pub signed_package: SignedTransferPackage,
1613}
1614
1615#[allow(clippy::large_enum_variant)]
1616#[derive(Debug)]
1617#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1618pub enum PublishSignedLnurlPayResponse {
1619    SwapCompleted,
1620    PaymentSent { response: LnurlPayResponse },
1621}
1622
1623#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1624pub struct LnurlWithdrawRequest {
1625    /// The amount to withdraw in satoshis
1626    /// Must be within the min and max withdrawable limits
1627    pub amount_sats: u64,
1628    pub withdraw_request: LnurlWithdrawRequestDetails,
1629    /// If set, the function will return the payment if it is still pending after this
1630    /// number of seconds. If unset, the function will return immediately after
1631    /// initiating the LNURL withdraw.
1632    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1633    pub completion_timeout_secs: Option<u32>,
1634}
1635
1636#[derive(Debug, Serialize)]
1637#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1638pub struct LnurlWithdrawResponse {
1639    /// The Lightning invoice generated for the LNURL withdraw
1640    pub payment_request: String,
1641    pub payment: Option<Payment>,
1642}
1643
1644/// Represents the payment LNURL info
1645#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
1646#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1647pub struct LnurlPayInfo {
1648    pub ln_address: Option<String>,
1649    pub comment: Option<String>,
1650    pub domain: Option<String>,
1651    pub metadata: Option<String>,
1652    pub processed_success_action: Option<SuccessActionProcessed>,
1653    pub raw_success_action: Option<SuccessAction>,
1654}
1655
1656/// Represents the withdraw LNURL info
1657#[derive(Clone, Debug, Deserialize, Serialize)]
1658#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1659pub struct LnurlWithdrawInfo {
1660    pub withdraw_url: String,
1661}
1662
1663impl LnurlPayInfo {
1664    pub fn extract_description(&self) -> Option<String> {
1665        let Some(metadata) = &self.metadata else {
1666            return None;
1667        };
1668
1669        let Ok(metadata) = serde_json::from_str::<Vec<Vec<Value>>>(metadata) else {
1670            return None;
1671        };
1672
1673        for arr in metadata {
1674            if arr.len() != 2 {
1675                continue;
1676            }
1677            if let (Some(key), Some(value)) = (arr[0].as_str(), arr[1].as_str())
1678                && key == "text/plain"
1679            {
1680                return Some(value.to_string());
1681            }
1682        }
1683
1684        None
1685    }
1686}
1687
1688/// Specifies how fees are handled in a payment.
1689///
1690/// "Fees" are the wallet's sender-paid fees (Lightning routing, on-chain,
1691/// Spark transfer). They do not include provider spreads or destination-chain
1692/// costs on cross-chain routes; those are reported separately via
1693/// `estimated_out` on the prepare response and are not deterministic.
1694/// `FeePolicy` only controls the wallet's spend accounting.
1695#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
1696#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1697pub enum FeePolicy {
1698    /// Fees are added on top of `amount`. Wallet's total spend is
1699    /// `amount + fees`. For direct sat sends, the recipient receives exactly
1700    /// `amount`. Default.
1701    #[default]
1702    FeesExcluded,
1703    /// Fees are deducted from `amount`. Wallet's total spend is `amount`.
1704    /// Use this to drain a balance — pass `amount = balance` and the wallet
1705    /// spends exactly that.
1706    FeesIncluded,
1707}
1708
1709#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1710#[derive(Debug, Clone, Serialize, Deserialize)]
1711pub enum OnchainConfirmationSpeed {
1712    Fast,
1713    Medium,
1714    Slow,
1715}
1716
1717/// The payment destination. Either a raw string (bolt11, spark address, BIP-21,
1718/// cross-chain URI, etc.) that is parsed internally, or a structured
1719/// cross-chain destination with explicit chain + asset selection.
1720#[derive(Debug, Clone, Serialize)]
1721#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1722pub enum PaymentRequest {
1723    /// Unparsed user input string (bolt11, spark address, BIP-21, cross-chain URI, etc.)
1724    Input { input: String },
1725    /// Cross-chain send with a selected route from `get_cross_chain_routes()`.
1726    /// Amount comes from `PrepareSendPaymentRequest.amount`, not here.
1727    CrossChain {
1728        address: String,
1729        route: CrossChainRoutePair,
1730        /// Maximum slippage tolerance in basis points (1/100 of a percent)
1731        /// for the cross-chain quote. Must be in `10..=500`. Falls back to
1732        /// [`Config::default_slippage_bps`] when `None`, which itself
1733        /// defaults to 100 bps (1%) when unset.
1734        max_slippage_bps: Option<u32>,
1735        /// Target-overpay pad in basis points applied on `FeesExcluded`
1736        /// conversion sends. Inflates the destination target before quoting
1737        /// so the recipient lands at or above the user's requested amount
1738        /// despite provider slippage. Must be in `0..=500`. Falls back to
1739        /// [`CrossChainConfig::default_target_overpay_bps`] when `None`,
1740        /// which itself defaults to 15 bps.
1741        target_overpay_bps: Option<u32>,
1742    },
1743}
1744
1745#[allow(clippy::large_enum_variant)]
1746#[derive(Debug, Clone, Serialize, Deserialize)]
1747#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1748pub enum UnsignedTransferPackage {
1749    Swap {
1750        prepare_transfer: crate::signer::ExternalPrepareTransferRequest,
1751        target_amounts: Vec<u64>,
1752        amount_sat: u64,
1753        fee_sat: u64,
1754    },
1755    Transfer {
1756        prepare_transfer: crate::signer::ExternalPrepareTransferRequest,
1757        amount_sat: u64,
1758        fee_sat: u64,
1759        target: TransferTarget,
1760    },
1761    Token {
1762        prepare_token_transaction: crate::signer::ExternalPrepareTokenTransactionRequest,
1763        token_context: Vec<u8>,
1764        token_identifier: String,
1765        amount: u128,
1766        fee: u128,
1767        /// When set, this package re-shapes the wallet's token outputs instead of
1768        /// sending a payment. Publishing it returns `SwapCompleted`: rebuild the
1769        /// original send from the same prepare response and submit again.
1770        is_swap: bool,
1771    },
1772    /// One token transaction paying several recipients. Publishing it returns
1773    /// `PaymentsSent` with one payment per recipient.
1774    TokenBatch {
1775        prepare_token_transaction: crate::signer::ExternalPrepareTokenTransactionRequest,
1776        token_context: Vec<u8>,
1777        /// What the batch debits, per token. A batch spanning tokens has no
1778        /// single amount to report.
1779        totals: Vec<BatchTotal>,
1780        /// When set, this package re-shapes the wallet's token outputs instead of
1781        /// sending a payment. Publishing it returns `SwapCompleted`: rebuild the
1782        /// original send from the same prepare response and submit again.
1783        is_swap: bool,
1784    },
1785}
1786
1787#[derive(Debug, Clone, Serialize, Deserialize)]
1788#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1789pub enum TransferTarget {
1790    Spark {
1791        address: String,
1792        spark_invoice: Option<String>,
1793    },
1794    Lightning {
1795        bolt11: String,
1796        lnurl_pay: Option<LnurlPayContext>,
1797        fee_policy: FeePolicy,
1798        completion_timeout_secs: Option<u32>,
1799    },
1800    CoopExit {
1801        address: String,
1802        fee_quote: SendOnchainFeeQuote,
1803        confirmation_speed: OnchainConfirmationSpeed,
1804    },
1805}
1806
1807#[derive(Debug, Clone, Serialize, Deserialize)]
1808#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1809pub struct LnurlPayContext {
1810    pub pay_request: LnurlPayRequestDetails,
1811    pub comment: Option<String>,
1812    pub success_action: Option<SuccessAction>,
1813}
1814
1815#[derive(Debug, Clone, Serialize, Deserialize)]
1816#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1817pub struct SignedTransferPackage {
1818    pub unsigned: UnsignedTransferPackage,
1819    pub signature: TransferSignature,
1820}
1821
1822#[derive(Debug, Clone, Serialize, Deserialize)]
1823#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1824pub enum TransferSignature {
1825    Transfer {
1826        signed: crate::signer::ExternalPreparedTransfer,
1827    },
1828    Token {
1829        signed: crate::signer::ExternalPreparedTokenTransaction,
1830    },
1831}
1832
1833#[derive(Debug, Clone)]
1834#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1835pub enum BuildTransferPackageOptions {
1836    BitcoinAddress {
1837        confirmation_speed: OnchainConfirmationSpeed,
1838    },
1839    Bolt11Invoice {
1840        prefer_spark: bool,
1841
1842        /// If set, publishing the package waits up to this many seconds for the
1843        /// payment to complete before returning it while still pending. If unset,
1844        /// publishing returns immediately after initiating the payment.
1845        completion_timeout_secs: Option<u32>,
1846    },
1847}
1848
1849#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1850pub struct BuildUnsignedTransferPackageRequest {
1851    pub prepare_response: PrepareSendPaymentResponse,
1852    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1853    pub options: Option<BuildTransferPackageOptions>,
1854}
1855
1856#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1857pub struct PrepareSendPaymentRequest {
1858    pub payment_request: PaymentRequest,
1859    /// The amount to send.
1860    /// Optional for payment requests with embedded amounts (e.g., Spark/Bolt11 invoices with amounts).
1861    /// Required for Spark addresses, Bitcoin addresses, and amountless invoices.
1862    /// Denominated in satoshis for Bitcoin payments, or token base units for token payments.
1863    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1864    pub amount: Option<u128>,
1865    /// Optional token identifier for token payments.
1866    /// Absence indicates that the payment is a Bitcoin payment.
1867    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1868    pub token_identifier: Option<String>,
1869    /// If provided, the payment will include a conversion step before sending the payment
1870    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1871    pub conversion_options: Option<ConversionOptions>,
1872    /// How fees are handled. See [`FeePolicy`]. Defaults to `FeesExcluded`.
1873    ///
1874    /// Ignored on cross-chain AMM-conversion sends (whether the conversion was
1875    /// explicitly requested or auto-injected by stable balance) — fees come
1876    /// out of the converted sats. Bolt11 and Bitcoin AMM-conversion sends
1877    /// still respect this field by sizing the conversion to cover fees. The
1878    /// prepare response's `fee_policy` reflects what was actually applied.
1879    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1880    pub fee_policy: Option<FeePolicy>,
1881}
1882
1883#[derive(Debug, Clone, Serialize)]
1884#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1885pub struct PrepareSendPaymentResponse {
1886    pub payment_method: SendPaymentMethod,
1887    /// The amount to be sent, denominated in satoshis for Bitcoin payments
1888    /// (including token-to-Bitcoin conversions), or token base units for token payments.
1889    /// When a conversion is present, the input amount is in
1890    /// `conversion_estimate.amount_in`.
1891    pub amount: u128,
1892    /// Optional token identifier for token payments.
1893    /// Absence indicates that the payment is a Bitcoin payment.
1894    pub token_identifier: Option<String>,
1895    /// When set, the payment will include a conversion step before sending the payment
1896    pub conversion_estimate: Option<ConversionEstimate>,
1897    /// The fee policy actually applied. May differ from the request — e.g.,
1898    /// cross-chain AMM-conversion sends are always `FeesIncluded`.
1899    pub fee_policy: FeePolicy,
1900}
1901
1902#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1903pub enum SendPaymentOptions {
1904    BitcoinAddress {
1905        /// Confirmation speed for the on-chain transaction.
1906        confirmation_speed: OnchainConfirmationSpeed,
1907    },
1908    Bolt11Invoice {
1909        prefer_spark: bool,
1910
1911        /// If set, the function will return the payment if it is still pending after this
1912        /// number of seconds. If unset, the function will return immediately after initiating the payment.
1913        completion_timeout_secs: Option<u32>,
1914    },
1915    SparkAddress {
1916        /// Can only be provided for Bitcoin payments. If set, a Spark HTLC transfer will be created.
1917        /// The receiver will need to provide the preimage to claim it.
1918        htlc_options: Option<SparkHtlcOptions>,
1919    },
1920}
1921
1922#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1923pub struct SparkHtlcOptions {
1924    /// The payment hash of the HTLC. The receiver will need to provide the associated preimage to claim it.
1925    pub payment_hash: String,
1926    /// The duration of the HTLC in seconds.
1927    /// After this time, the HTLC will be returned.
1928    pub expiry_duration_secs: u64,
1929}
1930
1931#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1932pub struct SendPaymentRequest {
1933    pub prepare_response: PrepareSendPaymentResponse,
1934    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1935    pub options: Option<SendPaymentOptions>,
1936    /// The optional idempotency key for all Spark based transfers (excludes token payments
1937    /// and cross-chain sends).
1938    /// If set, providing the same idempotency key for multiple requests will ensure that only one
1939    /// payment is made. If an idempotency key is re-used, the same payment will be returned.
1940    /// The idempotency key must be a valid UUID.
1941    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1942    pub idempotency_key: Option<String>,
1943}
1944
1945/// A single payee in a batch send.
1946#[derive(Debug, Clone)]
1947#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1948pub struct BatchRecipient {
1949    /// Spark address or Spark invoice identifying the payee.
1950    pub payment_request: String,
1951    /// Amount to send, in the base units of the asset being sent. Required
1952    /// unless `payment_request` is an invoice that carries its own amount.
1953    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1954    pub amount: Option<u128>,
1955    /// Token to send. Unset means sats, which a batch cannot send yet, so a
1956    /// plain address needs this set. An invoice that names a token does not.
1957    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1958    pub token_identifier: Option<String>,
1959}
1960
1961#[derive(Debug, Clone)]
1962#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1963pub struct PrepareSendBatchRequest {
1964    /// The payees, all paid by one transaction. They may span several tokens,
1965    /// and may mix Spark addresses with Spark invoices. Once a Spark invoice is
1966    /// among them, every recipient must be paid in the same token.
1967    pub recipients: Vec<BatchRecipient>,
1968}
1969
1970/// Where a batch recipient is paid, once prepare has decoded its payment request.
1971#[derive(Debug, Clone, Serialize, Deserialize)]
1972#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1973pub enum BatchDestination {
1974    SparkAddress {
1975        address: String,
1976    },
1977    SparkInvoice {
1978        invoice_details: SparkInvoiceDetails,
1979    },
1980}
1981
1982/// A recipient after prepare has resolved the asset and amount it is owed.
1983#[derive(Debug, Clone, Serialize, Deserialize)]
1984#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1985pub struct ResolvedBatchRecipient {
1986    pub destination: BatchDestination,
1987    /// Amount in the base units of the asset this recipient is paid in.
1988    pub amount: u128,
1989    /// The token this recipient is paid in. Unset means sats, which a batch
1990    /// cannot send yet.
1991    pub token_identifier: Option<String>,
1992}
1993
1994/// What a batch debits for one asset.
1995#[derive(Debug, Clone, Serialize, Deserialize)]
1996#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1997pub struct BatchTotal {
1998    /// The token debited. Unset means sats, which a batch cannot send yet.
1999    pub token_identifier: Option<String>,
2000    /// Amount in the asset's base units.
2001    pub amount: u128,
2002}
2003
2004#[derive(Debug, Clone, Serialize, Deserialize)]
2005#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2006pub struct PrepareSendBatchResponse {
2007    /// The payees in the order they were requested, which is the order their
2008    /// payments come back in.
2009    pub recipients: Vec<ResolvedBatchRecipient>,
2010    /// What the batch debits, one entry per distinct asset.
2011    pub totals: Vec<BatchTotal>,
2012}
2013
2014#[derive(Debug, Clone)]
2015#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2016pub struct SendBatchRequest {
2017    pub prepare_response: PrepareSendBatchResponse,
2018}
2019
2020#[derive(Debug, Clone)]
2021#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2022pub struct SendBatchResponse {
2023    /// One payment per recipient, in recipient order, all sharing a transaction
2024    /// hash.
2025    pub payments: Vec<Payment>,
2026}
2027
2028#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2029pub struct BuildUnsignedBatchPackageRequest {
2030    pub prepare_response: PrepareSendBatchResponse,
2031}
2032
2033#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2034pub struct PublishSignedTransferPackageRequest {
2035    pub signed_package: SignedTransferPackage,
2036}
2037
2038#[allow(clippy::large_enum_variant)]
2039#[derive(Debug, Clone)]
2040#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
2041pub enum PublishSignedTransferPackageResponse {
2042    SwapCompleted,
2043    PaymentSent {
2044        payment: Payment,
2045    },
2046    /// Returned for a batch package: one payment per recipient, in recipient
2047    /// order.
2048    PaymentsSent {
2049        payments: Vec<Payment>,
2050    },
2051}
2052
2053#[derive(Debug, Clone, Serialize)]
2054#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2055pub struct SendPaymentResponse {
2056    pub payment: Payment,
2057}
2058
2059#[derive(Debug, Clone)]
2060#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
2061pub enum PaymentDetailsFilter {
2062    Spark {
2063        /// Filter specific Spark HTLC statuses
2064        htlc_status: Option<Vec<SparkHtlcStatus>>,
2065        /// Filter conversion payments with refund information
2066        conversion_refund_needed: Option<bool>,
2067    },
2068    Token {
2069        /// Filter conversion payments with refund information
2070        conversion_refund_needed: Option<bool>,
2071        /// Filter by transaction hash
2072        tx_hash: Option<String>,
2073        /// Filter by transaction type
2074        tx_type: Option<TokenTransactionType>,
2075    },
2076    Lightning {
2077        /// Filter specific Spark HTLC statuses
2078        htlc_status: Option<Vec<SparkHtlcStatus>>,
2079    },
2080}
2081
2082/// Request to list payments with optional filters and pagination
2083#[derive(Debug, Clone, Default)]
2084#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2085pub struct ListPaymentsRequest {
2086    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
2087    pub type_filter: Option<Vec<PaymentType>>,
2088    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
2089    pub status_filter: Option<Vec<PaymentStatus>>,
2090    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
2091    pub asset_filter: Option<AssetFilter>,
2092    /// Only include payments matching at least one of these payment details filters
2093    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
2094    pub payment_details_filter: Option<Vec<PaymentDetailsFilter>>,
2095    /// Only include payments created after this timestamp (inclusive)
2096    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
2097    pub from_timestamp: Option<u64>,
2098    /// Only include payments created before this timestamp (exclusive)
2099    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
2100    pub to_timestamp: Option<u64>,
2101    /// Number of records to skip
2102    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
2103    pub offset: Option<u32>,
2104    /// Maximum number of records to return
2105    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
2106    pub limit: Option<u32>,
2107    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
2108    pub sort_ascending: Option<bool>,
2109}
2110
2111/// A field of [`ListPaymentsRequest`] when listing payments filtered by asset
2112#[derive(Debug, Clone)]
2113#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
2114pub enum AssetFilter {
2115    Bitcoin,
2116    Token {
2117        /// Optional token identifier to filter by
2118        token_identifier: Option<String>,
2119    },
2120}
2121
2122impl FromStr for AssetFilter {
2123    type Err = String;
2124
2125    fn from_str(s: &str) -> Result<Self, Self::Err> {
2126        Ok(match s.to_lowercase().as_str() {
2127            "bitcoin" => AssetFilter::Bitcoin,
2128            "token" => AssetFilter::Token {
2129                token_identifier: None,
2130            },
2131            str if str.starts_with("token:") => AssetFilter::Token {
2132                token_identifier: Some(
2133                    str.split_once(':')
2134                        .ok_or(format!("Invalid asset filter '{s}'"))?
2135                        .1
2136                        .to_string(),
2137                ),
2138            },
2139            _ => return Err(format!("Invalid asset filter '{s}'")),
2140        })
2141    }
2142}
2143
2144/// Response from listing payments
2145#[derive(Debug, Clone, Serialize)]
2146#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2147pub struct ListPaymentsResponse {
2148    /// The list of payments
2149    pub payments: Vec<Payment>,
2150}
2151
2152#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2153pub struct GetPaymentRequest {
2154    pub payment_id: String,
2155}
2156
2157#[derive(Debug, Clone, Serialize)]
2158#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2159pub struct GetPaymentResponse {
2160    pub payment: Payment,
2161}
2162
2163#[cfg_attr(feature = "uniffi", uniffi::export(callback_interface))]
2164pub trait Logger: Send + Sync {
2165    fn log(&self, l: LogEntry);
2166}
2167
2168#[derive(Debug, Clone, Serialize)]
2169#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2170pub struct LogEntry {
2171    pub line: String,
2172    pub level: String,
2173}
2174
2175#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2176#[derive(Debug, Clone, Serialize, Deserialize)]
2177pub struct CheckLightningAddressRequest {
2178    pub username: String,
2179}
2180
2181#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2182#[derive(Debug, Clone, Serialize, Deserialize)]
2183pub struct RegisterLightningAddressRequest {
2184    pub username: String,
2185    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
2186    pub description: Option<String>,
2187}
2188
2189/// Authorization from the current owner granting a specific new owner the
2190/// right to take over a username. Produced by
2191/// [`BreezSdk::authorize_lightning_address_transfer`] and handed to the new
2192/// owner, who passes it to [`BreezSdk::claim_lightning_address_transfer`]. It
2193/// fully describes the transfer, so the new owner needs nothing else to claim.
2194#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2195#[derive(Debug, Clone, Serialize, Deserialize)]
2196pub struct TransferAuthorization {
2197    /// The username being handed over.
2198    pub username: String,
2199    /// The current owner's public key.
2200    pub pubkey: String,
2201    /// The current owner's signature authorizing the transfer.
2202    pub signature: String,
2203    /// The lightning-address domain the authorization is for, taken from the
2204    /// address being handed over. The signed message names this domain, so an
2205    /// authorization made for one server does not verify at another.
2206    pub domain: String,
2207    /// When the authorization was produced, in seconds since the Unix epoch.
2208    /// Covered by the signature, and valid for 10 minutes: the transferee has
2209    /// to claim within that window or the current owner authorizes again.
2210    pub timestamp: u64,
2211}
2212
2213/// Request for [`BreezSdk::authorize_lightning_address_transfer`]. Called by
2214/// the *current owner* to authorize handing their registered username over to
2215/// `transferee_pubkey`.
2216#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2217#[derive(Debug, Clone, Serialize, Deserialize)]
2218pub struct AuthorizeTransferRequest {
2219    /// The new owner's identity public key.
2220    pub transferee_pubkey: String,
2221}
2222
2223/// Request for [`BreezSdk::claim_lightning_address_transfer`]. Called by the
2224/// *new owner* to complete the takeover using the authorization produced by
2225/// the current owner.
2226#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2227#[derive(Debug, Clone, Serialize, Deserialize)]
2228pub struct ClaimTransferRequest {
2229    /// Authorization produced by the current owner via
2230    /// [`BreezSdk::authorize_lightning_address_transfer`].
2231    pub authorization: TransferAuthorization,
2232    /// Description for the address. Defaults to `"Pay to {username}@{domain}"`.
2233    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
2234    pub description: Option<String>,
2235}
2236
2237#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2238#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
2239pub struct LnurlInfo {
2240    pub url: String,
2241    pub bech32: String,
2242}
2243
2244impl LnurlInfo {
2245    pub fn new(url: String) -> Self {
2246        let bech32 =
2247            breez_sdk_common::lnurl::encode_lnurl_to_bech32(&url).unwrap_or_else(|_| url.clone());
2248        Self { url, bech32 }
2249    }
2250}
2251
2252#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2253#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
2254pub struct LightningAddressInfo {
2255    pub description: String,
2256    pub lightning_address: String,
2257    pub lnurl: LnurlInfo,
2258    pub username: String,
2259}
2260
2261impl From<RecoverLnurlPayResponse> for LightningAddressInfo {
2262    fn from(resp: RecoverLnurlPayResponse) -> Self {
2263        Self {
2264            description: resp.description,
2265            lightning_address: resp.lightning_address,
2266            lnurl: LnurlInfo::new(resp.lnurl),
2267            username: resp.username,
2268        }
2269    }
2270}
2271
2272/// Response from listing fiat currencies
2273#[derive(Debug, Clone, Serialize)]
2274#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2275pub struct ListFiatCurrenciesResponse {
2276    /// The list of fiat currencies
2277    pub currencies: Vec<FiatCurrency>,
2278}
2279
2280/// Response from listing fiat rates
2281#[derive(Debug, Clone, Serialize)]
2282#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2283pub struct ListFiatRatesResponse {
2284    /// The list of fiat rates
2285    pub rates: Vec<Rate>,
2286}
2287
2288/// The operational status of a Spark service.
2289#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
2290#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
2291pub enum ServiceStatus {
2292    /// Service is fully operational.
2293    Operational,
2294    /// Service is experiencing degraded performance.
2295    Degraded,
2296    /// Service is partially unavailable.
2297    Partial,
2298    /// Service status is unknown.
2299    Unknown,
2300    /// Service is experiencing a major outage.
2301    Major,
2302}
2303
2304/// The status of the Spark network services relevant to the SDK.
2305#[derive(Debug, Clone, Serialize)]
2306#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2307pub struct SparkStatus {
2308    /// The worst status across all relevant services.
2309    pub status: ServiceStatus,
2310    /// The last time the status was updated, as a unix timestamp in seconds.
2311    pub last_updated: u64,
2312}
2313
2314pub(crate) enum WaitForPaymentIdentifier {
2315    PaymentId(String),
2316    LightningReceive { invoice: String, ssp_id: String },
2317}
2318
2319#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2320pub struct GetTokensMetadataRequest {
2321    pub token_identifiers: Vec<String>,
2322}
2323
2324#[derive(Debug, Clone, Serialize)]
2325#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2326pub struct GetTokensMetadataResponse {
2327    pub tokens_metadata: Vec<TokenMetadata>,
2328}
2329
2330#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2331pub struct SignMessageRequest {
2332    pub message: String,
2333    /// If true, the signature will be encoded in compact format instead of DER format
2334    pub compact: bool,
2335}
2336
2337#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2338pub struct SignMessageResponse {
2339    pub pubkey: String,
2340    /// The DER or compact hex encoded signature
2341    pub signature: String,
2342}
2343
2344#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2345pub struct CheckMessageRequest {
2346    /// The message that was signed
2347    pub message: String,
2348    /// The public key that signed the message
2349    pub pubkey: String,
2350    /// The DER or compact hex encoded signature
2351    pub signature: String,
2352}
2353
2354#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2355pub struct CheckMessageResponse {
2356    pub is_valid: bool,
2357}
2358
2359#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2360#[derive(Debug, Clone, Serialize)]
2361pub struct UserSettings {
2362    pub spark_private_mode_enabled: bool,
2363
2364    /// The label of the currently active stable balance token, or `None` if deactivated.
2365    pub stable_balance_active_label: Option<String>,
2366
2367    /// The hex encoded public key designated as this wallet's master identity
2368    /// key, or `None` if none is designated.
2369    pub spark_master_identity_public_key: Option<String>,
2370}
2371
2372/// Specifies how to update the wallet's Spark master identity public key.
2373#[derive(Debug, Clone)]
2374#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
2375pub enum SparkMasterIdentityPublicKey {
2376    /// Designate the holder of this public key as the wallet's master
2377    /// identity, replacing any previously designated key. Must be hex encoded
2378    /// in the 33-byte compressed form.
2379    Set { public_key: String },
2380    /// Remove the designated master identity, leaving the owner as the only
2381    /// party able to read the wallet under private mode.
2382    Unset,
2383}
2384
2385#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2386pub struct UpdateUserSettingsRequest {
2387    pub spark_private_mode_enabled: Option<bool>,
2388
2389    /// Update the active stable balance token. `None` means no change.
2390    #[cfg_attr(feature = "uniffi", uniffi(default = None))]
2391    pub stable_balance_active_label: Option<StableBalanceActiveLabel>,
2392
2393    /// Designate or remove the wallet's master identity, a second public key
2394    /// the Spark operators accept as a reader of this wallet's balance and
2395    /// history while `spark_private_mode_enabled` is set. The master identity
2396    /// can only read: payments still require the owner's keys. `None` means no
2397    /// change.
2398    #[cfg_attr(feature = "uniffi", uniffi(default = None))]
2399    pub spark_master_identity_public_key: Option<SparkMasterIdentityPublicKey>,
2400}
2401
2402#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2403pub struct ClaimHtlcPaymentRequest {
2404    pub preimage: String,
2405}
2406
2407#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2408pub struct ClaimHtlcPaymentResponse {
2409    pub payment: Payment,
2410}
2411
2412#[derive(Debug, Clone, Deserialize, Serialize)]
2413#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2414pub struct LnurlReceiveMetadata {
2415    pub nostr_zap_request: Option<String>,
2416    pub nostr_zap_receipt: Option<String>,
2417    pub sender_comment: Option<String>,
2418}
2419
2420/// Mode of a manually-triggered optimization run.
2421#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)]
2422#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
2423pub enum OptimizationMode {
2424    /// Run until no further optimization is productive.
2425    #[default]
2426    Full,
2427    /// Execute a single round and return so the caller can drive progress.
2428    SingleRound,
2429}
2430
2431/// Request for [`BreezSdk::optimize_leaves`]. Defaults to
2432/// [`OptimizationMode::Full`].
2433#[derive(Debug, Clone, Default, Deserialize, Serialize)]
2434#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2435pub struct OptimizeLeavesRequest {
2436    /// Controls how much work the call performs before returning.
2437    pub mode: OptimizationMode,
2438}
2439
2440/// Response from a [`BreezSdk::optimize_leaves`] call.
2441#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
2442#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2443pub struct OptimizeLeavesResponse {
2444    /// The outcome of the optimization run.
2445    pub outcome: OptimizationOutcome,
2446}
2447
2448/// Outcome of a [`BreezSdk::optimize_leaves`] call.
2449///
2450/// `rounds_executed` on `Completed` refers to rounds run by *this call*.
2451/// The SDK holds no cross-call state — callers driving a `SingleRound`
2452/// loop maintain their own cumulative counter if they need one.
2453///
2454/// A `Completed { rounds_executed: 0 }` outcome means the wallet was
2455/// already optimal at call time (no swap was needed).
2456///
2457/// **`SingleRound` loop pattern**: terminate on anything that isn't
2458/// `InProgress`. `Completed` covers both the final swap of a productive
2459/// run and the "already optimal" no-op case (the latter as
2460/// `rounds_executed: 0`).
2461///
2462/// ```ignore
2463/// loop {
2464///     let request = OptimizeLeavesRequest { mode: OptimizationMode::SingleRound };
2465///     match sdk.optimize_leaves(request).await?.outcome {
2466///         OptimizationOutcome::InProgress => continue,
2467///         OptimizationOutcome::Completed { .. } => break,
2468///     }
2469/// }
2470/// ```
2471#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
2472#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
2473pub enum OptimizationOutcome {
2474    /// All planned optimization work was executed in this call.
2475    /// Returned by `Full` runs on success, and by `SingleRound` runs
2476    /// whose swap was the final one needed (the planner produced a
2477    /// single-swap plan with a convergence guarantee).
2478    /// `rounds_executed == 0` means the wallet was already optimal —
2479    /// no work was performed.
2480    Completed { rounds_executed: u32 },
2481    /// `SingleRound` only: a round ran but the planner could not
2482    /// guarantee it was the last. The caller should invoke
2483    /// `optimize_leaves` again.
2484    InProgress,
2485}
2486
2487/// A contact entry containing a name and payment identifier.
2488#[derive(Debug, Clone, Serialize, Deserialize)]
2489#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2490pub struct Contact {
2491    pub id: String,
2492    pub name: String,
2493    /// A Lightning address (user@domain).
2494    pub payment_identifier: String,
2495    pub created_at: u64,
2496    pub updated_at: u64,
2497}
2498
2499/// Request to add a new contact.
2500#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2501pub struct AddContactRequest {
2502    pub name: String,
2503    /// A Lightning address (user@domain).
2504    pub payment_identifier: String,
2505}
2506
2507/// Request to update an existing contact.
2508#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2509pub struct UpdateContactRequest {
2510    pub id: String,
2511    pub name: String,
2512    /// A Lightning address (user@domain).
2513    pub payment_identifier: String,
2514}
2515
2516/// Request to list contacts with optional pagination.
2517#[derive(Debug, Clone, Default)]
2518#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2519pub struct ListContactsRequest {
2520    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
2521    pub offset: Option<u32>,
2522    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
2523    pub limit: Option<u32>,
2524}
2525
2526/// The type of event that triggers a webhook notification.
2527#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
2528#[allow(clippy::enum_variant_names)]
2529#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
2530pub enum WebhookEventType {
2531    /// Triggered when a Lightning receive operation completes.
2532    LightningReceiveFinished,
2533    /// Triggered when a Lightning send operation completes.
2534    LightningSendFinished,
2535    /// Triggered when a cooperative exit completes.
2536    CoopExitFinished,
2537    /// Triggered when a static deposit completes.
2538    StaticDepositFinished,
2539    /// An event type not yet recognized by this version of the SDK.
2540    Unknown(String),
2541}
2542
2543/// A registered webhook entry.
2544#[derive(Debug, Clone, Serialize)]
2545#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2546pub struct Webhook {
2547    /// Unique identifier for this webhook.
2548    pub id: String,
2549    /// The URL that receives webhook notifications.
2550    pub url: String,
2551    /// The event types this webhook is subscribed to.
2552    pub event_types: Vec<WebhookEventType>,
2553}
2554
2555/// Request to register a new webhook.
2556#[derive(Debug, Clone)]
2557#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2558pub struct RegisterWebhookRequest {
2559    /// The URL that will receive webhook notifications.
2560    pub url: String,
2561    /// A secret used for HMAC-SHA256 signature verification of webhook payloads.
2562    pub secret: String,
2563    /// The event types to subscribe to.
2564    pub event_types: Vec<WebhookEventType>,
2565}
2566
2567/// Response from registering a webhook.
2568#[derive(Debug, Clone, Serialize)]
2569#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2570pub struct RegisterWebhookResponse {
2571    /// The unique identifier of the newly registered webhook.
2572    pub webhook_id: String,
2573}
2574
2575/// Request to unregister an existing webhook.
2576#[derive(Debug, Clone)]
2577#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2578pub struct UnregisterWebhookRequest {
2579    /// The unique identifier of the webhook to unregister.
2580    pub webhook_id: String,
2581}
2582
2583// ===========================================================================
2584// Unilateral exit
2585// ===========================================================================
2586
2587/// A funding UTXO that pays the on-chain fees of a unilateral exit.
2588#[derive(Debug, Clone, Serialize, Deserialize)]
2589#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
2590pub enum CpfpInput {
2591    /// A P2WPKH (native segwit v0) UTXO controlled by `pubkey` (33-byte
2592    /// compressed, hex).
2593    P2wpkh {
2594        txid: String,
2595        vout: u32,
2596        value: u64,
2597        pubkey: String,
2598    },
2599    /// A P2TR (taproot, key-path) UTXO. `pubkey` (x-only or compressed, hex) is
2600    /// the **internal** (untweaked, BIP86 key-path) public key whose secret signs
2601    /// the input, not the tweaked on-chain output key. The SDK applies the BIP86
2602    /// taproot tweak itself to derive the funding scriptPubKey, so passing the
2603    /// already-tweaked output key here produces a scriptPubKey that does not match
2604    /// the UTXO and the built transaction is rejected at broadcast.
2605    P2tr {
2606        txid: String,
2607        vout: u32,
2608        value: u64,
2609        pubkey: String,
2610    },
2611    /// Any witness-program script, signed via a custom `CpfpSigner`. Legacy
2612    /// (non-SegWit) scripts are rejected. `signed_input_weight` (weight units)
2613    /// is an upper bound on the input's signed weight, so the fee stays exact,
2614    /// or slightly conservative if the real signature is shorter.
2615    Custom {
2616        txid: String,
2617        vout: u32,
2618        value: u64,
2619        script_pubkey_hex: String,
2620        signed_input_weight: u64,
2621    },
2622}
2623
2624/// The kind of UTXO that will fund an exit's fees.
2625#[derive(Debug, Clone, Serialize, Deserialize)]
2626#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
2627pub enum CpfpFundingKind {
2628    /// Fees paid from P2WPKH (native segwit v0) UTXOs.
2629    P2wpkh,
2630    /// Fees paid from P2TR (taproot, key-path) UTXOs.
2631    P2tr,
2632    /// Fees paid from a custom witness-program script (legacy scripts are
2633    /// rejected). `script_pubkey_hex` (the funding scriptPubKey) sizes the
2634    /// fan-out output and dust; `signed_input_weight` (weight units) is an upper
2635    /// bound on the input's signed weight, so the quote stays exact or slightly
2636    /// conservative.
2637    Custom {
2638        script_pubkey_hex: String,
2639        signed_input_weight: u64,
2640    },
2641}
2642
2643/// Which leaves to exit.
2644#[derive(Debug, Clone, Serialize, Deserialize)]
2645#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
2646pub enum ExitLeafSelection {
2647    /// Exit every leaf whose value exceeds its own marginal exit cost (its tree
2648    /// and refund CPFP fees plus its sweep input). This per-leaf test does not
2649    /// include the shared fan-out fee, so funding many leaves from a single UTXO
2650    /// adds `fanout_fee_sat` on top: compare `recoverable_value_sat` with
2651    /// `total_fee_sat`, or fund one UTXO per branch to avoid the fan-out. Leaves
2652    /// that fail the per-leaf test are skipped.
2653    Auto,
2654    /// Exit exactly these leaves, regardless of profitability.
2655    Specific { leaf_ids: Vec<String> },
2656}
2657
2658/// The role of a transaction in the exit path.
2659#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2660#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
2661pub enum UnilateralExitTxKind {
2662    /// Splits the caller's funding into one output per branch. Present only
2663    /// when the funding couldn't be matched one-to-one to branches.
2664    FanOut,
2665    /// A tree node transaction (root, intermediate, or leaf node).
2666    Node,
2667    /// A leaf's refund transaction.
2668    Refund,
2669    /// The final transaction sweeping all refund outputs to the destination.
2670    Sweep,
2671}
2672
2673/// Whether a transaction in the exit path is already on-chain.
2674#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2675#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
2676pub enum ConfirmationStatus {
2677    /// This transaction is confirmed in a block. It needs no action.
2678    Confirmed,
2679    /// This transaction is not yet confirmed. Mempool state is not consulted.
2680    Unconfirmed,
2681    /// The on-chain status could not be determined (the chain service errored).
2682    /// Broadcasting may fail if a conflicting transaction already landed.
2683    Unverified,
2684}
2685
2686/// One transaction in the unilateral exit path, with everything needed to
2687/// order and broadcast it.
2688#[derive(Debug, Clone, Serialize, Deserialize)]
2689#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2690pub struct UnilateralExitTransaction {
2691    pub kind: UnilateralExitTxKind,
2692    /// The tree node this transaction belongs to. Unset for the fan-out and the
2693    /// sweep.
2694    pub node_id: Option<String>,
2695    pub txid: String,
2696    pub tx_hex: String,
2697    /// The signed CPFP child to broadcast alongside `tx_hex` as a package.
2698    /// Unset for the fan-out and the sweep (no anchor to bump) and for a
2699    /// `Confirmed` step (its CPFP is already on-chain).
2700    pub cpfp_tx_hex: Option<String>,
2701    /// Relative CSV timelock, in blocks, that must mature on the spent input
2702    /// before this transaction can confirm. Unset when there is no timelock.
2703    pub csv_timelock_blocks: Option<u32>,
2704    /// Txids of other entries in this list that must be confirmed before this
2705    /// one can be broadcast.
2706    pub depends_on: Vec<String>,
2707    pub status: ConfirmationStatus,
2708}
2709
2710/// A leaf selected for exit, with its value.
2711#[derive(Debug, Clone, Serialize, Deserialize)]
2712#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2713pub struct UnilateralExitLeaf {
2714    pub leaf_id: String,
2715    /// The leaf's value in satoshis.
2716    pub value: u64,
2717}
2718
2719/// Request for `prepare_unilateral_exit`, the exit quote.
2720#[derive(Debug, Clone)]
2721#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2722pub struct PrepareUnilateralExitRequest {
2723    /// Target fee rate in sat/vByte, applied to every CPFP child, the fan-out,
2724    /// and the sweep.
2725    pub fee_rate_sat_per_vbyte: u64,
2726    pub funding_kind: CpfpFundingKind,
2727    /// The Bitcoin address the swept funds are sent to.
2728    pub destination: String,
2729    pub selection: ExitLeafSelection,
2730}
2731
2732/// How much to fund one branch of the exit to avoid a fan-out.
2733#[derive(Debug, Clone, Serialize, Deserialize)]
2734#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2735pub struct PerBranchFunding {
2736    /// The leaf whose branch this funds.
2737    pub leaf_id: String,
2738    /// Fund a UTXO of at least this many satoshis for this branch.
2739    pub funding_sat: u64,
2740}
2741
2742/// Response from `prepare_unilateral_exit`: which leaves would exit, the exact
2743/// fee at the requested rate, and how much to fund.
2744#[derive(Debug, Clone, Serialize, Deserialize)]
2745#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2746pub struct PrepareUnilateralExitResponse {
2747    pub leaves: Vec<UnilateralExitLeaf>,
2748    /// Total value of the selected leaves, in satoshis.
2749    pub recoverable_value_sat: u64,
2750    /// Total on-chain fee when funding with a single UTXO (fanned out across
2751    /// branches), in satoshis. Exact for the given funding kind; nodes the
2752    /// operators report on-chain are assumed already paid, so a partially-exited
2753    /// tree quotes a lower fee than a fresh one.
2754    pub total_fee_sat: u64,
2755    /// The part of `total_fee_sat` paid for the fan-out transaction. Funding one
2756    /// UTXO per branch (`per_branch_funding`) avoids it. Zero for a single
2757    /// branch (no fan-out).
2758    pub fanout_fee_sat: u64,
2759    /// Fund a single UTXO of at least this many satoshis to exit with a fan-out.
2760    pub single_utxo_funding_sat: u64,
2761    /// To skip the fan-out, fund one UTXO per branch of at least the given
2762    /// amount (one entry per selected leaf).
2763    pub per_branch_funding: Vec<PerBranchFunding>,
2764    /// The fee rate this quote was computed at, in sat/vByte.
2765    pub fee_rate_sat_per_vbyte: u64,
2766    pub destination: String,
2767}
2768
2769/// Request for `unilateral_exit`: a `prepare_unilateral_exit` quote plus the
2770/// funding UTXOs that pay its fees. The signer is passed separately (it is not a
2771/// plain data value).
2772#[derive(Debug, Clone)]
2773#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2774pub struct UnilateralExitRequest {
2775    /// The quote returned by `prepare_unilateral_exit`, naming the leaves to exit.
2776    pub prepared: PrepareUnilateralExitResponse,
2777    /// The funding UTXOs that pay the exit's on-chain fees, meeting the quote's
2778    /// `single_utxo_funding_sat` (one UTXO) or `per_branch_funding` (one per branch).
2779    pub funding_inputs: Vec<CpfpInput>,
2780}
2781
2782/// Result of `unilateral_exit`: a cost summary plus the complete, signed exit
2783/// path.
2784#[derive(Debug, Clone, Serialize)]
2785#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2786pub struct UnilateralExitResponse {
2787    /// Total value of the selected leaves, in satoshis.
2788    pub recoverable_value_sat: u64,
2789    /// The actual total on-chain fee the returned transactions pay at the
2790    /// requested rate, in satoshis. A resumed or partially-confirmed exit pays
2791    /// less because already-confirmed steps are not rebuilt.
2792    pub total_fee_sat: u64,
2793    pub leaves: Vec<UnilateralExitLeaf>,
2794    /// The full signed transaction set, in valid topological (broadcast) order
2795    /// with shared ancestors appearing once and the sweep last.
2796    pub transactions: Vec<UnilateralExitTransaction>,
2797}
2798
2799/// Result of `export_unilateral_exit_state`: a self-contained copy of the
2800/// wallet's exit state, ready to be stored outside the wallet.
2801#[derive(Debug, Clone, Serialize, Deserialize)]
2802#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2803pub struct ExportUnilateralExitStateResponse {
2804    /// The serialized exit state, to be handed back to
2805    /// `import_unilateral_exit_state` unmodified.
2806    pub exit_state: String,
2807}
2808
2809/// Request for `import_unilateral_exit_state`.
2810#[derive(Debug, Clone, Serialize, Deserialize)]
2811#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2812pub struct ImportUnilateralExitStateRequest {
2813    /// An exit state as returned by `export_unilateral_exit_state`.
2814    pub exit_state: String,
2815}
2816
2817/// Result of `import_unilateral_exit_state`.
2818#[derive(Debug, Clone, Serialize, Deserialize)]
2819#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2820pub struct ImportUnilateralExitStateResponse {
2821    /// Leaves merged into the wallet's exit state, whether or not their exit
2822    /// data was taken with them.
2823    pub imported_leaves: u32,
2824    /// Leaves left out because the exit state does not record this wallet as
2825    /// their owner.
2826    pub skipped_foreign_leaves: u32,
2827    /// Leaves left out because their exit data disagrees with what the wallet
2828    /// already holds, so none of it could be trusted. The wallet is left without
2829    /// these leaves.
2830    pub skipped_conflicting_leaves: u32,
2831    /// Leaves the wallet holds whose imported exit data was left out: it is
2832    /// incomplete, the wallet's own copy can already back an exit, or the leaf
2833    /// was named more than once. The leaf itself is in the wallet either way.
2834    pub skipped_chains: u32,
2835}