Skip to main content

breez_sdk_spark/models/
mod.rs

1pub(crate) mod adaptors;
2pub mod payment_observer;
3pub use payment_observer::*;
4
5// Re-export public conversion types from the conversion module
6pub use crate::token_conversion::{
7    AmountAdjustmentReason, ConversionEstimate, ConversionInfo, ConversionOptions,
8    ConversionPurpose, ConversionStatus, ConversionType, FetchConversionLimitsRequest,
9    FetchConversionLimitsResponse,
10};
11
12use core::fmt;
13use lnurl_models::RecoverLnurlPayResponse;
14use serde::{Deserialize, Serialize};
15use serde_json::Value;
16use std::{
17    collections::{HashMap, HashSet},
18    fmt::Display,
19    str::FromStr,
20};
21
22use crate::{
23    BitcoinAddressDetails, BitcoinChainService, BitcoinNetwork, Bolt11InvoiceDetails,
24    ExternalInputParser, FiatCurrency, LnurlPayRequestDetails, LnurlWithdrawRequestDetails, Rate,
25    SdkError, SparkInvoiceDetails, SuccessAction, SuccessActionProcessed, error::DepositClaimError,
26};
27
28/// A list of external input parsers that are used by default.
29/// To opt-out, set `use_default_external_input_parsers` in [Config] to false.
30pub const DEFAULT_EXTERNAL_INPUT_PARSERS: &[(&str, &str, &str)] = &[
31    (
32        "picknpay",
33        "(.*)(za.co.electrum.picknpay)(.*)",
34        "https://cryptoqr.net/.well-known/lnurlp/<input>",
35    ),
36    (
37        "bootleggers",
38        r"(.*)(wigroup\.co|yoyogroup\.co)(.*)",
39        "https://cryptoqr.net/.well-known/lnurlw/<input>",
40    ),
41];
42
43/// Represents the seed for wallet generation, either as a mnemonic phrase with an optional
44/// passphrase or as raw entropy bytes.
45#[derive(Debug, Clone)]
46#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
47pub enum Seed {
48    /// A BIP-39 mnemonic phrase with an optional passphrase.
49    Mnemonic {
50        /// The mnemonic phrase. 12 or 24 words.
51        mnemonic: String,
52        /// An optional passphrase for the mnemonic.
53        passphrase: Option<String>,
54    },
55    /// Raw entropy bytes.
56    Entropy(Vec<u8>),
57}
58
59impl Seed {
60    pub fn to_bytes(&self) -> Result<Vec<u8>, SdkError> {
61        match self {
62            Seed::Mnemonic {
63                mnemonic,
64                passphrase,
65            } => {
66                let mnemonic = bip39::Mnemonic::parse(mnemonic)
67                    .map_err(|e| SdkError::Generic(e.to_string()))?;
68
69                Ok(mnemonic
70                    .to_seed(passphrase.as_deref().unwrap_or(""))
71                    .to_vec())
72            }
73            Seed::Entropy(entropy) => Ok(entropy.clone()),
74        }
75    }
76}
77
78#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
79pub struct ConnectRequest {
80    pub config: Config,
81    pub seed: Seed,
82    pub storage_dir: String,
83}
84
85/// Request object for connecting to the Spark network using an external signer.
86///
87/// This allows using a custom signer implementation instead of providing a seed directly.
88#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
89#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
90pub struct ConnectWithSignerRequest {
91    pub config: Config,
92    pub signer: std::sync::Arc<dyn crate::signer::ExternalSigner>,
93    pub storage_dir: String,
94}
95
96/// The type of payment
97#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
98#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
99pub enum PaymentType {
100    /// Payment sent from this wallet
101    Send,
102    /// Payment received to this wallet
103    Receive,
104}
105
106impl fmt::Display for PaymentType {
107    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108        match self {
109            PaymentType::Send => write!(f, "send"),
110            PaymentType::Receive => write!(f, "receive"),
111        }
112    }
113}
114
115impl FromStr for PaymentType {
116    type Err = String;
117
118    fn from_str(s: &str) -> Result<Self, Self::Err> {
119        Ok(match s.to_lowercase().as_str() {
120            "receive" => PaymentType::Receive,
121            "send" => PaymentType::Send,
122            _ => return Err(format!("invalid payment type '{s}'")),
123        })
124    }
125}
126
127/// The status of a payment
128#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
129#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
130pub enum PaymentStatus {
131    /// Payment is completed successfully
132    Completed,
133    /// Payment is in progress
134    Pending,
135    /// Payment has failed
136    Failed,
137}
138
139impl PaymentStatus {
140    /// Returns true if the payment status is final (either Completed or Failed)
141    pub fn is_final(&self) -> bool {
142        matches!(self, PaymentStatus::Completed | PaymentStatus::Failed)
143    }
144}
145
146impl fmt::Display for PaymentStatus {
147    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148        match self {
149            PaymentStatus::Completed => write!(f, "completed"),
150            PaymentStatus::Pending => write!(f, "pending"),
151            PaymentStatus::Failed => write!(f, "failed"),
152        }
153    }
154}
155
156impl FromStr for PaymentStatus {
157    type Err = String;
158
159    fn from_str(s: &str) -> Result<Self, Self::Err> {
160        Ok(match s.to_lowercase().as_str() {
161            "completed" => PaymentStatus::Completed,
162            "pending" => PaymentStatus::Pending,
163            "failed" => PaymentStatus::Failed,
164            _ => return Err(format!("Invalid payment status '{s}'")),
165        })
166    }
167}
168
169#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
170#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
171pub enum PaymentMethod {
172    Lightning,
173    Spark,
174    Token,
175    Deposit,
176    Withdraw,
177    Unknown,
178}
179
180impl Display for PaymentMethod {
181    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
182        match self {
183            PaymentMethod::Lightning => write!(f, "lightning"),
184            PaymentMethod::Spark => write!(f, "spark"),
185            PaymentMethod::Token => write!(f, "token"),
186            PaymentMethod::Deposit => write!(f, "deposit"),
187            PaymentMethod::Withdraw => write!(f, "withdraw"),
188            PaymentMethod::Unknown => write!(f, "unknown"),
189        }
190    }
191}
192
193impl FromStr for PaymentMethod {
194    type Err = ();
195
196    fn from_str(s: &str) -> Result<Self, Self::Err> {
197        match s {
198            "lightning" => Ok(PaymentMethod::Lightning),
199            "spark" => Ok(PaymentMethod::Spark),
200            "token" => Ok(PaymentMethod::Token),
201            "deposit" => Ok(PaymentMethod::Deposit),
202            "withdraw" => Ok(PaymentMethod::Withdraw),
203            "unknown" => Ok(PaymentMethod::Unknown),
204            _ => Err(()),
205        }
206    }
207}
208
209/// Represents a payment (sent or received)
210#[derive(Debug, Clone, Serialize, Deserialize)]
211#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
212pub struct Payment {
213    /// Unique identifier for the payment
214    pub id: String,
215    /// Type of payment (send or receive)
216    pub payment_type: PaymentType,
217    /// Status of the payment
218    pub status: PaymentStatus,
219    /// Amount in satoshis or token base units
220    pub amount: u128,
221    /// Fee paid in satoshis or token base units
222    pub fees: u128,
223    /// Timestamp of when the payment was created
224    pub timestamp: u64,
225    /// Method of payment. Sometimes the payment details is empty so this field
226    /// is used to determine the payment method.
227    pub method: PaymentMethod,
228    /// Details of the payment
229    pub details: Option<PaymentDetails>,
230    /// If set, this payment involved a conversion before the payment
231    pub conversion_details: Option<ConversionDetails>,
232}
233
234impl Payment {
235    /// Returns `true` if this payment is a child of a conversion operation.
236    ///
237    /// Conversion operations (stable balance, ongoing sends) create internal child
238    /// payments (send sats→Flashnet, receive tokens). These are identified by having
239    /// `conversion_info` set in their payment details.
240    pub fn is_conversion_child(&self) -> bool {
241        matches!(
242            &self.details,
243            Some(
244                PaymentDetails::Spark {
245                    conversion_info: Some(_),
246                    ..
247                } | PaymentDetails::Token {
248                    conversion_info: Some(_),
249                    ..
250                }
251            )
252        )
253    }
254}
255
256/// Outlines the steps involved in a conversion.
257///
258/// Built progressively: `status` is available immediately from payment metadata,
259/// while `from`/`to` steps are enriched later from child payments.
260#[derive(Debug, Clone, Serialize, Deserialize)]
261#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
262pub struct ConversionDetails {
263    /// Current status of the conversion
264    pub status: ConversionStatus,
265    /// The send step of the conversion (e.g., sats sent to Flashnet)
266    pub from: Option<ConversionStep>,
267    /// The receive step of the conversion (e.g., tokens received from Flashnet)
268    pub to: Option<ConversionStep>,
269}
270
271/// Extracts conversion steps (from/to) from a conversion's child payments.
272///
273/// Each conversion produces a send payment (sats → Flashnet) and a receive payment
274/// (tokens ← Flashnet), linked to the parent payment via `parent_payment_id` in
275/// payment metadata. The SDK queries these children from storage and converts them
276/// into the `from` and `to` steps.
277///
278/// Returns `(None, None)` when no child payments exist yet (e.g. Pending or Failed).
279pub fn conversion_steps_from_payments(
280    payments: &[Payment],
281) -> Result<(Option<ConversionStep>, Option<ConversionStep>), SdkError> {
282    let from = payments
283        .iter()
284        .find(|p| p.payment_type == PaymentType::Send)
285        .map(TryInto::try_into)
286        .transpose()?;
287    let to = payments
288        .iter()
289        .find(|p| p.payment_type == PaymentType::Receive)
290        .map(TryInto::try_into)
291        .transpose()?;
292    Ok((from, to))
293}
294
295/// A single step in a conversion
296#[derive(Debug, Clone, Serialize, Deserialize)]
297#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
298pub struct ConversionStep {
299    /// The underlying payment id of the conversion step
300    pub payment_id: String,
301    /// Payment amount in satoshis or token base units
302    pub amount: u128,
303    /// Fee paid in satoshis or token base units
304    /// This represents the payment fee + the conversion fee
305    pub fee: u128,
306    /// Method of payment
307    pub method: PaymentMethod,
308    /// Token metadata if a token is used for payment
309    pub token_metadata: Option<TokenMetadata>,
310    /// The reason the conversion amount was adjusted, if applicable.
311    #[serde(default)]
312    pub amount_adjustment: Option<AmountAdjustmentReason>,
313}
314
315/// Converts a Spark or Token payment into a `ConversionStep`.
316/// Fees are a sum of the payment fee and the conversion fee, if applicable,
317/// from the payment details. Token metadata should only be set for a token payment.
318impl TryFrom<&Payment> for ConversionStep {
319    type Error = SdkError;
320    fn try_from(payment: &Payment) -> Result<Self, Self::Error> {
321        let (conversion_info, token_metadata) = match &payment.details {
322            Some(PaymentDetails::Spark {
323                conversion_info: Some(info),
324                ..
325            }) => (info, None),
326            Some(PaymentDetails::Token {
327                conversion_info: Some(info),
328                metadata,
329                ..
330            }) => (info, Some(metadata.clone())),
331            _ => {
332                return Err(SdkError::Generic(format!(
333                    "No conversion info available for payment {}",
334                    payment.id
335                )));
336            }
337        };
338        Ok(ConversionStep {
339            payment_id: payment.id.clone(),
340            amount: payment.amount,
341            fee: payment
342                .fees
343                .saturating_add(conversion_info.fee.unwrap_or(0)),
344            method: payment.method,
345            token_metadata,
346            amount_adjustment: conversion_info.amount_adjustment.clone(),
347        })
348    }
349}
350
351#[cfg(feature = "uniffi")]
352uniffi::custom_type!(u128, String, {
353    remote,
354    try_lift: |val| val.parse::<u128>().map_err(uniffi::deps::anyhow::Error::msg),
355    lower: |obj| obj.to_string(),
356});
357
358// TODO: fix large enum variant lint - may be done by boxing lnurl_pay_info but that requires
359//  some changes to the wasm bindgen macro
360#[allow(clippy::large_enum_variant)]
361#[derive(Debug, Clone, Serialize, Deserialize)]
362#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
363pub enum PaymentDetails {
364    Spark {
365        /// The invoice details if the payment fulfilled a spark invoice
366        invoice_details: Option<SparkInvoicePaymentDetails>,
367        /// The HTLC transfer details if the payment fulfilled an HTLC transfer
368        htlc_details: Option<SparkHtlcDetails>,
369        /// The information for a conversion
370        conversion_info: Option<ConversionInfo>,
371    },
372    Token {
373        metadata: TokenMetadata,
374        tx_hash: String,
375        tx_type: TokenTransactionType,
376        /// The invoice details if the payment fulfilled a spark invoice
377        invoice_details: Option<SparkInvoicePaymentDetails>,
378        /// The information for a conversion
379        conversion_info: Option<ConversionInfo>,
380    },
381    Lightning {
382        /// Represents the invoice description
383        description: Option<String>,
384        /// Represents the Bolt11/Bolt12 invoice associated with a payment
385        /// In the case of a Send payment, this is the invoice paid by the user
386        /// In the case of a Receive payment, this is the invoice paid to the user
387        invoice: String,
388
389        /// The invoice destination/payee pubkey
390        destination_pubkey: String,
391
392        /// The HTLC transfer details
393        htlc_details: SparkHtlcDetails,
394
395        /// Lnurl payment information if this was an lnurl payment.
396        lnurl_pay_info: Option<LnurlPayInfo>,
397
398        /// Lnurl withdrawal information if this was an lnurl payment.
399        lnurl_withdraw_info: Option<LnurlWithdrawInfo>,
400
401        /// Lnurl receive information if this was a received lnurl payment.
402        lnurl_receive_metadata: Option<LnurlReceiveMetadata>,
403    },
404    Withdraw {
405        tx_id: String,
406    },
407    Deposit {
408        tx_id: String,
409    },
410}
411
412#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
413#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
414pub enum TokenTransactionType {
415    Transfer,
416    Mint,
417    Burn,
418}
419
420impl fmt::Display for TokenTransactionType {
421    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
422        match self {
423            TokenTransactionType::Transfer => write!(f, "transfer"),
424            TokenTransactionType::Mint => write!(f, "mint"),
425            TokenTransactionType::Burn => write!(f, "burn"),
426        }
427    }
428}
429
430impl FromStr for TokenTransactionType {
431    type Err = String;
432
433    fn from_str(s: &str) -> Result<Self, Self::Err> {
434        match s.to_lowercase().as_str() {
435            "transfer" => Ok(TokenTransactionType::Transfer),
436            "mint" => Ok(TokenTransactionType::Mint),
437            "burn" => Ok(TokenTransactionType::Burn),
438            _ => Err(format!("Invalid token transaction type '{s}'")),
439        }
440    }
441}
442
443#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
444#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
445pub struct SparkInvoicePaymentDetails {
446    /// Represents the spark invoice description
447    pub description: Option<String>,
448    /// The raw spark invoice string
449    pub invoice: String,
450}
451
452#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
453#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
454pub struct SparkHtlcDetails {
455    /// The payment hash of the HTLC
456    pub payment_hash: String,
457    /// The preimage of the HTLC. Empty until receiver has released it.
458    pub preimage: Option<String>,
459    /// The expiry time of the HTLC as a unix timestamp in seconds
460    pub expiry_time: u64,
461    /// The HTLC status
462    pub status: SparkHtlcStatus,
463}
464
465#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
466#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
467pub enum SparkHtlcStatus {
468    /// The HTLC is waiting for the preimage to be shared by the receiver
469    WaitingForPreimage,
470    /// The HTLC preimage has been shared and the transfer can be or has been claimed by the receiver
471    PreimageShared,
472    /// The HTLC has been returned to the sender due to expiry
473    Returned,
474}
475
476impl fmt::Display for SparkHtlcStatus {
477    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
478        match self {
479            SparkHtlcStatus::WaitingForPreimage => write!(f, "WaitingForPreimage"),
480            SparkHtlcStatus::PreimageShared => write!(f, "PreimageShared"),
481            SparkHtlcStatus::Returned => write!(f, "Returned"),
482        }
483    }
484}
485
486impl FromStr for SparkHtlcStatus {
487    type Err = String;
488
489    fn from_str(s: &str) -> Result<Self, Self::Err> {
490        match s {
491            "WaitingForPreimage" => Ok(SparkHtlcStatus::WaitingForPreimage),
492            "PreimageShared" => Ok(SparkHtlcStatus::PreimageShared),
493            "Returned" => Ok(SparkHtlcStatus::Returned),
494            _ => Err("Invalid Spark HTLC status".to_string()),
495        }
496    }
497}
498
499#[derive(Debug, Clone, Copy, PartialEq, Eq)]
500#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
501pub enum Network {
502    Mainnet,
503    Regtest,
504}
505
506impl std::fmt::Display for Network {
507    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
508        match self {
509            Network::Mainnet => write!(f, "Mainnet"),
510            Network::Regtest => write!(f, "Regtest"),
511        }
512    }
513}
514
515impl From<Network> for BitcoinNetwork {
516    fn from(network: Network) -> Self {
517        match network {
518            Network::Mainnet => BitcoinNetwork::Bitcoin,
519            Network::Regtest => BitcoinNetwork::Regtest,
520        }
521    }
522}
523
524impl From<Network> for breez_sdk_common::network::BitcoinNetwork {
525    fn from(network: Network) -> Self {
526        match network {
527            Network::Mainnet => breez_sdk_common::network::BitcoinNetwork::Bitcoin,
528            Network::Regtest => breez_sdk_common::network::BitcoinNetwork::Regtest,
529        }
530    }
531}
532
533impl From<Network> for bitcoin::Network {
534    fn from(network: Network) -> Self {
535        match network {
536            Network::Mainnet => bitcoin::Network::Bitcoin,
537            Network::Regtest => bitcoin::Network::Regtest,
538        }
539    }
540}
541
542impl FromStr for Network {
543    type Err = String;
544
545    fn from_str(s: &str) -> Result<Self, Self::Err> {
546        match s {
547            "mainnet" => Ok(Network::Mainnet),
548            "regtest" => Ok(Network::Regtest),
549            _ => Err("Invalid network".to_string()),
550        }
551    }
552}
553
554#[derive(Debug, Clone)]
555#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
556#[allow(clippy::struct_excessive_bools)]
557pub struct Config {
558    pub api_key: Option<String>,
559    pub network: Network,
560    pub sync_interval_secs: u32,
561
562    // The maximum fee that can be paid for a static deposit claim
563    // If not set then any fee is allowed
564    pub max_deposit_claim_fee: Option<MaxFee>,
565
566    /// The domain used for receiving through lnurl-pay and lightning address.
567    pub lnurl_domain: Option<String>,
568
569    /// When this is set to `true` we will prefer to use spark payments over
570    /// lightning when sending and receiving. This has the benefit of lower fees
571    /// but is at the cost of privacy.
572    pub prefer_spark_over_lightning: bool,
573
574    /// A set of external input parsers that are used by [`BreezSdk::parse`](crate::sdk::BreezSdk::parse) when the input
575    /// is not recognized. See [`ExternalInputParser`] for more details on how to configure
576    /// external parsing.
577    pub external_input_parsers: Option<Vec<ExternalInputParser>>,
578    /// The SDK includes some default external input parsers
579    /// ([`DEFAULT_EXTERNAL_INPUT_PARSERS`]).
580    /// Set this to false in order to prevent their use.
581    pub use_default_external_input_parsers: bool,
582
583    /// Url to use for the real-time sync server. Defaults to the Breez real-time sync server.
584    pub real_time_sync_server_url: Option<String>,
585
586    /// Whether the Spark private mode is enabled by default.
587    ///
588    /// If set to true, the Spark private mode will be enabled on the first
589    /// initialization of the SDK. If set to false, no changes will be made
590    /// to the Spark private mode.
591    ///
592    /// This default is only auto-applied when `background_tasks_enabled` is
593    /// `true`. When `background_tasks_enabled` is `false`, the SDK does not
594    /// touch the Spark private mode on startup; call `update_user_settings`
595    /// with `spark_private_mode_enabled` set as needed on a one-time setup
596    /// pass.
597    pub private_enabled_default: bool,
598
599    /// Configuration for leaf optimization.
600    ///
601    /// Leaf optimization controls the denominations of leaves that are held in the wallet.
602    /// Fewer, bigger leaves allow for more funds to be exited unilaterally.
603    /// More leaves allow payments to be made without needing a swap, reducing payment latency.
604    pub leaf_optimization_config: LeafOptimizationConfig,
605
606    /// Configuration for token-output optimization.
607    ///
608    /// Token-output optimization controls automatic consolidation of a token's
609    /// available outputs. Keeping the output set small reduces transaction size,
610    /// while keeping enough distinct outputs preserves concurrency for parallel
611    /// sends.
612    pub token_optimization_config: TokenOptimizationConfig,
613
614    /// Configuration for automatic conversion of Bitcoin to stable tokens.
615    ///
616    /// When set, received sats will be automatically converted to the specified token
617    /// once the balance exceeds the threshold.
618    pub stable_balance_config: Option<StableBalanceConfig>,
619
620    /// Maximum number of concurrent transfer claims.
621    ///
622    /// Default is 4. Increase for server environments with high incoming payment volume.
623    pub max_concurrent_claims: u32,
624
625    /// Optional custom Spark environment configuration.
626    ///
627    /// When set, overrides the default Spark operator pool, service provider,
628    /// threshold, and token settings. Use this to connect to alternative Spark
629    /// deployments (e.g. dev/staging environments).
630    pub spark_config: Option<SparkConfig>,
631
632    /// Master switch for per-instance background services.
633    ///
634    /// When `true` (default), the SDK runs its standard background work:
635    /// periodic sync, lightning-address recovery, private-mode initialization,
636    /// the leaf and token-output optimizers, the Spark server-event
637    /// subscription, and the real-time sync client (when
638    /// `real_time_sync_server_url` is set).
639    ///
640    /// When `false`, **no background service is started**, regardless of any
641    /// other setting on this config. This is intended for multi-tenant server
642    /// deployments where the host application orchestrates sync and claims
643    /// explicitly and receives events via webhooks. Use
644    /// `default_server_config` to get this preset.
645    ///
646    /// Explicit operations (`sync_wallet`, `claim_deposit`,
647    /// `list_unclaimed_deposits`, `refund_deposit`,
648    /// `refund_pending_conversions`, leaf/token optimization, etc.) work
649    /// regardless of this flag.
650    ///
651    /// When `false`, the SDK rejects builds where fields whose backing
652    /// service is gated off are still in their active shape:
653    /// `stable_balance_config` must be `None`, `real_time_sync_server_url`
654    /// must be `None`, and `optimization_config.auto_enabled` must be `false`.
655    /// `default_server_config` already sets these compatible values.
656    pub background_tasks_enabled: bool,
657}
658
659/// Configuration for leaf optimization.
660#[derive(Debug, Clone)]
661#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
662pub struct LeafOptimizationConfig {
663    /// Whether automatic leaf optimization is enabled.
664    ///
665    /// If set to true, the SDK will automatically optimize the leaf set when it changes.
666    /// Otherwise, the manual optimization API must be used to optimize the leaf set.
667    ///
668    /// Default value is true.
669    pub auto_enabled: bool,
670    /// The desired multiplicity for the leaf set.
671    ///
672    /// Setting this to 0 will optimize for maximizing unilateral exit.
673    /// Higher values will optimize for minimizing transfer swaps, with higher values
674    /// being more aggressive and allowing better TPS rates.
675    ///
676    /// For end-user wallets, values of 1-5 are recommended. Values above 5 are
677    /// intended for high-throughput server environments and are not recommended
678    /// for end-user wallets due to significantly higher unilateral exit costs.
679    ///
680    /// Default value is 1.
681    pub multiplicity: u8,
682}
683
684/// Configuration for token-output optimization.
685#[derive(Debug, Clone)]
686#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
687pub struct TokenOptimizationConfig {
688    /// Whether automatic token-output consolidation is enabled.
689    ///
690    /// If set to true, the SDK will periodically consolidate a token's outputs
691    /// once their count exceeds [`Self::min_outputs_threshold`]. Otherwise, no
692    /// automatic consolidation is performed.
693    ///
694    /// Default value is true.
695    pub auto_enabled: bool,
696    /// Number of token outputs to produce when token-output auto-consolidation
697    /// fires.
698    ///
699    /// Instead of collapsing a token's outputs into a single output (which
700    /// serializes subsequent payments), the SDK splits the consolidated balance
701    /// across this many outputs of roughly equal value. Higher values preserve
702    /// concurrency for parallel sends at the cost of a slightly larger output
703    /// set.
704    ///
705    /// Must be >= 1 and strictly less than [`Self::min_outputs_threshold`].
706    ///
707    /// Default value is 5.
708    pub target_output_count: u32,
709    /// Output count that triggers per-token auto-consolidation.
710    ///
711    /// Auto-consolidation triggers for a token when its available output count
712    /// strictly exceeds this threshold.
713    ///
714    /// Must be greater than 1.
715    ///
716    /// Default value is 50.
717    pub min_outputs_threshold: u32,
718}
719
720/// A stable token that can be used for automatic balance conversion.
721#[derive(Debug, Clone)]
722#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
723pub struct StableBalanceToken {
724    /// Integrator-defined display label for the token, e.g. "USD".
725    ///
726    /// This is a short, human-readable name set by the integrator for display purposes.
727    /// It is **not** a canonical Spark token ticker — it has no protocol-level meaning.
728    /// Labels must be unique within the [`StableBalanceConfig::tokens`] list.
729    pub label: String,
730
731    /// The full token identifier string used for conversions.
732    pub token_identifier: String,
733}
734
735/// Configuration for automatic conversion of Bitcoin to stable tokens.
736///
737/// When configured, the SDK automatically monitors the Bitcoin balance after each
738/// wallet sync. When the balance exceeds the configured threshold plus the reserved
739/// amount, the SDK automatically converts the excess balance (above the reserve)
740/// to the active stable token.
741///
742/// When the balance is held in a stable token, Bitcoin payments can still be sent.
743/// The SDK automatically detects when there's not enough Bitcoin balance to cover a
744/// payment and auto-populates the token-to-Bitcoin conversion options to facilitate
745/// the payment.
746///
747/// The active token can be changed at runtime via [`UpdateUserSettingsRequest`].
748#[derive(Debug, Clone)]
749#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
750pub struct StableBalanceConfig {
751    /// Available tokens that can be used for stable balance.
752    pub tokens: Vec<StableBalanceToken>,
753
754    /// The label of the token to activate by default.
755    ///
756    /// If `None`, stable balance starts deactivated. The user can activate it
757    /// at runtime via [`UpdateUserSettingsRequest`]. If a user setting is cached
758    /// locally, it takes precedence over this default.
759    #[cfg_attr(feature = "uniffi", uniffi(default = None))]
760    pub default_active_label: Option<String>,
761
762    /// The minimum sats balance that triggers auto-conversion.
763    ///
764    /// If not provided, uses the minimum from conversion limits.
765    /// If provided but less than the conversion limit minimum, the limit minimum is used.
766    #[cfg_attr(feature = "uniffi", uniffi(default = None))]
767    pub threshold_sats: Option<u64>,
768
769    /// Maximum slippage in basis points (1/100 of a percent).
770    ///
771    /// Defaults to 10 bps (0.1%) if not set.
772    #[cfg_attr(feature = "uniffi", uniffi(default = None))]
773    pub max_slippage_bps: Option<u32>,
774}
775
776/// Specifies how to update the active stable balance token.
777#[derive(Debug, Clone)]
778#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
779pub enum StableBalanceActiveLabel {
780    /// Activate stable balance with the given label.
781    Set { label: String },
782    /// Deactivate stable balance.
783    Unset,
784}
785
786/// Configuration for a custom Spark environment.
787///
788/// When set on [`Config`], overrides the default Spark operator pool,
789/// service provider, threshold, and token settings. This allows connecting
790/// to alternative Spark deployments (e.g. dev/staging environments).
791#[derive(Debug, Clone)]
792#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
793pub struct SparkConfig {
794    /// Hex-encoded identifier of the coordinator operator.
795    pub coordinator_identifier: String,
796    /// The FROST signing threshold (e.g. 2 of 3).
797    pub threshold: u32,
798    /// The set of signing operators.
799    pub signing_operators: Vec<SparkSigningOperator>,
800    /// Service provider (SSP) configuration.
801    pub ssp_config: SparkSspConfig,
802    /// Expected bond amount in sats for token withdrawals.
803    pub expected_withdraw_bond_sats: u64,
804    /// Expected relative block locktime for token withdrawals.
805    pub expected_withdraw_relative_block_locktime: u64,
806}
807
808/// A Spark signing operator.
809#[derive(Debug, Clone)]
810#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
811pub struct SparkSigningOperator {
812    /// Sequential operator ID (0-indexed).
813    pub id: u32,
814    /// Hex-encoded 32-byte FROST identifier.
815    pub identifier: String,
816    /// gRPC address of the operator (e.g. `https://0.spark.lightspark.com`).
817    pub address: String,
818    /// Hex-encoded compressed public key of the operator.
819    pub identity_public_key: String,
820}
821
822/// Configuration for the Spark Service Provider (SSP).
823#[derive(Debug, Clone)]
824#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
825pub struct SparkSspConfig {
826    /// Base URL of the SSP GraphQL API.
827    pub base_url: String,
828    /// Hex-encoded compressed public key of the SSP.
829    pub identity_public_key: String,
830    /// Optional GraphQL schema endpoint path (e.g. "graphql/spark/rc").
831    /// Defaults to the hardcoded schema endpoint if not set.
832    pub schema_endpoint: Option<String>,
833}
834
835impl Config {
836    /// Validates the configuration.
837    ///
838    /// Returns an error if any configuration values are invalid.
839    pub fn validate(&self) -> Result<(), SdkError> {
840        if self.max_concurrent_claims == 0 {
841            return Err(SdkError::InvalidInput(
842                "max_concurrent_claims must be greater than 0".to_string(),
843            ));
844        }
845
846        if let Some(sb) = &self.stable_balance_config {
847            if sb.tokens.is_empty() {
848                return Err(SdkError::InvalidInput(
849                    "tokens must not be empty".to_string(),
850                ));
851            }
852
853            let mut seen_labels = HashSet::new();
854            let mut seen_identifiers = HashSet::new();
855            for token in &sb.tokens {
856                if token.label.is_empty() {
857                    return Err(SdkError::InvalidInput(
858                        "token label must not be empty".to_string(),
859                    ));
860                }
861                if token.token_identifier.is_empty() {
862                    return Err(SdkError::InvalidInput(
863                        "token_identifier must not be empty".to_string(),
864                    ));
865                }
866                if !seen_labels.insert(&token.label) {
867                    return Err(SdkError::InvalidInput(format!(
868                        "tokens contains duplicate label: {}",
869                        token.label
870                    )));
871                }
872                if !seen_identifiers.insert(&token.token_identifier) {
873                    return Err(SdkError::InvalidInput(format!(
874                        "tokens contains duplicate token_identifier: {}",
875                        token.token_identifier
876                    )));
877                }
878            }
879
880            if let Some(bps) = sb.max_slippage_bps
881                && bps > 10000
882            {
883                return Err(SdkError::InvalidInput(
884                    "max_slippage_bps must be <= 10000".to_string(),
885                ));
886            }
887
888            if let Some(default_label) = &sb.default_active_label
889                && !seen_labels.contains(default_label)
890            {
891                return Err(SdkError::InvalidInput(format!(
892                    "default_active_label '{default_label}' not found in tokens list"
893                )));
894            }
895        }
896
897        let token_opt = &self.token_optimization_config;
898        if token_opt.min_outputs_threshold <= 1 {
899            return Err(SdkError::InvalidInput(
900                "token optimization minimum outputs threshold must be greater than 1".to_string(),
901            ));
902        }
903        if token_opt.target_output_count < 1 {
904            return Err(SdkError::InvalidInput(
905                "token optimization target output count must be at least 1".to_string(),
906            ));
907        }
908        if token_opt.target_output_count >= token_opt.min_outputs_threshold {
909            return Err(SdkError::InvalidInput(
910                "token optimization target output count must be less than the minimum outputs threshold".to_string(),
911            ));
912        }
913
914        Ok(())
915    }
916
917    pub(crate) fn get_all_external_input_parsers(&self) -> Vec<ExternalInputParser> {
918        let mut external_input_parsers = Vec::new();
919        if self.use_default_external_input_parsers {
920            let default_parsers = DEFAULT_EXTERNAL_INPUT_PARSERS
921                .iter()
922                .map(|(id, regex, url)| ExternalInputParser {
923                    provider_id: (*id).to_string(),
924                    input_regex: (*regex).to_string(),
925                    parser_url: (*url).to_string(),
926                })
927                .collect::<Vec<_>>();
928            external_input_parsers.extend(default_parsers);
929        }
930        external_input_parsers.extend(self.external_input_parsers.clone().unwrap_or_default());
931
932        external_input_parsers
933    }
934}
935
936#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
937#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
938pub enum MaxFee {
939    // Fixed fee amount in sats
940    Fixed { amount: u64 },
941    // Relative fee rate in satoshis per vbyte
942    Rate { sat_per_vbyte: u64 },
943    // Fastest network recommended fee at the time of claim, with a leeway in satoshis per vbyte
944    NetworkRecommended { leeway_sat_per_vbyte: u64 },
945}
946
947impl MaxFee {
948    pub(crate) async fn to_fee(&self, client: &dyn BitcoinChainService) -> Result<Fee, SdkError> {
949        match self {
950            MaxFee::Fixed { amount } => Ok(Fee::Fixed { amount: *amount }),
951            MaxFee::Rate { sat_per_vbyte } => Ok(Fee::Rate {
952                sat_per_vbyte: *sat_per_vbyte,
953            }),
954            MaxFee::NetworkRecommended {
955                leeway_sat_per_vbyte,
956            } => {
957                let recommended_fees = client.recommended_fees().await?;
958                let max_fee_rate = recommended_fees
959                    .fastest_fee
960                    .saturating_add(*leeway_sat_per_vbyte);
961                Ok(Fee::Rate {
962                    sat_per_vbyte: max_fee_rate,
963                })
964            }
965        }
966    }
967}
968
969#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
970#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
971pub enum Fee {
972    // Fixed fee amount in sats
973    Fixed { amount: u64 },
974    // Relative fee rate in satoshis per vbyte
975    Rate { sat_per_vbyte: u64 },
976}
977
978impl Fee {
979    pub fn to_sats(&self, vbytes: u64) -> u64 {
980        match self {
981            Fee::Fixed { amount } => *amount,
982            Fee::Rate { sat_per_vbyte } => sat_per_vbyte.saturating_mul(vbytes),
983        }
984    }
985}
986
987#[derive(Debug, Clone, Serialize, Deserialize)]
988#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
989pub struct DepositInfo {
990    pub txid: String,
991    pub vout: u32,
992    pub amount_sats: u64,
993    pub is_mature: bool,
994    pub refund_tx: Option<String>,
995    pub refund_tx_id: Option<String>,
996    pub claim_error: Option<DepositClaimError>,
997}
998
999#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1000pub struct ClaimDepositRequest {
1001    pub txid: String,
1002    pub vout: u32,
1003    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1004    pub max_fee: Option<MaxFee>,
1005}
1006
1007#[derive(Debug, Clone, Serialize)]
1008#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1009pub struct ClaimDepositResponse {
1010    pub payment: Payment,
1011}
1012
1013#[derive(Debug, Clone, Serialize)]
1014#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1015pub struct RefundDepositRequest {
1016    pub txid: String,
1017    pub vout: u32,
1018    pub destination_address: String,
1019    pub fee: Fee,
1020}
1021
1022#[derive(Debug, Clone, Serialize)]
1023#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1024pub struct RefundDepositResponse {
1025    pub tx_id: String,
1026    pub tx_hex: String,
1027}
1028
1029#[derive(Debug, Clone, Serialize)]
1030#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1031pub struct ListUnclaimedDepositsRequest {}
1032
1033#[derive(Debug, Clone, Serialize)]
1034#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1035pub struct ListUnclaimedDepositsResponse {
1036    pub deposits: Vec<DepositInfo>,
1037}
1038
1039/// The available providers for buying Bitcoin
1040/// Request to buy Bitcoin using an external provider.
1041///
1042/// Each variant carries only the parameters relevant to that provider.
1043#[derive(Debug, Clone)]
1044#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1045pub enum BuyBitcoinRequest {
1046    /// `MoonPay`: Fiat-to-Bitcoin via credit card, Apple Pay, etc.
1047    /// Uses an on-chain deposit address.
1048    Moonpay {
1049        /// Lock the purchase to a specific amount in satoshis.
1050        locked_amount_sat: Option<u64>,
1051        /// Custom redirect URL after purchase completion.
1052        redirect_url: Option<String>,
1053    },
1054    /// `CashApp`: Pay via the Lightning Network.
1055    /// Generates a bolt11 invoice for the given amount and returns a
1056    /// `cash.app` deep link. Only available on mainnet.
1057    ///
1058    /// The amount is required. With an amountless invoice, Cash App only
1059    /// lets the payer fund from their existing Cash App BTC balance. With
1060    /// a fixed-amount invoice, Cash App opens up funding via fiat balance
1061    /// and debit card.
1062    CashApp {
1063        /// Amount in satoshis for the Lightning invoice. Must be non-zero.
1064        amount_sats: u64,
1065    },
1066}
1067
1068impl Default for BuyBitcoinRequest {
1069    fn default() -> Self {
1070        Self::Moonpay {
1071            locked_amount_sat: None,
1072            redirect_url: None,
1073        }
1074    }
1075}
1076
1077/// Response containing a URL to complete the Bitcoin purchase
1078#[derive(Debug, Clone, Serialize)]
1079#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1080pub struct BuyBitcoinResponse {
1081    /// The URL to open in a browser to complete the purchase
1082    pub url: String,
1083}
1084
1085impl std::fmt::Display for MaxFee {
1086    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1087        match self {
1088            MaxFee::Fixed { amount } => write!(f, "Fixed: {amount}"),
1089            MaxFee::Rate { sat_per_vbyte } => write!(f, "Rate: {sat_per_vbyte}"),
1090            MaxFee::NetworkRecommended {
1091                leeway_sat_per_vbyte,
1092            } => write!(f, "NetworkRecommended: {leeway_sat_per_vbyte}"),
1093        }
1094    }
1095}
1096
1097#[derive(Debug, Clone)]
1098#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1099pub struct Credentials {
1100    pub username: String,
1101    pub password: String,
1102}
1103
1104/// Request to get the balance of the wallet
1105#[derive(Debug, Clone)]
1106#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1107pub struct GetInfoRequest {
1108    /// When `Some(true)`, and `background_tasks_enabled` is `true`, the call
1109    /// waits for the initial Full sync to complete before returning.
1110    ///
1111    /// When `background_tasks_enabled` is `false`, setting this to `Some(true)`
1112    /// is rejected with an invalid-input error. There is no background sync to
1113    /// wait on; call `sync_wallet` explicitly first if you need fresh state.
1114    pub ensure_synced: Option<bool>,
1115}
1116
1117/// Response containing the balance of the wallet
1118#[derive(Debug, Clone, Serialize)]
1119#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1120pub struct GetInfoResponse {
1121    /// The identity public key of the wallet as a hex string
1122    pub identity_pubkey: String,
1123    /// The balance in satoshis
1124    pub balance_sats: u64,
1125    /// The balances of the tokens in the wallet keyed by the token identifier
1126    pub token_balances: HashMap<String, TokenBalance>,
1127}
1128
1129#[derive(Debug, Clone, Serialize, Deserialize)]
1130#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1131pub struct TokenBalance {
1132    pub balance: u128,
1133    pub token_metadata: TokenMetadata,
1134}
1135
1136#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1137#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1138pub struct TokenMetadata {
1139    pub identifier: String,
1140    /// Hex representation of the issuer public key
1141    pub issuer_public_key: String,
1142    pub name: String,
1143    pub ticker: String,
1144    /// Number of decimals the token uses
1145    pub decimals: u32,
1146    pub max_supply: u128,
1147    pub is_freezable: bool,
1148}
1149
1150/// Request to sync the wallet with the Spark network
1151#[derive(Debug, Clone)]
1152#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1153pub struct SyncWalletRequest {}
1154
1155/// Response from synchronizing the wallet
1156#[derive(Debug, Clone, Serialize)]
1157#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1158pub struct SyncWalletResponse {}
1159
1160#[derive(Debug, Clone, Serialize)]
1161#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1162pub enum ReceivePaymentMethod {
1163    SparkAddress,
1164    SparkInvoice {
1165        /// Amount to receive. Denominated in sats if token identifier is empty, otherwise in the token base units
1166        amount: Option<u128>,
1167        /// The presence of this field indicates that the payment is for a token
1168        /// If empty, it is a Bitcoin payment
1169        token_identifier: Option<String>,
1170        /// The expiry time of the invoice as a unix timestamp in seconds
1171        expiry_time: Option<u64>,
1172        /// A description to embed in the invoice.
1173        description: Option<String>,
1174        /// If set, the invoice may only be fulfilled by a payer with this public key
1175        sender_public_key: Option<String>,
1176    },
1177    BitcoinAddress {
1178        /// If true, rotate to a new deposit address. Previous ones remain valid.
1179        /// If false or absent, return the existing address (creating one if none
1180        /// exists yet).
1181        new_address: Option<bool>,
1182    },
1183    Bolt11Invoice {
1184        description: String,
1185        amount_sats: Option<u64>,
1186        /// The expiry of the invoice as a duration in seconds
1187        expiry_secs: Option<u32>,
1188        /// If set, creates a HODL invoice with this payment hash (hex-encoded).
1189        /// The payer's HTLC will be held until the preimage is provided via
1190        /// `claim_htlc_payment` or the HTLC expires.
1191        payment_hash: Option<String>,
1192    },
1193}
1194
1195#[derive(Debug, Clone, Serialize)]
1196#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1197pub enum SendPaymentMethod {
1198    BitcoinAddress {
1199        address: BitcoinAddressDetails,
1200        fee_quote: SendOnchainFeeQuote,
1201    },
1202    Bolt11Invoice {
1203        invoice_details: Bolt11InvoiceDetails,
1204        spark_transfer_fee_sats: Option<u64>,
1205        lightning_fee_sats: u64,
1206    }, // should be replaced with the parsed invoice
1207    SparkAddress {
1208        address: String,
1209        /// Fee to pay for the transaction
1210        /// Denominated in sats if token identifier is empty, otherwise in the token base units
1211        fee: u128,
1212        /// The presence of this field indicates that the payment is for a token
1213        /// If empty, it is a Bitcoin payment
1214        token_identifier: Option<String>,
1215    },
1216    SparkInvoice {
1217        spark_invoice_details: SparkInvoiceDetails,
1218        /// Fee to pay for the transaction
1219        /// Denominated in sats if token identifier is empty, otherwise in the token base units
1220        fee: u128,
1221        /// The presence of this field indicates that the payment is for a token
1222        /// If empty, it is a Bitcoin payment
1223        token_identifier: Option<String>,
1224    },
1225}
1226
1227#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1228#[derive(Debug, Clone, Serialize)]
1229pub struct SendOnchainFeeQuote {
1230    pub id: String,
1231    pub expires_at: u64,
1232    pub speed_fast: SendOnchainSpeedFeeQuote,
1233    pub speed_medium: SendOnchainSpeedFeeQuote,
1234    pub speed_slow: SendOnchainSpeedFeeQuote,
1235}
1236
1237#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1238#[derive(Debug, Clone, Serialize)]
1239pub struct SendOnchainSpeedFeeQuote {
1240    pub user_fee_sat: u64,
1241    pub l1_broadcast_fee_sat: u64,
1242}
1243
1244impl SendOnchainSpeedFeeQuote {
1245    pub fn total_fee_sat(&self) -> u64 {
1246        self.user_fee_sat.saturating_add(self.l1_broadcast_fee_sat)
1247    }
1248}
1249
1250#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1251pub struct ReceivePaymentRequest {
1252    pub payment_method: ReceivePaymentMethod,
1253}
1254
1255#[derive(Debug, Clone, Serialize)]
1256#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1257pub struct ReceivePaymentResponse {
1258    pub payment_request: String,
1259    /// Fee to pay to receive the payment
1260    /// Denominated in sats or token base units
1261    pub fee: u128,
1262}
1263
1264#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1265pub struct PrepareLnurlPayRequest {
1266    /// The amount to send. Denominated in satoshis, or in token base units
1267    /// when `token_identifier` is set.
1268    pub amount: u128,
1269    pub pay_request: LnurlPayRequestDetails,
1270    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1271    pub comment: Option<String>,
1272    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1273    pub validate_success_action_url: Option<bool>,
1274    /// The token identifier when sending a token amount with conversion.
1275    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1276    pub token_identifier: Option<String>,
1277    /// If provided, the payment will include a token conversion step before sending the payment
1278    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1279    pub conversion_options: Option<ConversionOptions>,
1280    /// How fees should be handled. Defaults to `FeesExcluded` (fees added on top).
1281    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1282    pub fee_policy: Option<FeePolicy>,
1283}
1284
1285#[derive(Debug, Clone)]
1286#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1287pub struct PrepareLnurlPayResponse {
1288    /// The amount for the payment, always denominated in sats, even when a
1289    /// `token_identifier` and conversion are present.
1290    /// When a conversion is present, the token input amount is available in
1291    /// `conversion_estimate.amount_in`.
1292    pub amount_sats: u64,
1293    pub comment: Option<String>,
1294    pub pay_request: LnurlPayRequestDetails,
1295    /// The fee in satoshis. For `FeesIncluded` operations, this represents the total fee
1296    /// (including potential overpayment).
1297    pub fee_sats: u64,
1298    pub invoice_details: Bolt11InvoiceDetails,
1299    pub success_action: Option<SuccessAction>,
1300    /// When set, the payment will include a token conversion step before sending the payment
1301    pub conversion_estimate: Option<ConversionEstimate>,
1302    /// How fees are handled for this payment.
1303    pub fee_policy: FeePolicy,
1304}
1305
1306#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1307pub struct LnurlPayRequest {
1308    pub prepare_response: PrepareLnurlPayResponse,
1309    /// If set, providing the same idempotency key for multiple requests will ensure that only one
1310    /// payment is made. If an idempotency key is re-used, the same payment will be returned.
1311    /// The idempotency key must be a valid UUID.
1312    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1313    pub idempotency_key: Option<String>,
1314}
1315
1316#[derive(Debug, Serialize)]
1317#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1318pub struct LnurlPayResponse {
1319    pub payment: Payment,
1320    pub success_action: Option<SuccessActionProcessed>,
1321}
1322
1323#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1324pub struct LnurlWithdrawRequest {
1325    /// The amount to withdraw in satoshis
1326    /// Must be within the min and max withdrawable limits
1327    pub amount_sats: u64,
1328    pub withdraw_request: LnurlWithdrawRequestDetails,
1329    /// If set, the function will return the payment if it is still pending after this
1330    /// number of seconds. If unset, the function will return immediately after
1331    /// initiating the LNURL withdraw.
1332    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1333    pub completion_timeout_secs: Option<u32>,
1334}
1335
1336#[derive(Debug, Serialize)]
1337#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1338pub struct LnurlWithdrawResponse {
1339    /// The Lightning invoice generated for the LNURL withdraw
1340    pub payment_request: String,
1341    pub payment: Option<Payment>,
1342}
1343
1344/// Represents the payment LNURL info
1345#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
1346#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1347pub struct LnurlPayInfo {
1348    pub ln_address: Option<String>,
1349    pub comment: Option<String>,
1350    pub domain: Option<String>,
1351    pub metadata: Option<String>,
1352    pub processed_success_action: Option<SuccessActionProcessed>,
1353    pub raw_success_action: Option<SuccessAction>,
1354}
1355
1356/// Represents the withdraw LNURL info
1357#[derive(Clone, Debug, Deserialize, Serialize)]
1358#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1359pub struct LnurlWithdrawInfo {
1360    pub withdraw_url: String,
1361}
1362
1363impl LnurlPayInfo {
1364    pub fn extract_description(&self) -> Option<String> {
1365        let Some(metadata) = &self.metadata else {
1366            return None;
1367        };
1368
1369        let Ok(metadata) = serde_json::from_str::<Vec<Vec<Value>>>(metadata) else {
1370            return None;
1371        };
1372
1373        for arr in metadata {
1374            if arr.len() != 2 {
1375                continue;
1376            }
1377            if let (Some(key), Some(value)) = (arr[0].as_str(), arr[1].as_str())
1378                && key == "text/plain"
1379            {
1380                return Some(value.to_string());
1381            }
1382        }
1383
1384        None
1385    }
1386}
1387
1388/// Specifies how fees are handled in a payment.
1389#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
1390#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1391pub enum FeePolicy {
1392    /// Fees are added on top of the specified amount (default behavior).
1393    /// The receiver gets the exact amount specified.
1394    #[default]
1395    FeesExcluded,
1396    /// Fees are deducted from the specified amount.
1397    /// The receiver gets the amount minus fees.
1398    FeesIncluded,
1399}
1400
1401#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1402#[derive(Debug, Clone, Serialize)]
1403pub enum OnchainConfirmationSpeed {
1404    Fast,
1405    Medium,
1406    Slow,
1407}
1408
1409#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1410pub struct PrepareSendPaymentRequest {
1411    pub payment_request: String,
1412    /// The amount to send.
1413    /// Optional for payment requests with embedded amounts (e.g., Spark/Bolt11 invoices with amounts).
1414    /// Required for Spark addresses, Bitcoin addresses, and amountless invoices.
1415    /// Denominated in satoshis for Bitcoin payments, or token base units for token payments.
1416    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1417    pub amount: Option<u128>,
1418    /// Optional token identifier for token payments.
1419    /// Absence indicates that the payment is a Bitcoin payment.
1420    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1421    pub token_identifier: Option<String>,
1422    /// If provided, the payment will include a conversion step before sending the payment
1423    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1424    pub conversion_options: Option<ConversionOptions>,
1425    /// How fees should be handled. Defaults to `FeesExcluded` (fees added on top).
1426    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1427    pub fee_policy: Option<FeePolicy>,
1428}
1429
1430#[derive(Debug, Clone, Serialize)]
1431#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1432pub struct PrepareSendPaymentResponse {
1433    pub payment_method: SendPaymentMethod,
1434    /// The amount to be sent, denominated in satoshis for Bitcoin payments
1435    /// (including token-to-Bitcoin conversions), or token base units for token payments.
1436    /// When a conversion is present, the input amount is in
1437    /// `conversion_estimate.amount_in`.
1438    pub amount: u128,
1439    /// Optional token identifier for token payments.
1440    /// Absence indicates that the payment is a Bitcoin payment.
1441    pub token_identifier: Option<String>,
1442    /// When set, the payment will include a conversion step before sending the payment
1443    pub conversion_estimate: Option<ConversionEstimate>,
1444    /// How fees are handled for this payment.
1445    pub fee_policy: FeePolicy,
1446}
1447
1448#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1449pub enum SendPaymentOptions {
1450    BitcoinAddress {
1451        /// Confirmation speed for the on-chain transaction.
1452        confirmation_speed: OnchainConfirmationSpeed,
1453    },
1454    Bolt11Invoice {
1455        prefer_spark: bool,
1456
1457        /// If set, the function will return the payment if it is still pending after this
1458        /// number of seconds. If unset, the function will return immediately after initiating the payment.
1459        completion_timeout_secs: Option<u32>,
1460    },
1461    SparkAddress {
1462        /// Can only be provided for Bitcoin payments. If set, a Spark HTLC transfer will be created.
1463        /// The receiver will need to provide the preimage to claim it.
1464        htlc_options: Option<SparkHtlcOptions>,
1465    },
1466}
1467
1468#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1469pub struct SparkHtlcOptions {
1470    /// The payment hash of the HTLC. The receiver will need to provide the associated preimage to claim it.
1471    pub payment_hash: String,
1472    /// The duration of the HTLC in seconds.
1473    /// After this time, the HTLC will be returned.
1474    pub expiry_duration_secs: u64,
1475}
1476
1477#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1478pub struct SendPaymentRequest {
1479    pub prepare_response: PrepareSendPaymentResponse,
1480    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1481    pub options: Option<SendPaymentOptions>,
1482    /// The optional idempotency key for all Spark based transfers (excludes token payments).
1483    /// If set, providing the same idempotency key for multiple requests will ensure that only one
1484    /// payment is made. If an idempotency key is re-used, the same payment will be returned.
1485    /// The idempotency key must be a valid UUID.
1486    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1487    pub idempotency_key: Option<String>,
1488}
1489
1490#[derive(Debug, Clone, Serialize)]
1491#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1492pub struct SendPaymentResponse {
1493    pub payment: Payment,
1494}
1495
1496#[derive(Debug, Clone)]
1497#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1498pub enum PaymentDetailsFilter {
1499    Spark {
1500        /// Filter specific Spark HTLC statuses
1501        htlc_status: Option<Vec<SparkHtlcStatus>>,
1502        /// Filter conversion payments with refund information
1503        conversion_refund_needed: Option<bool>,
1504    },
1505    Token {
1506        /// Filter conversion payments with refund information
1507        conversion_refund_needed: Option<bool>,
1508        /// Filter by transaction hash
1509        tx_hash: Option<String>,
1510        /// Filter by transaction type
1511        tx_type: Option<TokenTransactionType>,
1512    },
1513    Lightning {
1514        /// Filter specific Spark HTLC statuses
1515        htlc_status: Option<Vec<SparkHtlcStatus>>,
1516    },
1517}
1518
1519/// Request to list payments with optional filters and pagination
1520#[derive(Debug, Clone, Default)]
1521#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1522pub struct ListPaymentsRequest {
1523    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1524    pub type_filter: Option<Vec<PaymentType>>,
1525    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1526    pub status_filter: Option<Vec<PaymentStatus>>,
1527    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1528    pub asset_filter: Option<AssetFilter>,
1529    /// Only include payments matching at least one of these payment details filters
1530    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1531    pub payment_details_filter: Option<Vec<PaymentDetailsFilter>>,
1532    /// Only include payments created after this timestamp (inclusive)
1533    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1534    pub from_timestamp: Option<u64>,
1535    /// Only include payments created before this timestamp (exclusive)
1536    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1537    pub to_timestamp: Option<u64>,
1538    /// Number of records to skip
1539    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1540    pub offset: Option<u32>,
1541    /// Maximum number of records to return
1542    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1543    pub limit: Option<u32>,
1544    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1545    pub sort_ascending: Option<bool>,
1546}
1547
1548/// A field of [`ListPaymentsRequest`] when listing payments filtered by asset
1549#[derive(Debug, Clone)]
1550#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1551pub enum AssetFilter {
1552    Bitcoin,
1553    Token {
1554        /// Optional token identifier to filter by
1555        token_identifier: Option<String>,
1556    },
1557}
1558
1559impl FromStr for AssetFilter {
1560    type Err = String;
1561
1562    fn from_str(s: &str) -> Result<Self, Self::Err> {
1563        Ok(match s.to_lowercase().as_str() {
1564            "bitcoin" => AssetFilter::Bitcoin,
1565            "token" => AssetFilter::Token {
1566                token_identifier: None,
1567            },
1568            str if str.starts_with("token:") => AssetFilter::Token {
1569                token_identifier: Some(
1570                    str.split_once(':')
1571                        .ok_or(format!("Invalid asset filter '{s}'"))?
1572                        .1
1573                        .to_string(),
1574                ),
1575            },
1576            _ => return Err(format!("Invalid asset filter '{s}'")),
1577        })
1578    }
1579}
1580
1581/// Response from listing payments
1582#[derive(Debug, Clone, Serialize)]
1583#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1584pub struct ListPaymentsResponse {
1585    /// The list of payments
1586    pub payments: Vec<Payment>,
1587}
1588
1589#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1590pub struct GetPaymentRequest {
1591    pub payment_id: String,
1592}
1593
1594#[derive(Debug, Clone, Serialize)]
1595#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1596pub struct GetPaymentResponse {
1597    pub payment: Payment,
1598}
1599
1600#[cfg_attr(feature = "uniffi", uniffi::export(callback_interface))]
1601pub trait Logger: Send + Sync {
1602    fn log(&self, l: LogEntry);
1603}
1604
1605#[derive(Debug, Clone, Serialize)]
1606#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1607pub struct LogEntry {
1608    pub line: String,
1609    pub level: String,
1610}
1611
1612#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1613#[derive(Debug, Clone, Serialize, Deserialize)]
1614pub struct CheckLightningAddressRequest {
1615    pub username: String,
1616}
1617
1618#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1619#[derive(Debug, Clone, Serialize, Deserialize)]
1620pub struct RegisterLightningAddressRequest {
1621    pub username: String,
1622    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1623    pub description: Option<String>,
1624}
1625
1626#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1627#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
1628pub struct LnurlInfo {
1629    pub url: String,
1630    pub bech32: String,
1631}
1632
1633impl LnurlInfo {
1634    pub fn new(url: String) -> Self {
1635        let bech32 =
1636            breez_sdk_common::lnurl::encode_lnurl_to_bech32(&url).unwrap_or_else(|_| url.clone());
1637        Self { url, bech32 }
1638    }
1639}
1640
1641#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1642#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
1643pub struct LightningAddressInfo {
1644    pub description: String,
1645    pub lightning_address: String,
1646    pub lnurl: LnurlInfo,
1647    pub username: String,
1648}
1649
1650impl From<RecoverLnurlPayResponse> for LightningAddressInfo {
1651    fn from(resp: RecoverLnurlPayResponse) -> Self {
1652        Self {
1653            description: resp.description,
1654            lightning_address: resp.lightning_address,
1655            lnurl: LnurlInfo::new(resp.lnurl),
1656            username: resp.username,
1657        }
1658    }
1659}
1660
1661#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1662#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1663pub enum KeySetType {
1664    #[default]
1665    Default,
1666    Taproot,
1667    NativeSegwit,
1668    WrappedSegwit,
1669    Legacy,
1670}
1671
1672impl From<spark_wallet::KeySetType> for KeySetType {
1673    fn from(value: spark_wallet::KeySetType) -> Self {
1674        match value {
1675            spark_wallet::KeySetType::Default => KeySetType::Default,
1676            spark_wallet::KeySetType::Taproot => KeySetType::Taproot,
1677            spark_wallet::KeySetType::NativeSegwit => KeySetType::NativeSegwit,
1678            spark_wallet::KeySetType::WrappedSegwit => KeySetType::WrappedSegwit,
1679            spark_wallet::KeySetType::Legacy => KeySetType::Legacy,
1680        }
1681    }
1682}
1683
1684impl From<KeySetType> for spark_wallet::KeySetType {
1685    fn from(value: KeySetType) -> Self {
1686        match value {
1687            KeySetType::Default => spark_wallet::KeySetType::Default,
1688            KeySetType::Taproot => spark_wallet::KeySetType::Taproot,
1689            KeySetType::NativeSegwit => spark_wallet::KeySetType::NativeSegwit,
1690            KeySetType::WrappedSegwit => spark_wallet::KeySetType::WrappedSegwit,
1691            KeySetType::Legacy => spark_wallet::KeySetType::Legacy,
1692        }
1693    }
1694}
1695
1696/// Configuration for key set derivation.
1697///
1698/// This struct encapsulates the parameters needed for BIP32 key derivation.
1699#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1700#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1701pub struct KeySetConfig {
1702    /// The key set type which determines the derivation path
1703    pub key_set_type: KeySetType,
1704    /// Controls the structure of the BIP derivation path
1705    pub use_address_index: bool,
1706    /// Optional account number for key derivation
1707    pub account_number: Option<u32>,
1708}
1709
1710impl Default for KeySetConfig {
1711    fn default() -> Self {
1712        Self {
1713            key_set_type: KeySetType::Default,
1714            use_address_index: false,
1715            account_number: None,
1716        }
1717    }
1718}
1719
1720/// Response from listing fiat currencies
1721#[derive(Debug, Clone, Serialize)]
1722#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1723pub struct ListFiatCurrenciesResponse {
1724    /// The list of fiat currencies
1725    pub currencies: Vec<FiatCurrency>,
1726}
1727
1728/// Response from listing fiat rates
1729#[derive(Debug, Clone, Serialize)]
1730#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1731pub struct ListFiatRatesResponse {
1732    /// The list of fiat rates
1733    pub rates: Vec<Rate>,
1734}
1735
1736/// The operational status of a Spark service.
1737#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
1738#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1739pub enum ServiceStatus {
1740    /// Service is fully operational.
1741    Operational,
1742    /// Service is experiencing degraded performance.
1743    Degraded,
1744    /// Service is partially unavailable.
1745    Partial,
1746    /// Service status is unknown.
1747    Unknown,
1748    /// Service is experiencing a major outage.
1749    Major,
1750}
1751
1752/// The status of the Spark network services relevant to the SDK.
1753#[derive(Debug, Clone, Serialize)]
1754#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1755pub struct SparkStatus {
1756    /// The worst status across all relevant services.
1757    pub status: ServiceStatus,
1758    /// The last time the status was updated, as a unix timestamp in seconds.
1759    pub last_updated: u64,
1760}
1761
1762pub(crate) enum WaitForPaymentIdentifier {
1763    PaymentId(String),
1764    PaymentRequest(String),
1765}
1766
1767#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1768pub struct GetTokensMetadataRequest {
1769    pub token_identifiers: Vec<String>,
1770}
1771
1772#[derive(Debug, Clone, Serialize)]
1773#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1774pub struct GetTokensMetadataResponse {
1775    pub tokens_metadata: Vec<TokenMetadata>,
1776}
1777
1778#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1779pub struct SignMessageRequest {
1780    pub message: String,
1781    /// If true, the signature will be encoded in compact format instead of DER format
1782    pub compact: bool,
1783}
1784
1785#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1786pub struct SignMessageResponse {
1787    pub pubkey: String,
1788    /// The DER or compact hex encoded signature
1789    pub signature: String,
1790}
1791
1792#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1793pub struct CheckMessageRequest {
1794    /// The message that was signed
1795    pub message: String,
1796    /// The public key that signed the message
1797    pub pubkey: String,
1798    /// The DER or compact hex encoded signature
1799    pub signature: String,
1800}
1801
1802#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1803pub struct CheckMessageResponse {
1804    pub is_valid: bool,
1805}
1806
1807#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1808#[derive(Debug, Clone, Serialize)]
1809pub struct UserSettings {
1810    pub spark_private_mode_enabled: bool,
1811
1812    /// The label of the currently active stable balance token, or `None` if deactivated.
1813    pub stable_balance_active_label: Option<String>,
1814}
1815
1816#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1817pub struct UpdateUserSettingsRequest {
1818    pub spark_private_mode_enabled: Option<bool>,
1819
1820    /// Update the active stable balance token. `None` means no change.
1821    #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1822    pub stable_balance_active_label: Option<StableBalanceActiveLabel>,
1823}
1824
1825#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1826pub struct ClaimHtlcPaymentRequest {
1827    pub preimage: String,
1828}
1829
1830#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1831pub struct ClaimHtlcPaymentResponse {
1832    pub payment: Payment,
1833}
1834
1835#[derive(Debug, Clone, Deserialize, Serialize)]
1836#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1837pub struct LnurlReceiveMetadata {
1838    pub nostr_zap_request: Option<String>,
1839    pub nostr_zap_receipt: Option<String>,
1840    pub sender_comment: Option<String>,
1841}
1842
1843#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1844pub struct OptimizationProgress {
1845    pub is_running: bool,
1846    pub current_round: u32,
1847    pub total_rounds: u32,
1848}
1849
1850/// A contact entry containing a name and payment identifier.
1851#[derive(Debug, Clone, Serialize, Deserialize)]
1852#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1853pub struct Contact {
1854    pub id: String,
1855    pub name: String,
1856    /// A Lightning address (user@domain).
1857    pub payment_identifier: String,
1858    pub created_at: u64,
1859    pub updated_at: u64,
1860}
1861
1862/// Request to add a new contact.
1863#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1864pub struct AddContactRequest {
1865    pub name: String,
1866    /// A Lightning address (user@domain).
1867    pub payment_identifier: String,
1868}
1869
1870/// Request to update an existing contact.
1871#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1872pub struct UpdateContactRequest {
1873    pub id: String,
1874    pub name: String,
1875    /// A Lightning address (user@domain).
1876    pub payment_identifier: String,
1877}
1878
1879/// Request to list contacts with optional pagination.
1880#[derive(Debug, Clone, Default)]
1881#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1882pub struct ListContactsRequest {
1883    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1884    pub offset: Option<u32>,
1885    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
1886    pub limit: Option<u32>,
1887}
1888
1889/// The type of event that triggers a webhook notification.
1890#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1891#[allow(clippy::enum_variant_names)]
1892#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1893pub enum WebhookEventType {
1894    /// Triggered when a Lightning receive operation completes.
1895    LightningReceiveFinished,
1896    /// Triggered when a Lightning send operation completes.
1897    LightningSendFinished,
1898    /// Triggered when a cooperative exit completes.
1899    CoopExitFinished,
1900    /// Triggered when a static deposit completes.
1901    StaticDepositFinished,
1902    /// An event type not yet recognized by this version of the SDK.
1903    Unknown(String),
1904}
1905
1906/// A registered webhook entry.
1907#[derive(Debug, Clone, Serialize)]
1908#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1909pub struct Webhook {
1910    /// Unique identifier for this webhook.
1911    pub id: String,
1912    /// The URL that receives webhook notifications.
1913    pub url: String,
1914    /// The event types this webhook is subscribed to.
1915    pub event_types: Vec<WebhookEventType>,
1916}
1917
1918/// Request to register a new webhook.
1919#[derive(Debug, Clone)]
1920#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1921pub struct RegisterWebhookRequest {
1922    /// The URL that will receive webhook notifications.
1923    pub url: String,
1924    /// A secret used for HMAC-SHA256 signature verification of webhook payloads.
1925    pub secret: String,
1926    /// The event types to subscribe to.
1927    pub event_types: Vec<WebhookEventType>,
1928}
1929
1930/// Response from registering a webhook.
1931#[derive(Debug, Clone, Serialize)]
1932#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1933pub struct RegisterWebhookResponse {
1934    /// The unique identifier of the newly registered webhook.
1935    pub webhook_id: String,
1936}
1937
1938/// Request to unregister an existing webhook.
1939#[derive(Debug, Clone)]
1940#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1941pub struct UnregisterWebhookRequest {
1942    /// The unique identifier of the webhook to unregister.
1943    pub webhook_id: String,
1944}