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    ConversionEstimate, ConversionInfo, ConversionOptions, ConversionPurpose, ConversionStatus,
8    ConversionType, FetchConversionLimitsRequest, FetchConversionLimitsResponse,
9};
10
11use core::fmt;
12use lnurl_models::RecoverLnurlPayResponse;
13use serde::{Deserialize, Serialize};
14use serde_json::Value;
15use std::{collections::HashMap, fmt::Display, str::FromStr};
16
17use crate::{
18    BitcoinAddressDetails, BitcoinChainService, BitcoinNetwork, Bolt11InvoiceDetails,
19    ExternalInputParser, FiatCurrency, LnurlPayRequestDetails, LnurlWithdrawRequestDetails, Rate,
20    SdkError, SparkInvoiceDetails, SuccessAction, SuccessActionProcessed, error::DepositClaimError,
21};
22
23/// A list of external input parsers that are used by default.
24/// To opt-out, set `use_default_external_input_parsers` in [Config] to false.
25pub const DEFAULT_EXTERNAL_INPUT_PARSERS: &[(&str, &str, &str)] = &[
26    (
27        "picknpay",
28        "(.*)(za.co.electrum.picknpay)(.*)",
29        "https://cryptoqr.net/.well-known/lnurlp/<input>",
30    ),
31    (
32        "bootleggers",
33        r"(.*)(wigroup\.co|yoyogroup\.co)(.*)",
34        "https://cryptoqr.net/.well-known/lnurlw/<input>",
35    ),
36];
37
38/// Represents the seed for wallet generation, either as a mnemonic phrase with an optional
39/// passphrase or as raw entropy bytes.
40#[derive(Debug, Clone)]
41#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
42pub enum Seed {
43    /// A BIP-39 mnemonic phrase with an optional passphrase.
44    Mnemonic {
45        /// The mnemonic phrase. 12 or 24 words.
46        mnemonic: String,
47        /// An optional passphrase for the mnemonic.
48        passphrase: Option<String>,
49    },
50    /// Raw entropy bytes.
51    Entropy(Vec<u8>),
52}
53
54impl Seed {
55    pub fn to_bytes(&self) -> Result<Vec<u8>, SdkError> {
56        match self {
57            Seed::Mnemonic {
58                mnemonic,
59                passphrase,
60            } => {
61                let mnemonic = bip39::Mnemonic::parse(mnemonic)
62                    .map_err(|e| SdkError::Generic(e.to_string()))?;
63
64                Ok(mnemonic
65                    .to_seed(passphrase.as_deref().unwrap_or(""))
66                    .to_vec())
67            }
68            Seed::Entropy(entropy) => Ok(entropy.clone()),
69        }
70    }
71}
72
73#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
74pub struct ConnectRequest {
75    pub config: Config,
76    pub seed: Seed,
77    pub storage_dir: String,
78}
79
80/// Request object for connecting to the Spark network using an external signer.
81///
82/// This allows using a custom signer implementation instead of providing a seed directly.
83#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
84#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
85pub struct ConnectWithSignerRequest {
86    pub config: Config,
87    pub signer: std::sync::Arc<dyn crate::signer::ExternalSigner>,
88    pub storage_dir: String,
89}
90
91/// The type of payment
92#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
93#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
94pub enum PaymentType {
95    /// Payment sent from this wallet
96    Send,
97    /// Payment received to this wallet
98    Receive,
99}
100
101impl fmt::Display for PaymentType {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        match self {
104            PaymentType::Send => write!(f, "send"),
105            PaymentType::Receive => write!(f, "receive"),
106        }
107    }
108}
109
110impl FromStr for PaymentType {
111    type Err = String;
112
113    fn from_str(s: &str) -> Result<Self, Self::Err> {
114        Ok(match s.to_lowercase().as_str() {
115            "receive" => PaymentType::Receive,
116            "send" => PaymentType::Send,
117            _ => return Err(format!("invalid payment type '{s}'")),
118        })
119    }
120}
121
122/// The status of a payment
123#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
124#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
125pub enum PaymentStatus {
126    /// Payment is completed successfully
127    Completed,
128    /// Payment is in progress
129    Pending,
130    /// Payment has failed
131    Failed,
132}
133
134impl PaymentStatus {
135    /// Returns true if the payment status is final (either Completed or Failed)
136    pub fn is_final(&self) -> bool {
137        matches!(self, PaymentStatus::Completed | PaymentStatus::Failed)
138    }
139}
140
141impl fmt::Display for PaymentStatus {
142    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143        match self {
144            PaymentStatus::Completed => write!(f, "completed"),
145            PaymentStatus::Pending => write!(f, "pending"),
146            PaymentStatus::Failed => write!(f, "failed"),
147        }
148    }
149}
150
151impl FromStr for PaymentStatus {
152    type Err = String;
153
154    fn from_str(s: &str) -> Result<Self, Self::Err> {
155        Ok(match s.to_lowercase().as_str() {
156            "completed" => PaymentStatus::Completed,
157            "pending" => PaymentStatus::Pending,
158            "failed" => PaymentStatus::Failed,
159            _ => return Err(format!("Invalid payment status '{s}'")),
160        })
161    }
162}
163
164#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
165#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
166pub enum PaymentMethod {
167    Lightning,
168    Spark,
169    Token,
170    Deposit,
171    Withdraw,
172    Unknown,
173}
174
175impl Display for PaymentMethod {
176    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
177        match self {
178            PaymentMethod::Lightning => write!(f, "lightning"),
179            PaymentMethod::Spark => write!(f, "spark"),
180            PaymentMethod::Token => write!(f, "token"),
181            PaymentMethod::Deposit => write!(f, "deposit"),
182            PaymentMethod::Withdraw => write!(f, "withdraw"),
183            PaymentMethod::Unknown => write!(f, "unknown"),
184        }
185    }
186}
187
188impl FromStr for PaymentMethod {
189    type Err = ();
190
191    fn from_str(s: &str) -> Result<Self, Self::Err> {
192        match s {
193            "lightning" => Ok(PaymentMethod::Lightning),
194            "spark" => Ok(PaymentMethod::Spark),
195            "token" => Ok(PaymentMethod::Token),
196            "deposit" => Ok(PaymentMethod::Deposit),
197            "withdraw" => Ok(PaymentMethod::Withdraw),
198            "unknown" => Ok(PaymentMethod::Unknown),
199            _ => Err(()),
200        }
201    }
202}
203
204/// Represents a payment (sent or received)
205#[derive(Debug, Clone, Serialize, Deserialize)]
206#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
207pub struct Payment {
208    /// Unique identifier for the payment
209    pub id: String,
210    /// Type of payment (send or receive)
211    pub payment_type: PaymentType,
212    /// Status of the payment
213    pub status: PaymentStatus,
214    /// Amount in satoshis or token base units
215    pub amount: u128,
216    /// Fee paid in satoshis or token base units
217    pub fees: u128,
218    /// Timestamp of when the payment was created
219    pub timestamp: u64,
220    /// Method of payment. Sometimes the payment details is empty so this field
221    /// is used to determine the payment method.
222    pub method: PaymentMethod,
223    /// Details of the payment
224    pub details: Option<PaymentDetails>,
225    /// If set, this payment involved a conversion before the payment
226    pub conversion_details: Option<ConversionDetails>,
227}
228
229/// Outlines the steps involved in a conversion
230#[derive(Debug, Clone, Serialize, Deserialize)]
231#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
232pub struct ConversionDetails {
233    /// First step is converting from the available asset
234    pub from: ConversionStep,
235    /// Second step is converting to the requested asset
236    pub to: ConversionStep,
237}
238
239/// Conversions have one send and one receive payment that are associated to the
240/// ongoing payment via the `parent_payment_id` in the payment metadata. These payments
241/// are queried from the storage by the SDK, then converted.
242impl TryFrom<&Vec<Payment>> for ConversionDetails {
243    type Error = SdkError;
244    fn try_from(payments: &Vec<Payment>) -> Result<Self, Self::Error> {
245        let from = payments
246            .iter()
247            .find(|p| p.payment_type == PaymentType::Send)
248            .ok_or(SdkError::Generic(
249                "From step of conversion not found".to_string(),
250            ))?;
251        let to = payments
252            .iter()
253            .find(|p| p.payment_type == PaymentType::Receive)
254            .ok_or(SdkError::Generic(
255                "To step of conversion not found".to_string(),
256            ))?;
257        Ok(ConversionDetails {
258            from: from.try_into()?,
259            to: to.try_into()?,
260        })
261    }
262}
263
264/// A single step in a conversion
265#[derive(Debug, Clone, Serialize, Deserialize)]
266#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
267pub struct ConversionStep {
268    /// The underlying payment id of the conversion step
269    pub payment_id: String,
270    /// Payment amount in satoshis or token base units
271    pub amount: u128,
272    /// Fee paid in satoshis or token base units
273    /// This represents the payment fee + the conversion fee
274    pub fee: u128,
275    /// Method of payment
276    pub method: PaymentMethod,
277    /// Token metadata if a token is used for payment
278    pub token_metadata: Option<TokenMetadata>,
279}
280
281/// Converts a Spark or Token payment into a `ConversionStep`.
282/// Fees are a sum of the payment fee and the conversion fee, if applicable,
283/// from the payment details. Token metadata should only be set for a token payment.
284impl TryFrom<&Payment> for ConversionStep {
285    type Error = SdkError;
286    fn try_from(payment: &Payment) -> Result<Self, Self::Error> {
287        let (conversion_info, token_metadata) = match &payment.details {
288            Some(PaymentDetails::Spark {
289                conversion_info: Some(info),
290                ..
291            }) => (info, None),
292            Some(PaymentDetails::Token {
293                conversion_info: Some(info),
294                metadata,
295                ..
296            }) => (info, Some(metadata.clone())),
297            _ => {
298                return Err(SdkError::Generic(format!(
299                    "No conversion info available for payment {}",
300                    payment.id
301                )));
302            }
303        };
304        Ok(ConversionStep {
305            payment_id: payment.id.clone(),
306            amount: payment.amount,
307            fee: payment
308                .fees
309                .saturating_add(conversion_info.fee.unwrap_or(0)),
310            method: payment.method,
311            token_metadata,
312        })
313    }
314}
315
316#[cfg(feature = "uniffi")]
317uniffi::custom_type!(u128, String);
318
319#[cfg(feature = "uniffi")]
320impl crate::UniffiCustomTypeConverter for u128 {
321    type Builtin = String;
322
323    fn into_custom(val: Self::Builtin) -> ::uniffi::Result<Self>
324    where
325        Self: ::std::marker::Sized,
326    {
327        val.parse::<u128>()
328            .map_err(uniffi::deps::anyhow::Error::msg)
329    }
330
331    fn from_custom(obj: Self) -> Self::Builtin {
332        obj.to_string()
333    }
334}
335
336// TODO: fix large enum variant lint - may be done by boxing lnurl_pay_info but that requires
337//  some changes to the wasm bindgen macro
338#[allow(clippy::large_enum_variant)]
339#[derive(Debug, Clone, Serialize, Deserialize)]
340#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
341pub enum PaymentDetails {
342    Spark {
343        /// The invoice details if the payment fulfilled a spark invoice
344        invoice_details: Option<SparkInvoicePaymentDetails>,
345        /// The HTLC transfer details if the payment fulfilled an HTLC transfer
346        htlc_details: Option<SparkHtlcDetails>,
347        /// The information for a conversion
348        conversion_info: Option<ConversionInfo>,
349    },
350    Token {
351        metadata: TokenMetadata,
352        tx_hash: String,
353        tx_type: TokenTransactionType,
354        /// The invoice details if the payment fulfilled a spark invoice
355        invoice_details: Option<SparkInvoicePaymentDetails>,
356        /// The information for a conversion
357        conversion_info: Option<ConversionInfo>,
358    },
359    Lightning {
360        /// Represents the invoice description
361        description: Option<String>,
362        /// Represents the Bolt11/Bolt12 invoice associated with a payment
363        /// In the case of a Send payment, this is the invoice paid by the user
364        /// In the case of a Receive payment, this is the invoice paid to the user
365        invoice: String,
366
367        /// The invoice destination/payee pubkey
368        destination_pubkey: String,
369
370        /// The HTLC transfer details
371        htlc_details: SparkHtlcDetails,
372
373        /// Lnurl payment information if this was an lnurl payment.
374        lnurl_pay_info: Option<LnurlPayInfo>,
375
376        /// Lnurl withdrawal information if this was an lnurl payment.
377        lnurl_withdraw_info: Option<LnurlWithdrawInfo>,
378
379        /// Lnurl receive information if this was a received lnurl payment.
380        lnurl_receive_metadata: Option<LnurlReceiveMetadata>,
381    },
382    Withdraw {
383        tx_id: String,
384    },
385    Deposit {
386        tx_id: String,
387    },
388}
389
390#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
391#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
392pub enum TokenTransactionType {
393    Transfer,
394    Mint,
395    Burn,
396}
397
398impl fmt::Display for TokenTransactionType {
399    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
400        match self {
401            TokenTransactionType::Transfer => write!(f, "transfer"),
402            TokenTransactionType::Mint => write!(f, "mint"),
403            TokenTransactionType::Burn => write!(f, "burn"),
404        }
405    }
406}
407
408impl FromStr for TokenTransactionType {
409    type Err = String;
410
411    fn from_str(s: &str) -> Result<Self, Self::Err> {
412        match s.to_lowercase().as_str() {
413            "transfer" => Ok(TokenTransactionType::Transfer),
414            "mint" => Ok(TokenTransactionType::Mint),
415            "burn" => Ok(TokenTransactionType::Burn),
416            _ => Err(format!("Invalid token transaction type '{s}'")),
417        }
418    }
419}
420
421#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
422#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
423pub struct SparkInvoicePaymentDetails {
424    /// Represents the spark invoice description
425    pub description: Option<String>,
426    /// The raw spark invoice string
427    pub invoice: String,
428}
429
430#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
431#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
432pub struct SparkHtlcDetails {
433    /// The payment hash of the HTLC
434    pub payment_hash: String,
435    /// The preimage of the HTLC. Empty until receiver has released it.
436    pub preimage: Option<String>,
437    /// The expiry time of the HTLC as a unix timestamp in seconds
438    pub expiry_time: u64,
439    /// The HTLC status
440    pub status: SparkHtlcStatus,
441}
442
443#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
444#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
445pub enum SparkHtlcStatus {
446    /// The HTLC is waiting for the preimage to be shared by the receiver
447    WaitingForPreimage,
448    /// The HTLC preimage has been shared and the transfer can be or has been claimed by the receiver
449    PreimageShared,
450    /// The HTLC has been returned to the sender due to expiry
451    Returned,
452}
453
454impl fmt::Display for SparkHtlcStatus {
455    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
456        match self {
457            SparkHtlcStatus::WaitingForPreimage => write!(f, "WaitingForPreimage"),
458            SparkHtlcStatus::PreimageShared => write!(f, "PreimageShared"),
459            SparkHtlcStatus::Returned => write!(f, "Returned"),
460        }
461    }
462}
463
464impl FromStr for SparkHtlcStatus {
465    type Err = String;
466
467    fn from_str(s: &str) -> Result<Self, Self::Err> {
468        match s {
469            "WaitingForPreimage" => Ok(SparkHtlcStatus::WaitingForPreimage),
470            "PreimageShared" => Ok(SparkHtlcStatus::PreimageShared),
471            "Returned" => Ok(SparkHtlcStatus::Returned),
472            _ => Err("Invalid Spark HTLC status".to_string()),
473        }
474    }
475}
476
477#[derive(Debug, Clone, Copy)]
478#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
479pub enum Network {
480    Mainnet,
481    Regtest,
482}
483
484impl std::fmt::Display for Network {
485    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
486        match self {
487            Network::Mainnet => write!(f, "Mainnet"),
488            Network::Regtest => write!(f, "Regtest"),
489        }
490    }
491}
492
493impl From<Network> for BitcoinNetwork {
494    fn from(network: Network) -> Self {
495        match network {
496            Network::Mainnet => BitcoinNetwork::Bitcoin,
497            Network::Regtest => BitcoinNetwork::Regtest,
498        }
499    }
500}
501
502impl From<Network> for breez_sdk_common::network::BitcoinNetwork {
503    fn from(network: Network) -> Self {
504        match network {
505            Network::Mainnet => breez_sdk_common::network::BitcoinNetwork::Bitcoin,
506            Network::Regtest => breez_sdk_common::network::BitcoinNetwork::Regtest,
507        }
508    }
509}
510
511impl From<Network> for bitcoin::Network {
512    fn from(network: Network) -> Self {
513        match network {
514            Network::Mainnet => bitcoin::Network::Bitcoin,
515            Network::Regtest => bitcoin::Network::Regtest,
516        }
517    }
518}
519
520impl FromStr for Network {
521    type Err = String;
522
523    fn from_str(s: &str) -> Result<Self, Self::Err> {
524        match s {
525            "mainnet" => Ok(Network::Mainnet),
526            "regtest" => Ok(Network::Regtest),
527            _ => Err("Invalid network".to_string()),
528        }
529    }
530}
531
532#[derive(Debug, Clone)]
533#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
534#[allow(clippy::struct_excessive_bools)]
535pub struct Config {
536    pub api_key: Option<String>,
537    pub network: Network,
538    pub sync_interval_secs: u32,
539
540    // The maximum fee that can be paid for a static deposit claim
541    // If not set then any fee is allowed
542    pub max_deposit_claim_fee: Option<MaxFee>,
543
544    /// The domain used for receiving through lnurl-pay and lightning address.
545    pub lnurl_domain: Option<String>,
546
547    /// When this is set to `true` we will prefer to use spark payments over
548    /// lightning when sending and receiving. This has the benefit of lower fees
549    /// but is at the cost of privacy.
550    pub prefer_spark_over_lightning: bool,
551
552    /// A set of external input parsers that are used by [`BreezSdk::parse`](crate::sdk::BreezSdk::parse) when the input
553    /// is not recognized. See [`ExternalInputParser`] for more details on how to configure
554    /// external parsing.
555    pub external_input_parsers: Option<Vec<ExternalInputParser>>,
556    /// The SDK includes some default external input parsers
557    /// ([`DEFAULT_EXTERNAL_INPUT_PARSERS`]).
558    /// Set this to false in order to prevent their use.
559    pub use_default_external_input_parsers: bool,
560
561    /// Url to use for the real-time sync server. Defaults to the Breez real-time sync server.
562    pub real_time_sync_server_url: Option<String>,
563
564    /// Whether the Spark private mode is enabled by default.
565    ///
566    /// If set to true, the Spark private mode will be enabled on the first initialization of the SDK.
567    /// If set to false, no changes will be made to the Spark private mode.
568    pub private_enabled_default: bool,
569
570    /// Configuration for leaf optimization.
571    ///
572    /// Leaf optimization controls the denominations of leaves that are held in the wallet.
573    /// Fewer, bigger leaves allow for more funds to be exited unilaterally.
574    /// More leaves allow payments to be made without needing a swap, reducing payment latency.
575    pub optimization_config: OptimizationConfig,
576
577    /// Configuration for automatic conversion of Bitcoin to stable tokens.
578    ///
579    /// When set, received sats will be automatically converted to the specified token
580    /// once the balance exceeds the threshold.
581    pub stable_balance_config: Option<StableBalanceConfig>,
582
583    /// Maximum number of concurrent transfer claims.
584    ///
585    /// Default is 4. Increase for server environments with high incoming payment volume.
586    pub max_concurrent_claims: u32,
587
588    /// When true, enables LNURL verify support (LUD-21) and zap receipts (NIP-57).
589    /// When false (default), these features are disabled for privacy.
590    pub support_lnurl_verify: bool,
591}
592
593#[derive(Debug, Clone)]
594#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
595pub struct OptimizationConfig {
596    /// Whether automatic leaf optimization is enabled.
597    ///
598    /// If set to true, the SDK will automatically optimize the leaf set when it changes.
599    /// Otherwise, the manual optimization API must be used to optimize the leaf set.
600    ///
601    /// Default value is true.
602    pub auto_enabled: bool,
603    /// The desired multiplicity for the leaf set.
604    ///
605    /// Setting this to 0 will optimize for maximizing unilateral exit.
606    /// Higher values will optimize for minimizing transfer swaps, with higher values
607    /// being more aggressive and allowing better TPS rates.
608    ///
609    /// For end-user wallets, values of 1-5 are recommended. Values above 5 are
610    /// intended for high-throughput server environments and are not recommended
611    /// for end-user wallets due to significantly higher unilateral exit costs.
612    ///
613    /// Default value is 1.
614    pub multiplicity: u8,
615}
616
617/// Configuration for automatic conversion of Bitcoin to stable tokens.
618///
619/// When configured, the SDK automatically monitors the Bitcoin balance after each
620/// wallet sync. When the balance exceeds the configured threshold plus the reserved
621/// amount, the SDK automatically converts the excess balance (above the reserve)
622/// to the specified stable token.
623///
624/// When the balance is held in a stable token, Bitcoin payments can still be sent.
625/// The SDK automatically detects when there's not enough Bitcoin balance to cover a
626/// payment and auto-populates the token-to-Bitcoin conversion options to facilitate
627/// the payment.
628#[derive(Debug, Clone)]
629#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
630pub struct StableBalanceConfig {
631    /// The token identifier to convert Bitcoin to (required).
632    pub token_identifier: String,
633
634    /// The minimum sats balance that triggers auto-conversion.
635    ///
636    /// If not provided, uses the minimum from conversion limits.
637    /// If provided but less than the conversion limit minimum, the limit minimum is used.
638    #[cfg_attr(feature = "uniffi", uniffi(default = None))]
639    pub threshold_sats: Option<u64>,
640
641    /// Maximum slippage in basis points (1/100 of a percent).
642    ///
643    /// Defaults to 50 bps (0.5%) if not set.
644    #[cfg_attr(feature = "uniffi", uniffi(default = None))]
645    pub max_slippage_bps: Option<u32>,
646
647    /// Amount of sats to keep as Bitcoin and not convert to stable tokens.
648    ///
649    /// This reserve ensures you can send Bitcoin payments without hitting
650    /// the minimum conversion limit. Defaults to the conversion minimum if not set.
651    #[cfg_attr(feature = "uniffi", uniffi(default = None))]
652    pub reserved_sats: Option<u64>,
653}
654
655impl Config {
656    /// Validates the configuration.
657    ///
658    /// Returns an error if any configuration values are invalid.
659    pub fn validate(&self) -> Result<(), SdkError> {
660        if self.max_concurrent_claims == 0 {
661            return Err(SdkError::InvalidInput(
662                "max_concurrent_claims must be greater than 0".to_string(),
663            ));
664        }
665        Ok(())
666    }
667
668    pub(crate) fn get_all_external_input_parsers(&self) -> Vec<ExternalInputParser> {
669        let mut external_input_parsers = Vec::new();
670        if self.use_default_external_input_parsers {
671            let default_parsers = DEFAULT_EXTERNAL_INPUT_PARSERS
672                .iter()
673                .map(|(id, regex, url)| ExternalInputParser {
674                    provider_id: (*id).to_string(),
675                    input_regex: (*regex).to_string(),
676                    parser_url: (*url).to_string(),
677                })
678                .collect::<Vec<_>>();
679            external_input_parsers.extend(default_parsers);
680        }
681        external_input_parsers.extend(self.external_input_parsers.clone().unwrap_or_default());
682
683        external_input_parsers
684    }
685}
686
687#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
688#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
689pub enum MaxFee {
690    // Fixed fee amount in sats
691    Fixed { amount: u64 },
692    // Relative fee rate in satoshis per vbyte
693    Rate { sat_per_vbyte: u64 },
694    // Fastest network recommended fee at the time of claim, with a leeway in satoshis per vbyte
695    NetworkRecommended { leeway_sat_per_vbyte: u64 },
696}
697
698impl MaxFee {
699    pub(crate) async fn to_fee(&self, client: &dyn BitcoinChainService) -> Result<Fee, SdkError> {
700        match self {
701            MaxFee::Fixed { amount } => Ok(Fee::Fixed { amount: *amount }),
702            MaxFee::Rate { sat_per_vbyte } => Ok(Fee::Rate {
703                sat_per_vbyte: *sat_per_vbyte,
704            }),
705            MaxFee::NetworkRecommended {
706                leeway_sat_per_vbyte,
707            } => {
708                let recommended_fees = client.recommended_fees().await?;
709                let max_fee_rate = recommended_fees
710                    .fastest_fee
711                    .saturating_add(*leeway_sat_per_vbyte);
712                Ok(Fee::Rate {
713                    sat_per_vbyte: max_fee_rate,
714                })
715            }
716        }
717    }
718}
719
720#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
721#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
722pub enum Fee {
723    // Fixed fee amount in sats
724    Fixed { amount: u64 },
725    // Relative fee rate in satoshis per vbyte
726    Rate { sat_per_vbyte: u64 },
727}
728
729impl Fee {
730    pub fn to_sats(&self, vbytes: u64) -> u64 {
731        match self {
732            Fee::Fixed { amount } => *amount,
733            Fee::Rate { sat_per_vbyte } => sat_per_vbyte.saturating_mul(vbytes),
734        }
735    }
736}
737
738#[derive(Debug, Clone, Serialize, Deserialize)]
739#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
740pub struct DepositInfo {
741    pub txid: String,
742    pub vout: u32,
743    pub amount_sats: u64,
744    pub refund_tx: Option<String>,
745    pub refund_tx_id: Option<String>,
746    pub claim_error: Option<DepositClaimError>,
747}
748
749#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
750pub struct ClaimDepositRequest {
751    pub txid: String,
752    pub vout: u32,
753    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
754    pub max_fee: Option<MaxFee>,
755}
756
757#[derive(Debug, Clone, Serialize)]
758#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
759pub struct ClaimDepositResponse {
760    pub payment: Payment,
761}
762
763#[derive(Debug, Clone, Serialize)]
764#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
765pub struct RefundDepositRequest {
766    pub txid: String,
767    pub vout: u32,
768    pub destination_address: String,
769    pub fee: Fee,
770}
771
772#[derive(Debug, Clone, Serialize)]
773#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
774pub struct RefundDepositResponse {
775    pub tx_id: String,
776    pub tx_hex: String,
777}
778
779#[derive(Debug, Clone, Serialize)]
780#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
781pub struct ListUnclaimedDepositsRequest {}
782
783#[derive(Debug, Clone, Serialize)]
784#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
785pub struct ListUnclaimedDepositsResponse {
786    pub deposits: Vec<DepositInfo>,
787}
788
789/// Request to buy Bitcoin using an external provider (`MoonPay`)
790#[derive(Debug, Clone, Default)]
791#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
792pub struct BuyBitcoinRequest {
793    /// Optional: Lock the purchase to a specific amount in satoshis.
794    /// When provided, the user cannot change the amount in the purchase flow.
795    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
796    pub locked_amount_sat: Option<u64>,
797    /// Optional: Custom redirect URL after purchase completion
798    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
799    pub redirect_url: Option<String>,
800}
801
802/// Response containing a URL to complete the Bitcoin purchase
803#[derive(Debug, Clone, Serialize)]
804#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
805pub struct BuyBitcoinResponse {
806    /// The URL to open in a browser to complete the purchase
807    pub url: String,
808}
809
810impl std::fmt::Display for MaxFee {
811    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
812        match self {
813            MaxFee::Fixed { amount } => write!(f, "Fixed: {amount}"),
814            MaxFee::Rate { sat_per_vbyte } => write!(f, "Rate: {sat_per_vbyte}"),
815            MaxFee::NetworkRecommended {
816                leeway_sat_per_vbyte,
817            } => write!(f, "NetworkRecommended: {leeway_sat_per_vbyte}"),
818        }
819    }
820}
821
822#[derive(Debug, Clone)]
823#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
824pub struct Credentials {
825    pub username: String,
826    pub password: String,
827}
828
829/// Request to get the balance of the wallet
830#[derive(Debug, Clone)]
831#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
832pub struct GetInfoRequest {
833    pub ensure_synced: Option<bool>,
834}
835
836/// Response containing the balance of the wallet
837#[derive(Debug, Clone, Serialize)]
838#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
839pub struct GetInfoResponse {
840    /// The identity public key of the wallet as a hex string
841    pub identity_pubkey: String,
842    /// The balance in satoshis
843    pub balance_sats: u64,
844    /// The balances of the tokens in the wallet keyed by the token identifier
845    pub token_balances: HashMap<String, TokenBalance>,
846}
847
848#[derive(Debug, Clone, Serialize, Deserialize)]
849#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
850pub struct TokenBalance {
851    pub balance: u128,
852    pub token_metadata: TokenMetadata,
853}
854
855#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
856#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
857pub struct TokenMetadata {
858    pub identifier: String,
859    /// Hex representation of the issuer public key
860    pub issuer_public_key: String,
861    pub name: String,
862    pub ticker: String,
863    /// Number of decimals the token uses
864    pub decimals: u32,
865    pub max_supply: u128,
866    pub is_freezable: bool,
867}
868
869/// Request to sync the wallet with the Spark network
870#[derive(Debug, Clone)]
871#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
872pub struct SyncWalletRequest {}
873
874/// Response from synchronizing the wallet
875#[derive(Debug, Clone, Serialize)]
876#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
877pub struct SyncWalletResponse {}
878
879#[derive(Debug, Clone, Serialize)]
880#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
881pub enum ReceivePaymentMethod {
882    SparkAddress,
883    SparkInvoice {
884        /// Amount to receive. Denominated in sats if token identifier is empty, otherwise in the token base units
885        amount: Option<u128>,
886        /// The presence of this field indicates that the payment is for a token
887        /// If empty, it is a Bitcoin payment
888        token_identifier: Option<String>,
889        /// The expiry time of the invoice as a unix timestamp in seconds
890        expiry_time: Option<u64>,
891        /// A description to embed in the invoice.
892        description: Option<String>,
893        /// If set, the invoice may only be fulfilled by a payer with this public key
894        sender_public_key: Option<String>,
895    },
896    BitcoinAddress,
897    Bolt11Invoice {
898        description: String,
899        amount_sats: Option<u64>,
900        /// The expiry of the invoice as a duration in seconds
901        expiry_secs: Option<u32>,
902        /// If set, creates a HODL invoice with this payment hash (hex-encoded).
903        /// The payer's HTLC will be held until the preimage is provided via
904        /// `claim_htlc_payment` or the HTLC expires.
905        payment_hash: Option<String>,
906    },
907}
908
909#[derive(Debug, Clone, Serialize)]
910#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
911pub enum SendPaymentMethod {
912    BitcoinAddress {
913        address: BitcoinAddressDetails,
914        fee_quote: SendOnchainFeeQuote,
915    },
916    Bolt11Invoice {
917        invoice_details: Bolt11InvoiceDetails,
918        spark_transfer_fee_sats: Option<u64>,
919        lightning_fee_sats: u64,
920    }, // should be replaced with the parsed invoice
921    SparkAddress {
922        address: String,
923        /// Fee to pay for the transaction
924        /// Denominated in sats if token identifier is empty, otherwise in the token base units
925        fee: u128,
926        /// The presence of this field indicates that the payment is for a token
927        /// If empty, it is a Bitcoin payment
928        token_identifier: Option<String>,
929    },
930    SparkInvoice {
931        spark_invoice_details: SparkInvoiceDetails,
932        /// Fee to pay for the transaction
933        /// Denominated in sats if token identifier is empty, otherwise in the token base units
934        fee: u128,
935        /// The presence of this field indicates that the payment is for a token
936        /// If empty, it is a Bitcoin payment
937        token_identifier: Option<String>,
938    },
939}
940
941#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
942#[derive(Debug, Clone, Serialize)]
943pub struct SendOnchainFeeQuote {
944    pub id: String,
945    pub expires_at: u64,
946    pub speed_fast: SendOnchainSpeedFeeQuote,
947    pub speed_medium: SendOnchainSpeedFeeQuote,
948    pub speed_slow: SendOnchainSpeedFeeQuote,
949}
950
951#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
952#[derive(Debug, Clone, Serialize)]
953pub struct SendOnchainSpeedFeeQuote {
954    pub user_fee_sat: u64,
955    pub l1_broadcast_fee_sat: u64,
956}
957
958impl SendOnchainSpeedFeeQuote {
959    pub fn total_fee_sat(&self) -> u64 {
960        self.user_fee_sat.saturating_add(self.l1_broadcast_fee_sat)
961    }
962}
963
964#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
965pub struct ReceivePaymentRequest {
966    pub payment_method: ReceivePaymentMethod,
967}
968
969#[derive(Debug, Clone, Serialize)]
970#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
971pub struct ReceivePaymentResponse {
972    pub payment_request: String,
973    /// Fee to pay to receive the payment
974    /// Denominated in sats or token base units
975    pub fee: u128,
976}
977
978#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
979pub struct PrepareLnurlPayRequest {
980    /// The amount to send in satoshis.
981    pub amount_sats: u64,
982    pub pay_request: LnurlPayRequestDetails,
983    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
984    pub comment: Option<String>,
985    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
986    pub validate_success_action_url: Option<bool>,
987    /// If provided, the payment will include a token conversion step before sending the payment
988    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
989    pub conversion_options: Option<ConversionOptions>,
990    /// How fees should be handled. Defaults to `FeesExcluded` (fees added on top).
991    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
992    pub fee_policy: Option<FeePolicy>,
993}
994
995#[derive(Debug, Clone)]
996#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
997pub struct PrepareLnurlPayResponse {
998    /// The amount to send in satoshis.
999    pub amount_sats: u64,
1000    pub comment: Option<String>,
1001    pub pay_request: LnurlPayRequestDetails,
1002    /// The fee in satoshis. For `FeesIncluded` operations, this represents the total fee
1003    /// (including potential overpayment).
1004    pub fee_sats: u64,
1005    pub invoice_details: Bolt11InvoiceDetails,
1006    pub success_action: Option<SuccessAction>,
1007    /// When set, the payment will include a token conversion step before sending the payment
1008    pub conversion_estimate: Option<ConversionEstimate>,
1009    /// How fees are handled for this payment.
1010    pub fee_policy: FeePolicy,
1011}
1012
1013#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1014pub struct LnurlPayRequest {
1015    pub prepare_response: PrepareLnurlPayResponse,
1016    /// If set, providing the same idempotency key for multiple requests will ensure that only one
1017    /// payment is made. If an idempotency key is re-used, the same payment will be returned.
1018    /// The idempotency key must be a valid UUID.
1019    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1020    pub idempotency_key: Option<String>,
1021}
1022
1023#[derive(Debug, Serialize)]
1024#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1025pub struct LnurlPayResponse {
1026    pub payment: Payment,
1027    pub success_action: Option<SuccessActionProcessed>,
1028}
1029
1030#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1031pub struct LnurlWithdrawRequest {
1032    /// The amount to withdraw in satoshis
1033    /// Must be within the min and max withdrawable limits
1034    pub amount_sats: u64,
1035    pub withdraw_request: LnurlWithdrawRequestDetails,
1036    /// If set, the function will return the payment if it is still pending after this
1037    /// number of seconds. If unset, the function will return immediately after
1038    /// initiating the LNURL withdraw.
1039    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1040    pub completion_timeout_secs: Option<u32>,
1041}
1042
1043#[derive(Debug, Serialize)]
1044#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1045pub struct LnurlWithdrawResponse {
1046    /// The Lightning invoice generated for the LNURL withdraw
1047    pub payment_request: String,
1048    pub payment: Option<Payment>,
1049}
1050
1051/// Represents the payment LNURL info
1052#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
1053#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1054pub struct LnurlPayInfo {
1055    pub ln_address: Option<String>,
1056    pub comment: Option<String>,
1057    pub domain: Option<String>,
1058    pub metadata: Option<String>,
1059    pub processed_success_action: Option<SuccessActionProcessed>,
1060    pub raw_success_action: Option<SuccessAction>,
1061}
1062
1063/// Represents the withdraw LNURL info
1064#[derive(Clone, Debug, Deserialize, Serialize)]
1065#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1066pub struct LnurlWithdrawInfo {
1067    pub withdraw_url: String,
1068}
1069
1070impl LnurlPayInfo {
1071    pub fn extract_description(&self) -> Option<String> {
1072        let Some(metadata) = &self.metadata else {
1073            return None;
1074        };
1075
1076        let Ok(metadata) = serde_json::from_str::<Vec<Vec<Value>>>(metadata) else {
1077            return None;
1078        };
1079
1080        for arr in metadata {
1081            if arr.len() != 2 {
1082                continue;
1083            }
1084            if let (Some(key), Some(value)) = (arr[0].as_str(), arr[1].as_str())
1085                && key == "text/plain"
1086            {
1087                return Some(value.to_string());
1088            }
1089        }
1090
1091        None
1092    }
1093}
1094
1095/// Specifies how fees are handled in a payment.
1096#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
1097#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1098pub enum FeePolicy {
1099    /// Fees are added on top of the specified amount (default behavior).
1100    /// The receiver gets the exact amount specified.
1101    #[default]
1102    FeesExcluded,
1103    /// Fees are deducted from the specified amount.
1104    /// The receiver gets the amount minus fees.
1105    FeesIncluded,
1106}
1107
1108#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1109#[derive(Debug, Clone, Serialize)]
1110pub enum OnchainConfirmationSpeed {
1111    Fast,
1112    Medium,
1113    Slow,
1114}
1115
1116#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1117pub struct PrepareSendPaymentRequest {
1118    pub payment_request: String,
1119    /// The amount to send.
1120    /// Optional for payment requests with embedded amounts (e.g., Spark/Bolt11 invoices with amounts).
1121    /// Required for Spark addresses, Bitcoin addresses, and amountless invoices.
1122    /// Denominated in satoshis for Bitcoin payments, or token base units for token payments.
1123    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1124    pub amount: Option<u128>,
1125    /// Optional token identifier for token payments.
1126    /// Absence indicates that the payment is a Bitcoin payment.
1127    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1128    pub token_identifier: Option<String>,
1129    /// If provided, the payment will include a conversion step before sending the payment
1130    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1131    pub conversion_options: Option<ConversionOptions>,
1132    /// How fees should be handled. Defaults to `FeesExcluded` (fees added on top).
1133    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1134    pub fee_policy: Option<FeePolicy>,
1135}
1136
1137#[derive(Debug, Clone, Serialize)]
1138#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1139pub struct PrepareSendPaymentResponse {
1140    pub payment_method: SendPaymentMethod,
1141    /// The amount for the payment.
1142    /// Denominated in satoshis for Bitcoin payments, or token base units for token payments.
1143    pub amount: u128,
1144    /// Optional token identifier for token payments.
1145    /// Absence indicates that the payment is a Bitcoin payment.
1146    pub token_identifier: Option<String>,
1147    /// When set, the payment will include a conversion step before sending the payment
1148    pub conversion_estimate: Option<ConversionEstimate>,
1149    /// How fees are handled for this payment.
1150    pub fee_policy: FeePolicy,
1151}
1152
1153#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1154pub enum SendPaymentOptions {
1155    BitcoinAddress {
1156        /// Confirmation speed for the on-chain transaction.
1157        confirmation_speed: OnchainConfirmationSpeed,
1158    },
1159    Bolt11Invoice {
1160        prefer_spark: bool,
1161
1162        /// If set, the function will return the payment if it is still pending after this
1163        /// number of seconds. If unset, the function will return immediately after initiating the payment.
1164        completion_timeout_secs: Option<u32>,
1165    },
1166    SparkAddress {
1167        /// Can only be provided for Bitcoin payments. If set, a Spark HTLC transfer will be created.
1168        /// The receiver will need to provide the preimage to claim it.
1169        htlc_options: Option<SparkHtlcOptions>,
1170    },
1171}
1172
1173#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1174pub struct SparkHtlcOptions {
1175    /// The payment hash of the HTLC. The receiver will need to provide the associated preimage to claim it.
1176    pub payment_hash: String,
1177    /// The duration of the HTLC in seconds.
1178    /// After this time, the HTLC will be returned.
1179    pub expiry_duration_secs: u64,
1180}
1181
1182#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1183pub struct SendPaymentRequest {
1184    pub prepare_response: PrepareSendPaymentResponse,
1185    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1186    pub options: Option<SendPaymentOptions>,
1187    /// The optional idempotency key for all Spark based transfers (excludes token payments).
1188    /// If set, providing the same idempotency key for multiple requests will ensure that only one
1189    /// payment is made. If an idempotency key is re-used, the same payment will be returned.
1190    /// The idempotency key must be a valid UUID.
1191    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1192    pub idempotency_key: Option<String>,
1193}
1194
1195#[derive(Debug, Clone, Serialize)]
1196#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1197pub struct SendPaymentResponse {
1198    pub payment: Payment,
1199}
1200
1201#[derive(Debug, Clone)]
1202#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1203pub enum PaymentDetailsFilter {
1204    Spark {
1205        /// Filter specific Spark HTLC statuses
1206        htlc_status: Option<Vec<SparkHtlcStatus>>,
1207        /// Filter conversion payments with refund information
1208        conversion_refund_needed: Option<bool>,
1209    },
1210    Token {
1211        /// Filter conversion payments with refund information
1212        conversion_refund_needed: Option<bool>,
1213        /// Filter by transaction hash
1214        tx_hash: Option<String>,
1215        /// Filter by transaction type
1216        tx_type: Option<TokenTransactionType>,
1217    },
1218    Lightning {
1219        /// Filter specific Spark HTLC statuses
1220        htlc_status: Option<Vec<SparkHtlcStatus>>,
1221    },
1222}
1223
1224/// Request to list payments with optional filters and pagination
1225#[derive(Debug, Clone, Default)]
1226#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1227pub struct ListPaymentsRequest {
1228    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1229    pub type_filter: Option<Vec<PaymentType>>,
1230    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1231    pub status_filter: Option<Vec<PaymentStatus>>,
1232    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1233    pub asset_filter: Option<AssetFilter>,
1234    /// Only include payments matching at least one of these payment details filters
1235    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1236    pub payment_details_filter: Option<Vec<PaymentDetailsFilter>>,
1237    /// Only include payments created after this timestamp (inclusive)
1238    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1239    pub from_timestamp: Option<u64>,
1240    /// Only include payments created before this timestamp (exclusive)
1241    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1242    pub to_timestamp: Option<u64>,
1243    /// Number of records to skip
1244    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1245    pub offset: Option<u32>,
1246    /// Maximum number of records to return
1247    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1248    pub limit: Option<u32>,
1249    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1250    pub sort_ascending: Option<bool>,
1251}
1252
1253/// A field of [`ListPaymentsRequest`] when listing payments filtered by asset
1254#[derive(Debug, Clone)]
1255#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1256pub enum AssetFilter {
1257    Bitcoin,
1258    Token {
1259        /// Optional token identifier to filter by
1260        token_identifier: Option<String>,
1261    },
1262}
1263
1264impl FromStr for AssetFilter {
1265    type Err = String;
1266
1267    fn from_str(s: &str) -> Result<Self, Self::Err> {
1268        Ok(match s.to_lowercase().as_str() {
1269            "bitcoin" => AssetFilter::Bitcoin,
1270            "token" => AssetFilter::Token {
1271                token_identifier: None,
1272            },
1273            str if str.starts_with("token:") => AssetFilter::Token {
1274                token_identifier: Some(
1275                    str.split_once(':')
1276                        .ok_or(format!("Invalid asset filter '{s}'"))?
1277                        .1
1278                        .to_string(),
1279                ),
1280            },
1281            _ => return Err(format!("Invalid asset filter '{s}'")),
1282        })
1283    }
1284}
1285
1286/// Response from listing payments
1287#[derive(Debug, Clone, Serialize)]
1288#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1289pub struct ListPaymentsResponse {
1290    /// The list of payments
1291    pub payments: Vec<Payment>,
1292}
1293
1294#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1295pub struct GetPaymentRequest {
1296    pub payment_id: String,
1297}
1298
1299#[derive(Debug, Clone, Serialize)]
1300#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1301pub struct GetPaymentResponse {
1302    pub payment: Payment,
1303}
1304
1305#[cfg_attr(feature = "uniffi", uniffi::export(callback_interface))]
1306pub trait Logger: Send + Sync {
1307    fn log(&self, l: LogEntry);
1308}
1309
1310#[derive(Debug, Clone, Serialize)]
1311#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1312pub struct LogEntry {
1313    pub line: String,
1314    pub level: String,
1315}
1316
1317#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1318#[derive(Debug, Clone, Serialize, Deserialize)]
1319pub struct CheckLightningAddressRequest {
1320    pub username: String,
1321}
1322
1323#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1324#[derive(Debug, Clone, Serialize, Deserialize)]
1325pub struct RegisterLightningAddressRequest {
1326    pub username: String,
1327    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1328    pub description: Option<String>,
1329}
1330
1331#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1332#[derive(Debug, Clone, Deserialize, Serialize)]
1333pub struct LnurlInfo {
1334    pub url: String,
1335    pub bech32: String,
1336}
1337
1338impl LnurlInfo {
1339    pub fn new(url: String) -> Self {
1340        let bech32 =
1341            breez_sdk_common::lnurl::encode_lnurl_to_bech32(&url).unwrap_or_else(|_| url.clone());
1342        Self { url, bech32 }
1343    }
1344}
1345
1346#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1347#[derive(Deserialize, Serialize)]
1348pub struct LightningAddressInfo {
1349    pub description: String,
1350    pub lightning_address: String,
1351    pub lnurl: LnurlInfo,
1352    pub username: String,
1353}
1354
1355impl From<RecoverLnurlPayResponse> for LightningAddressInfo {
1356    fn from(resp: RecoverLnurlPayResponse) -> Self {
1357        Self {
1358            description: resp.description,
1359            lightning_address: resp.lightning_address,
1360            lnurl: LnurlInfo::new(resp.lnurl),
1361            username: resp.username,
1362        }
1363    }
1364}
1365
1366#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1367#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1368pub enum KeySetType {
1369    #[default]
1370    Default,
1371    Taproot,
1372    NativeSegwit,
1373    WrappedSegwit,
1374    Legacy,
1375}
1376
1377impl From<spark_wallet::KeySetType> for KeySetType {
1378    fn from(value: spark_wallet::KeySetType) -> Self {
1379        match value {
1380            spark_wallet::KeySetType::Default => KeySetType::Default,
1381            spark_wallet::KeySetType::Taproot => KeySetType::Taproot,
1382            spark_wallet::KeySetType::NativeSegwit => KeySetType::NativeSegwit,
1383            spark_wallet::KeySetType::WrappedSegwit => KeySetType::WrappedSegwit,
1384            spark_wallet::KeySetType::Legacy => KeySetType::Legacy,
1385        }
1386    }
1387}
1388
1389impl From<KeySetType> for spark_wallet::KeySetType {
1390    fn from(value: KeySetType) -> Self {
1391        match value {
1392            KeySetType::Default => spark_wallet::KeySetType::Default,
1393            KeySetType::Taproot => spark_wallet::KeySetType::Taproot,
1394            KeySetType::NativeSegwit => spark_wallet::KeySetType::NativeSegwit,
1395            KeySetType::WrappedSegwit => spark_wallet::KeySetType::WrappedSegwit,
1396            KeySetType::Legacy => spark_wallet::KeySetType::Legacy,
1397        }
1398    }
1399}
1400
1401/// Configuration for key set derivation.
1402///
1403/// This struct encapsulates the parameters needed for BIP32 key derivation.
1404#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1405#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1406pub struct KeySetConfig {
1407    /// The key set type which determines the derivation path
1408    pub key_set_type: KeySetType,
1409    /// Controls the structure of the BIP derivation path
1410    pub use_address_index: bool,
1411    /// Optional account number for key derivation
1412    pub account_number: Option<u32>,
1413}
1414
1415impl Default for KeySetConfig {
1416    fn default() -> Self {
1417        Self {
1418            key_set_type: KeySetType::Default,
1419            use_address_index: false,
1420            account_number: None,
1421        }
1422    }
1423}
1424
1425/// Response from listing fiat currencies
1426#[derive(Debug, Clone, Serialize)]
1427#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1428pub struct ListFiatCurrenciesResponse {
1429    /// The list of fiat currencies
1430    pub currencies: Vec<FiatCurrency>,
1431}
1432
1433/// Response from listing fiat rates
1434#[derive(Debug, Clone, Serialize)]
1435#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1436pub struct ListFiatRatesResponse {
1437    /// The list of fiat rates
1438    pub rates: Vec<Rate>,
1439}
1440
1441/// The operational status of a Spark service.
1442#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
1443#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1444pub enum ServiceStatus {
1445    /// Service is fully operational.
1446    Operational,
1447    /// Service is experiencing degraded performance.
1448    Degraded,
1449    /// Service is partially unavailable.
1450    Partial,
1451    /// Service status is unknown.
1452    Unknown,
1453    /// Service is experiencing a major outage.
1454    Major,
1455}
1456
1457/// The status of the Spark network services relevant to the SDK.
1458#[derive(Debug, Clone, Serialize)]
1459#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1460pub struct SparkStatus {
1461    /// The worst status across all relevant services.
1462    pub status: ServiceStatus,
1463    /// The last time the status was updated, as a unix timestamp in seconds.
1464    pub last_updated: u64,
1465}
1466
1467pub(crate) enum WaitForPaymentIdentifier {
1468    PaymentId(String),
1469    PaymentRequest(String),
1470}
1471
1472#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1473pub struct GetTokensMetadataRequest {
1474    pub token_identifiers: Vec<String>,
1475}
1476
1477#[derive(Debug, Clone, Serialize)]
1478#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1479pub struct GetTokensMetadataResponse {
1480    pub tokens_metadata: Vec<TokenMetadata>,
1481}
1482
1483#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1484pub struct SignMessageRequest {
1485    pub message: String,
1486    /// If true, the signature will be encoded in compact format instead of DER format
1487    pub compact: bool,
1488}
1489
1490#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1491pub struct SignMessageResponse {
1492    pub pubkey: String,
1493    /// The DER or compact hex encoded signature
1494    pub signature: String,
1495}
1496
1497#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1498pub struct CheckMessageRequest {
1499    /// The message that was signed
1500    pub message: String,
1501    /// The public key that signed the message
1502    pub pubkey: String,
1503    /// The DER or compact hex encoded signature
1504    pub signature: String,
1505}
1506
1507#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1508pub struct CheckMessageResponse {
1509    pub is_valid: bool,
1510}
1511
1512#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1513#[derive(Debug, Clone, Serialize)]
1514pub struct UserSettings {
1515    pub spark_private_mode_enabled: bool,
1516}
1517
1518#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1519pub struct UpdateUserSettingsRequest {
1520    pub spark_private_mode_enabled: Option<bool>,
1521}
1522
1523#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1524pub struct ClaimHtlcPaymentRequest {
1525    pub preimage: String,
1526}
1527
1528#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1529pub struct ClaimHtlcPaymentResponse {
1530    pub payment: Payment,
1531}
1532
1533#[derive(Debug, Clone, Deserialize, Serialize)]
1534#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1535pub struct LnurlReceiveMetadata {
1536    pub nostr_zap_request: Option<String>,
1537    pub nostr_zap_receipt: Option<String>,
1538    pub sender_comment: Option<String>,
1539}
1540
1541#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1542pub struct OptimizationProgress {
1543    pub is_running: bool,
1544    pub current_round: u32,
1545    pub total_rounds: u32,
1546}