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