breez_sdk_spark/models/
mod.rs

1pub(crate) mod adaptors;
2pub mod payment_observer;
3pub use payment_observer::*;
4
5use core::fmt;
6use lnurl_models::RecoverLnurlPayResponse;
7use serde::{Deserialize, Serialize};
8use serde_json::Value;
9use std::{collections::HashMap, fmt::Display, str::FromStr};
10
11use crate::{
12    BitcoinAddressDetails, BitcoinChainService, BitcoinNetwork, Bolt11InvoiceDetails,
13    ExternalInputParser, FiatCurrency, LnurlPayRequestDetails, LnurlWithdrawRequestDetails, Rate,
14    SdkError, SparkInvoiceDetails, SuccessAction, SuccessActionProcessed, error::DepositClaimError,
15};
16
17/// A list of external input parsers that are used by default.
18/// To opt-out, set `use_default_external_input_parsers` in [Config] to false.
19pub const DEFAULT_EXTERNAL_INPUT_PARSERS: &[(&str, &str, &str)] = &[
20    (
21        "picknpay",
22        "(.*)(za.co.electrum.picknpay)(.*)",
23        "https://cryptoqr.net/.well-known/lnurlp/<input>",
24    ),
25    (
26        "bootleggers",
27        r"(.*)(wigroup\.co|yoyogroup\.co)(.*)",
28        "https://cryptoqr.net/.well-known/lnurlw/<input>",
29    ),
30];
31
32/// Represents the seed for wallet generation, either as a mnemonic phrase with an optional
33/// passphrase or as raw entropy bytes.
34#[derive(Debug, Clone)]
35#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
36pub enum Seed {
37    /// A BIP-39 mnemonic phrase with an optional passphrase.
38    Mnemonic {
39        /// The mnemonic phrase. 12 or 24 words.
40        mnemonic: String,
41        /// An optional passphrase for the mnemonic.
42        passphrase: Option<String>,
43    },
44    /// Raw entropy bytes.
45    Entropy(Vec<u8>),
46}
47
48impl Seed {
49    pub fn to_bytes(&self) -> Result<Vec<u8>, SdkError> {
50        match self {
51            Seed::Mnemonic {
52                mnemonic,
53                passphrase,
54            } => {
55                let mnemonic = bip39::Mnemonic::parse(mnemonic)
56                    .map_err(|e| SdkError::Generic(e.to_string()))?;
57
58                Ok(mnemonic
59                    .to_seed(passphrase.as_deref().unwrap_or(""))
60                    .to_vec())
61            }
62            Seed::Entropy(entropy) => Ok(entropy.clone()),
63        }
64    }
65}
66
67#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
68pub struct ConnectRequest {
69    pub config: Config,
70    pub seed: Seed,
71    pub storage_dir: String,
72}
73
74/// The type of payment
75#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
76#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
77pub enum PaymentType {
78    /// Payment sent from this wallet
79    Send,
80    /// Payment received to this wallet
81    Receive,
82}
83
84impl fmt::Display for PaymentType {
85    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86        match self {
87            PaymentType::Send => write!(f, "send"),
88            PaymentType::Receive => write!(f, "receive"),
89        }
90    }
91}
92
93impl FromStr for PaymentType {
94    type Err = String;
95
96    fn from_str(s: &str) -> Result<Self, Self::Err> {
97        Ok(match s.to_lowercase().as_str() {
98            "receive" => PaymentType::Receive,
99            "send" => PaymentType::Send,
100            _ => return Err(format!("invalid payment type '{s}'")),
101        })
102    }
103}
104
105/// The status of a payment
106#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
107#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
108pub enum PaymentStatus {
109    /// Payment is completed successfully
110    Completed,
111    /// Payment is in progress
112    Pending,
113    /// Payment has failed
114    Failed,
115}
116
117impl PaymentStatus {
118    /// Returns true if the payment status is final (either Completed or Failed)
119    pub fn is_final(&self) -> bool {
120        matches!(self, PaymentStatus::Completed | PaymentStatus::Failed)
121    }
122}
123
124impl fmt::Display for PaymentStatus {
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        match self {
127            PaymentStatus::Completed => write!(f, "completed"),
128            PaymentStatus::Pending => write!(f, "pending"),
129            PaymentStatus::Failed => write!(f, "failed"),
130        }
131    }
132}
133
134impl FromStr for PaymentStatus {
135    type Err = String;
136
137    fn from_str(s: &str) -> Result<Self, Self::Err> {
138        Ok(match s.to_lowercase().as_str() {
139            "completed" => PaymentStatus::Completed,
140            "pending" => PaymentStatus::Pending,
141            "failed" => PaymentStatus::Failed,
142            _ => return Err(format!("Invalid payment status '{s}'")),
143        })
144    }
145}
146
147#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
148#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
149pub enum PaymentMethod {
150    Lightning,
151    Spark,
152    Token,
153    Deposit,
154    Withdraw,
155    Unknown,
156}
157
158impl Display for PaymentMethod {
159    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160        match self {
161            PaymentMethod::Lightning => write!(f, "lightning"),
162            PaymentMethod::Spark => write!(f, "spark"),
163            PaymentMethod::Token => write!(f, "token"),
164            PaymentMethod::Deposit => write!(f, "deposit"),
165            PaymentMethod::Withdraw => write!(f, "withdraw"),
166            PaymentMethod::Unknown => write!(f, "unknown"),
167        }
168    }
169}
170
171impl FromStr for PaymentMethod {
172    type Err = ();
173
174    fn from_str(s: &str) -> Result<Self, Self::Err> {
175        match s {
176            "lightning" => Ok(PaymentMethod::Lightning),
177            "spark" => Ok(PaymentMethod::Spark),
178            "token" => Ok(PaymentMethod::Token),
179            "deposit" => Ok(PaymentMethod::Deposit),
180            "withdraw" => Ok(PaymentMethod::Withdraw),
181            "unknown" => Ok(PaymentMethod::Unknown),
182            _ => Err(()),
183        }
184    }
185}
186
187/// Represents a payment (sent or received)
188#[derive(Debug, Clone, Serialize, Deserialize)]
189#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
190pub struct Payment {
191    /// Unique identifier for the payment
192    pub id: String,
193    /// Type of payment (send or receive)
194    pub payment_type: PaymentType,
195    /// Status of the payment
196    pub status: PaymentStatus,
197    /// Amount in satoshis or token base units
198    pub amount: u128,
199    /// Fee paid in satoshis or token base units
200    pub fees: u128,
201    /// Timestamp of when the payment was created
202    pub timestamp: u64,
203    /// Method of payment. Sometimes the payment details is empty so this field
204    /// is used to determine the payment method.
205    pub method: PaymentMethod,
206    /// Details of the payment
207    pub details: Option<PaymentDetails>,
208}
209
210#[cfg(feature = "uniffi")]
211uniffi::custom_type!(u128, String);
212
213#[cfg(feature = "uniffi")]
214impl crate::UniffiCustomTypeConverter for u128 {
215    type Builtin = String;
216
217    fn into_custom(val: Self::Builtin) -> ::uniffi::Result<Self>
218    where
219        Self: ::std::marker::Sized,
220    {
221        val.parse::<u128>()
222            .map_err(uniffi::deps::anyhow::Error::msg)
223    }
224
225    fn from_custom(obj: Self) -> Self::Builtin {
226        obj.to_string()
227    }
228}
229
230// TODO: fix large enum variant lint - may be done by boxing lnurl_pay_info but that requires
231//  some changes to the wasm bindgen macro
232#[allow(clippy::large_enum_variant)]
233#[derive(Debug, Clone, Serialize, Deserialize)]
234#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
235pub enum PaymentDetails {
236    Spark {
237        /// The invoice details if the payment fulfilled a spark invoice
238        invoice_details: Option<SparkInvoicePaymentDetails>,
239        /// The HTLC transfer details if the payment fulfilled an HTLC transfer
240        htlc_details: Option<SparkHtlcDetails>,
241    },
242    Token {
243        metadata: TokenMetadata,
244        tx_hash: String,
245        /// The invoice details if the payment fulfilled a spark invoice
246        invoice_details: Option<SparkInvoicePaymentDetails>,
247    },
248    Lightning {
249        /// Represents the invoice description
250        description: Option<String>,
251        /// The preimage of the paid invoice (proof of payment).
252        preimage: Option<String>,
253        /// Represents the Bolt11/Bolt12 invoice associated with a payment
254        /// In the case of a Send payment, this is the invoice paid by the user
255        /// In the case of a Receive payment, this is the invoice paid to the user
256        invoice: String,
257
258        /// The payment hash of the invoice
259        payment_hash: String,
260
261        /// The invoice destination/payee pubkey
262        destination_pubkey: String,
263
264        /// Lnurl payment information if this was an lnurl payment.
265        lnurl_pay_info: Option<LnurlPayInfo>,
266
267        /// Lnurl withdrawal information if this was an lnurl payment.
268        lnurl_withdraw_info: Option<LnurlWithdrawInfo>,
269
270        /// Lnurl receive information if this was a received lnurl payment.
271        lnurl_receive_metadata: Option<LnurlReceiveMetadata>,
272    },
273    Withdraw {
274        tx_id: String,
275    },
276    Deposit {
277        tx_id: String,
278    },
279}
280
281#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
282#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
283pub struct SparkInvoicePaymentDetails {
284    /// Represents the spark invoice description
285    pub description: Option<String>,
286    /// The raw spark invoice string
287    pub invoice: String,
288}
289
290#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
291#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
292pub struct SparkHtlcDetails {
293    /// The payment hash of the HTLC
294    pub payment_hash: String,
295    /// The preimage of the HTLC. Empty until receiver has released it.
296    pub preimage: Option<String>,
297    /// The expiry time of the HTLC in seconds since the Unix epoch
298    pub expiry_time: u64,
299    /// The HTLC status
300    pub status: SparkHtlcStatus,
301}
302
303#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
304#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
305pub enum SparkHtlcStatus {
306    /// The HTLC is waiting for the preimage to be shared by the receiver
307    WaitingForPreimage,
308    /// The HTLC preimage has been shared and the transfer can be or has been claimed by the receiver
309    PreimageShared,
310    /// The HTLC has been returned to the sender due to expiry
311    Returned,
312}
313
314impl fmt::Display for SparkHtlcStatus {
315    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
316        match self {
317            SparkHtlcStatus::WaitingForPreimage => write!(f, "WaitingForPreimage"),
318            SparkHtlcStatus::PreimageShared => write!(f, "PreimageShared"),
319            SparkHtlcStatus::Returned => write!(f, "Returned"),
320        }
321    }
322}
323
324impl FromStr for SparkHtlcStatus {
325    type Err = String;
326
327    fn from_str(s: &str) -> Result<Self, Self::Err> {
328        match s {
329            "WaitingForPreimage" => Ok(SparkHtlcStatus::WaitingForPreimage),
330            "PreimageShared" => Ok(SparkHtlcStatus::PreimageShared),
331            "Returned" => Ok(SparkHtlcStatus::Returned),
332            _ => Err("Invalid Spark HTLC status".to_string()),
333        }
334    }
335}
336
337#[derive(Debug, Clone, Copy)]
338#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
339pub enum Network {
340    Mainnet,
341    Regtest,
342}
343
344impl std::fmt::Display for Network {
345    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
346        match self {
347            Network::Mainnet => write!(f, "Mainnet"),
348            Network::Regtest => write!(f, "Regtest"),
349        }
350    }
351}
352
353impl From<Network> for BitcoinNetwork {
354    fn from(network: Network) -> Self {
355        match network {
356            Network::Mainnet => BitcoinNetwork::Bitcoin,
357            Network::Regtest => BitcoinNetwork::Regtest,
358        }
359    }
360}
361
362impl From<Network> for breez_sdk_common::network::BitcoinNetwork {
363    fn from(network: Network) -> Self {
364        match network {
365            Network::Mainnet => breez_sdk_common::network::BitcoinNetwork::Bitcoin,
366            Network::Regtest => breez_sdk_common::network::BitcoinNetwork::Regtest,
367        }
368    }
369}
370
371impl From<Network> for bitcoin::Network {
372    fn from(network: Network) -> Self {
373        match network {
374            Network::Mainnet => bitcoin::Network::Bitcoin,
375            Network::Regtest => bitcoin::Network::Regtest,
376        }
377    }
378}
379
380impl FromStr for Network {
381    type Err = String;
382
383    fn from_str(s: &str) -> Result<Self, Self::Err> {
384        match s {
385            "mainnet" => Ok(Network::Mainnet),
386            "regtest" => Ok(Network::Regtest),
387            _ => Err("Invalid network".to_string()),
388        }
389    }
390}
391
392#[derive(Debug, Clone)]
393#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
394pub struct Config {
395    pub api_key: Option<String>,
396    pub network: Network,
397    pub sync_interval_secs: u32,
398
399    // The maximum fee that can be paid for a static deposit claim
400    // If not set then any fee is allowed
401    pub max_deposit_claim_fee: Option<MaxFee>,
402
403    /// The domain used for receiving through lnurl-pay and lightning address.
404    pub lnurl_domain: Option<String>,
405
406    /// When this is set to `true` we will prefer to use spark payments over
407    /// lightning when sending and receiving. This has the benefit of lower fees
408    /// but is at the cost of privacy.
409    pub prefer_spark_over_lightning: bool,
410
411    /// A set of external input parsers that are used by [`BreezSdk::parse`](crate::sdk::BreezSdk::parse) when the input
412    /// is not recognized. See [`ExternalInputParser`] for more details on how to configure
413    /// external parsing.
414    pub external_input_parsers: Option<Vec<ExternalInputParser>>,
415    /// The SDK includes some default external input parsers
416    /// ([`DEFAULT_EXTERNAL_INPUT_PARSERS`]).
417    /// Set this to false in order to prevent their use.
418    pub use_default_external_input_parsers: bool,
419
420    /// Url to use for the real-time sync server. Defaults to the Breez real-time sync server.
421    pub real_time_sync_server_url: Option<String>,
422
423    /// Whether the Spark private mode is enabled by default.
424    ///
425    /// If set to true, the Spark private mode will be enabled on the first initialization of the SDK.
426    /// If set to false, no changes will be made to the Spark private mode.
427    pub private_enabled_default: bool,
428}
429
430impl Config {
431    pub(crate) fn get_all_external_input_parsers(&self) -> Vec<ExternalInputParser> {
432        let mut external_input_parsers = Vec::new();
433        if self.use_default_external_input_parsers {
434            let default_parsers = DEFAULT_EXTERNAL_INPUT_PARSERS
435                .iter()
436                .map(|(id, regex, url)| ExternalInputParser {
437                    provider_id: (*id).to_string(),
438                    input_regex: (*regex).to_string(),
439                    parser_url: (*url).to_string(),
440                })
441                .collect::<Vec<_>>();
442            external_input_parsers.extend(default_parsers);
443        }
444        external_input_parsers.extend(self.external_input_parsers.clone().unwrap_or_default());
445
446        external_input_parsers
447    }
448}
449
450#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
451#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
452pub enum MaxFee {
453    // Fixed fee amount in sats
454    Fixed { amount: u64 },
455    // Relative fee rate in satoshis per vbyte
456    Rate { sat_per_vbyte: u64 },
457    // Fastest network recommended fee at the time of claim, with a leeway in satoshis per vbyte
458    NetworkRecommended { leeway_sat_per_vbyte: u64 },
459}
460
461impl MaxFee {
462    pub(crate) async fn to_fee(&self, client: &dyn BitcoinChainService) -> Result<Fee, SdkError> {
463        match self {
464            MaxFee::Fixed { amount } => Ok(Fee::Fixed { amount: *amount }),
465            MaxFee::Rate { sat_per_vbyte } => Ok(Fee::Rate {
466                sat_per_vbyte: *sat_per_vbyte,
467            }),
468            MaxFee::NetworkRecommended {
469                leeway_sat_per_vbyte,
470            } => {
471                let recommended_fees = client.recommended_fees().await?;
472                let max_fee_rate = recommended_fees
473                    .fastest_fee
474                    .saturating_add(*leeway_sat_per_vbyte);
475                Ok(Fee::Rate {
476                    sat_per_vbyte: max_fee_rate,
477                })
478            }
479        }
480    }
481}
482
483#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
484#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
485pub enum Fee {
486    // Fixed fee amount in sats
487    Fixed { amount: u64 },
488    // Relative fee rate in satoshis per vbyte
489    Rate { sat_per_vbyte: u64 },
490}
491
492impl Fee {
493    pub fn to_sats(&self, vbytes: u64) -> u64 {
494        match self {
495            Fee::Fixed { amount } => *amount,
496            Fee::Rate { sat_per_vbyte } => sat_per_vbyte.saturating_mul(vbytes),
497        }
498    }
499}
500
501#[derive(Debug, Clone, Serialize, Deserialize)]
502#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
503pub struct DepositInfo {
504    pub txid: String,
505    pub vout: u32,
506    pub amount_sats: u64,
507    pub refund_tx: Option<String>,
508    pub refund_tx_id: Option<String>,
509    pub claim_error: Option<DepositClaimError>,
510}
511
512#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
513pub struct ClaimDepositRequest {
514    pub txid: String,
515    pub vout: u32,
516    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
517    pub max_fee: Option<MaxFee>,
518}
519
520#[derive(Debug, Clone, Serialize)]
521#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
522pub struct ClaimDepositResponse {
523    pub payment: Payment,
524}
525
526#[derive(Debug, Clone, Serialize)]
527#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
528pub struct RefundDepositRequest {
529    pub txid: String,
530    pub vout: u32,
531    pub destination_address: String,
532    pub fee: Fee,
533}
534
535#[derive(Debug, Clone, Serialize)]
536#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
537pub struct RefundDepositResponse {
538    pub tx_id: String,
539    pub tx_hex: String,
540}
541
542#[derive(Debug, Clone, Serialize)]
543#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
544pub struct ListUnclaimedDepositsRequest {}
545
546#[derive(Debug, Clone, Serialize)]
547#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
548pub struct ListUnclaimedDepositsResponse {
549    pub deposits: Vec<DepositInfo>,
550}
551
552impl std::fmt::Display for MaxFee {
553    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
554        match self {
555            MaxFee::Fixed { amount } => write!(f, "Fixed: {amount}"),
556            MaxFee::Rate { sat_per_vbyte } => write!(f, "Rate: {sat_per_vbyte}"),
557            MaxFee::NetworkRecommended {
558                leeway_sat_per_vbyte,
559            } => write!(f, "NetworkRecommended: {leeway_sat_per_vbyte}"),
560        }
561    }
562}
563
564#[derive(Debug, Clone)]
565#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
566pub struct Credentials {
567    pub username: String,
568    pub password: String,
569}
570
571/// Request to get the balance of the wallet
572#[derive(Debug, Clone)]
573#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
574pub struct GetInfoRequest {
575    pub ensure_synced: Option<bool>,
576}
577
578/// Response containing the balance of the wallet
579#[derive(Debug, Clone, Serialize)]
580#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
581pub struct GetInfoResponse {
582    /// The balance in satoshis
583    pub balance_sats: u64,
584    /// The balances of the tokens in the wallet keyed by the token identifier
585    pub token_balances: HashMap<String, TokenBalance>,
586}
587
588#[derive(Debug, Clone, Serialize, Deserialize)]
589#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
590pub struct TokenBalance {
591    pub balance: u128,
592    pub token_metadata: TokenMetadata,
593}
594
595#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
596#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
597pub struct TokenMetadata {
598    pub identifier: String,
599    /// Hex representation of the issuer public key
600    pub issuer_public_key: String,
601    pub name: String,
602    pub ticker: String,
603    /// Number of decimals the token uses
604    pub decimals: u32,
605    pub max_supply: u128,
606    pub is_freezable: bool,
607}
608
609/// Request to sync the wallet with the Spark network
610#[derive(Debug, Clone)]
611#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
612pub struct SyncWalletRequest {}
613
614/// Response from synchronizing the wallet
615#[derive(Debug, Clone, Serialize)]
616#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
617pub struct SyncWalletResponse {}
618
619#[derive(Debug, Clone, Serialize)]
620#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
621pub enum ReceivePaymentMethod {
622    SparkAddress,
623    SparkInvoice {
624        /// Amount to receive. Denominated in sats if token identifier is empty, otherwise in the token base units
625        amount: Option<u128>,
626        /// The presence of this field indicates that the payment is for a token
627        /// If empty, it is a Bitcoin payment
628        token_identifier: Option<String>,
629        /// The expiry time of the invoice in seconds since the Unix epoch
630        expiry_time: Option<u64>,
631        /// A description to embed in the invoice.
632        description: Option<String>,
633        /// If set, the invoice may only be fulfilled by a payer with this public key
634        sender_public_key: Option<String>,
635    },
636    BitcoinAddress,
637    Bolt11Invoice {
638        description: String,
639        amount_sats: Option<u64>,
640    },
641}
642
643#[derive(Debug, Clone, Serialize)]
644#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
645pub enum SendPaymentMethod {
646    BitcoinAddress {
647        address: BitcoinAddressDetails,
648        fee_quote: SendOnchainFeeQuote,
649    },
650    Bolt11Invoice {
651        invoice_details: Bolt11InvoiceDetails,
652        spark_transfer_fee_sats: Option<u64>,
653        lightning_fee_sats: u64,
654    }, // should be replaced with the parsed invoice
655    SparkAddress {
656        address: String,
657        /// Fee to pay for the transaction
658        /// Denominated in sats if token identifier is empty, otherwise in the token base units
659        fee: u128,
660        /// The presence of this field indicates that the payment is for a token
661        /// If empty, it is a Bitcoin payment
662        token_identifier: Option<String>,
663    },
664    SparkInvoice {
665        spark_invoice_details: SparkInvoiceDetails,
666        /// Fee to pay for the transaction
667        /// Denominated in sats if token identifier is empty, otherwise in the token base units
668        fee: u128,
669        /// The presence of this field indicates that the payment is for a token
670        /// If empty, it is a Bitcoin payment
671        token_identifier: Option<String>,
672    },
673}
674
675#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
676#[derive(Debug, Clone, Serialize)]
677pub struct SendOnchainFeeQuote {
678    pub id: String,
679    pub expires_at: u64,
680    pub speed_fast: SendOnchainSpeedFeeQuote,
681    pub speed_medium: SendOnchainSpeedFeeQuote,
682    pub speed_slow: SendOnchainSpeedFeeQuote,
683}
684
685#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
686#[derive(Debug, Clone, Serialize)]
687pub struct SendOnchainSpeedFeeQuote {
688    pub user_fee_sat: u64,
689    pub l1_broadcast_fee_sat: u64,
690}
691
692impl SendOnchainSpeedFeeQuote {
693    pub fn total_fee_sat(&self) -> u64 {
694        self.user_fee_sat.saturating_add(self.l1_broadcast_fee_sat)
695    }
696}
697
698#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
699pub struct ReceivePaymentRequest {
700    pub payment_method: ReceivePaymentMethod,
701}
702
703#[derive(Debug, Clone, Serialize)]
704#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
705pub struct ReceivePaymentResponse {
706    pub payment_request: String,
707    /// Fee to pay to receive the payment
708    /// Denominated in sats or token base units
709    pub fee: u128,
710}
711
712#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
713pub struct PrepareLnurlPayRequest {
714    pub amount_sats: u64,
715    pub pay_request: LnurlPayRequestDetails,
716    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
717    pub comment: Option<String>,
718    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
719    pub validate_success_action_url: Option<bool>,
720}
721
722#[derive(Debug)]
723#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
724pub struct PrepareLnurlPayResponse {
725    pub amount_sats: u64,
726    pub comment: Option<String>,
727    pub pay_request: LnurlPayRequestDetails,
728    pub fee_sats: u64,
729    pub invoice_details: Bolt11InvoiceDetails,
730    pub success_action: Option<SuccessAction>,
731}
732
733#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
734pub struct LnurlPayRequest {
735    pub prepare_response: PrepareLnurlPayResponse,
736    /// If set, providing the same idempotency key for multiple requests will ensure that only one
737    /// payment is made. If an idempotency key is re-used, the same payment will be returned.
738    /// The idempotency key must be a valid UUID.
739    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
740    pub idempotency_key: Option<String>,
741}
742
743#[derive(Debug, Serialize)]
744#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
745pub struct LnurlPayResponse {
746    pub payment: Payment,
747    pub success_action: Option<SuccessActionProcessed>,
748}
749
750#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
751pub struct LnurlWithdrawRequest {
752    /// The amount to withdraw in satoshis
753    /// Must be within the min and max withdrawable limits
754    pub amount_sats: u64,
755    pub withdraw_request: LnurlWithdrawRequestDetails,
756    /// If set, the function will return the payment if it is still pending after this
757    /// number of seconds. If unset, the function will return immediately after
758    /// initiating the LNURL withdraw.
759    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
760    pub completion_timeout_secs: Option<u32>,
761}
762
763#[derive(Debug, Serialize)]
764#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
765pub struct LnurlWithdrawResponse {
766    /// The Lightning invoice generated for the LNURL withdraw
767    pub payment_request: String,
768    pub payment: Option<Payment>,
769}
770
771/// Represents the payment LNURL info
772#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
773#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
774pub struct LnurlPayInfo {
775    pub ln_address: Option<String>,
776    pub comment: Option<String>,
777    pub domain: Option<String>,
778    pub metadata: Option<String>,
779    pub processed_success_action: Option<SuccessActionProcessed>,
780    pub raw_success_action: Option<SuccessAction>,
781}
782
783/// Represents the withdraw LNURL info
784#[derive(Clone, Debug, Deserialize, Serialize)]
785#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
786pub struct LnurlWithdrawInfo {
787    pub withdraw_url: String,
788}
789
790impl LnurlPayInfo {
791    pub fn extract_description(&self) -> Option<String> {
792        let Some(metadata) = &self.metadata else {
793            return None;
794        };
795
796        let Ok(metadata) = serde_json::from_str::<Vec<Vec<Value>>>(metadata) else {
797            return None;
798        };
799
800        for arr in metadata {
801            if arr.len() != 2 {
802                continue;
803            }
804            if let (Some(key), Some(value)) = (arr[0].as_str(), arr[1].as_str())
805                && key == "text/plain"
806            {
807                return Some(value.to_string());
808            }
809        }
810
811        None
812    }
813}
814
815#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
816#[derive(Debug, Clone, Serialize)]
817pub enum OnchainConfirmationSpeed {
818    Fast,
819    Medium,
820    Slow,
821}
822
823#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
824pub struct PrepareSendPaymentRequest {
825    pub payment_request: String,
826    /// Amount to send. By default is denominated in sats.
827    /// If a token identifier is provided, the amount will be denominated in the token base units.
828    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
829    pub amount: Option<u128>,
830    /// If provided, the payment will be for a token
831    /// May only be provided if the payment request is a spark address
832    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
833    pub token_identifier: Option<String>,
834}
835
836#[derive(Debug, Clone, Serialize)]
837#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
838pub struct PrepareSendPaymentResponse {
839    pub payment_method: SendPaymentMethod,
840    /// Amount to send. By default is denominated in sats.
841    /// If a token identifier is provided, the amount will be denominated in the token base units.
842    pub amount: u128,
843    /// The presence of this field indicates that the payment is for a token
844    /// If empty, it is a Bitcoin payment
845    pub token_identifier: Option<String>,
846}
847
848#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
849pub enum SendPaymentOptions {
850    BitcoinAddress {
851        confirmation_speed: OnchainConfirmationSpeed,
852    },
853    Bolt11Invoice {
854        prefer_spark: bool,
855
856        /// If set, the function will return the payment if it is still pending after this
857        /// number of seconds. If unset, the function will return immediately after initiating the payment.
858        completion_timeout_secs: Option<u32>,
859    },
860    SparkAddress {
861        /// Can only be provided for Bitcoin payments. If set, a Spark HTLC transfer will be created.
862        /// The receiver will need to provide the preimage to claim it.
863        htlc_options: Option<SparkHtlcOptions>,
864    },
865}
866
867#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
868pub struct SparkHtlcOptions {
869    /// The payment hash of the HTLC. The receiver will need to provide the associated preimage to claim it.
870    pub payment_hash: String,
871    /// The duration of the HTLC in seconds.
872    /// After this time, the HTLC will be returned.
873    pub expiry_duration_secs: u64,
874}
875
876#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
877pub struct SendPaymentRequest {
878    pub prepare_response: PrepareSendPaymentResponse,
879    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
880    pub options: Option<SendPaymentOptions>,
881    /// The optional idempotency key for all Spark based transfers (excludes token payments).
882    /// If set, providing the same idempotency key for multiple requests will ensure that only one
883    /// payment is made. If an idempotency key is re-used, the same payment will be returned.
884    /// The idempotency key must be a valid UUID.
885    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
886    pub idempotency_key: Option<String>,
887}
888
889#[derive(Debug, Clone, Serialize)]
890#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
891pub struct SendPaymentResponse {
892    pub payment: Payment,
893}
894
895/// Request to list payments with optional filters and pagination
896#[derive(Debug, Clone, Default)]
897#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
898pub struct ListPaymentsRequest {
899    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
900    pub type_filter: Option<Vec<PaymentType>>,
901    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
902    pub status_filter: Option<Vec<PaymentStatus>>,
903    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
904    pub asset_filter: Option<AssetFilter>,
905    /// Only include payments with specific Spark HTLC statuses
906    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
907    pub spark_htlc_status_filter: Option<Vec<SparkHtlcStatus>>,
908    /// Only include payments created after this timestamp (inclusive)
909    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
910    pub from_timestamp: Option<u64>,
911    /// Only include payments created before this timestamp (exclusive)
912    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
913    pub to_timestamp: Option<u64>,
914    /// Number of records to skip
915    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
916    pub offset: Option<u32>,
917    /// Maximum number of records to return
918    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
919    pub limit: Option<u32>,
920    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
921    pub sort_ascending: Option<bool>,
922}
923
924/// A field of [`ListPaymentsRequest`] when listing payments filtered by asset
925#[derive(Debug, Clone)]
926#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
927pub enum AssetFilter {
928    Bitcoin,
929    Token {
930        /// Optional token identifier to filter by
931        token_identifier: Option<String>,
932    },
933}
934
935impl FromStr for AssetFilter {
936    type Err = String;
937
938    fn from_str(s: &str) -> Result<Self, Self::Err> {
939        Ok(match s.to_lowercase().as_str() {
940            "bitcoin" => AssetFilter::Bitcoin,
941            "token" => AssetFilter::Token {
942                token_identifier: None,
943            },
944            str if str.starts_with("token:") => AssetFilter::Token {
945                token_identifier: Some(
946                    str.split_once(':')
947                        .ok_or(format!("Invalid asset filter '{s}'"))?
948                        .1
949                        .to_string(),
950                ),
951            },
952            _ => return Err(format!("Invalid asset filter '{s}'")),
953        })
954    }
955}
956
957/// Response from listing payments
958#[derive(Debug, Clone, Serialize)]
959#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
960pub struct ListPaymentsResponse {
961    /// The list of payments
962    pub payments: Vec<Payment>,
963}
964
965#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
966pub struct GetPaymentRequest {
967    pub payment_id: String,
968}
969
970#[derive(Debug, Clone, Serialize)]
971#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
972pub struct GetPaymentResponse {
973    pub payment: Payment,
974}
975
976#[cfg_attr(feature = "uniffi", uniffi::export(callback_interface))]
977pub trait Logger: Send + Sync {
978    fn log(&self, l: LogEntry);
979}
980
981#[derive(Debug, Clone, Serialize)]
982#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
983pub struct LogEntry {
984    pub line: String,
985    pub level: String,
986}
987
988#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
989#[derive(Debug, Clone, Serialize, Deserialize)]
990pub struct CheckLightningAddressRequest {
991    pub username: String,
992}
993
994#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
995#[derive(Debug, Clone, Serialize, Deserialize)]
996pub struct RegisterLightningAddressRequest {
997    pub username: String,
998    #[cfg_attr(feature = "uniffi", uniffi(default=None))]
999    pub description: Option<String>,
1000}
1001
1002#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1003#[derive(Deserialize, Serialize)]
1004pub struct LightningAddressInfo {
1005    pub description: String,
1006    pub lightning_address: String,
1007    pub lnurl: String,
1008    pub username: String,
1009}
1010
1011impl From<RecoverLnurlPayResponse> for LightningAddressInfo {
1012    fn from(resp: RecoverLnurlPayResponse) -> Self {
1013        Self {
1014            description: resp.description,
1015            lightning_address: resp.lightning_address,
1016            lnurl: resp.lnurl,
1017            username: resp.username,
1018        }
1019    }
1020}
1021
1022#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1023#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
1024pub enum KeySetType {
1025    #[default]
1026    Default,
1027    Taproot,
1028    NativeSegwit,
1029    WrappedSegwit,
1030    Legacy,
1031}
1032
1033impl From<spark_wallet::KeySetType> for KeySetType {
1034    fn from(value: spark_wallet::KeySetType) -> Self {
1035        match value {
1036            spark_wallet::KeySetType::Default => KeySetType::Default,
1037            spark_wallet::KeySetType::Taproot => KeySetType::Taproot,
1038            spark_wallet::KeySetType::NativeSegwit => KeySetType::NativeSegwit,
1039            spark_wallet::KeySetType::WrappedSegwit => KeySetType::WrappedSegwit,
1040            spark_wallet::KeySetType::Legacy => KeySetType::Legacy,
1041        }
1042    }
1043}
1044
1045impl From<KeySetType> for spark_wallet::KeySetType {
1046    fn from(value: KeySetType) -> Self {
1047        match value {
1048            KeySetType::Default => spark_wallet::KeySetType::Default,
1049            KeySetType::Taproot => spark_wallet::KeySetType::Taproot,
1050            KeySetType::NativeSegwit => spark_wallet::KeySetType::NativeSegwit,
1051            KeySetType::WrappedSegwit => spark_wallet::KeySetType::WrappedSegwit,
1052            KeySetType::Legacy => spark_wallet::KeySetType::Legacy,
1053        }
1054    }
1055}
1056
1057/// Response from listing fiat currencies
1058#[derive(Debug, Clone, Serialize)]
1059#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1060pub struct ListFiatCurrenciesResponse {
1061    /// The list of fiat currencies
1062    pub currencies: Vec<FiatCurrency>,
1063}
1064
1065/// Response from listing fiat rates
1066#[derive(Debug, Clone, Serialize)]
1067#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1068pub struct ListFiatRatesResponse {
1069    /// The list of fiat rates
1070    pub rates: Vec<Rate>,
1071}
1072
1073pub(crate) enum WaitForPaymentIdentifier {
1074    PaymentId(String),
1075    PaymentRequest(String),
1076}
1077
1078#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1079pub struct GetTokensMetadataRequest {
1080    pub token_identifiers: Vec<String>,
1081}
1082
1083#[derive(Debug, Clone, Serialize)]
1084#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1085pub struct GetTokensMetadataResponse {
1086    pub tokens_metadata: Vec<TokenMetadata>,
1087}
1088
1089#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1090pub struct SignMessageRequest {
1091    pub message: String,
1092    /// If true, the signature will be encoded in compact format instead of DER format
1093    pub compact: bool,
1094}
1095
1096#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1097pub struct SignMessageResponse {
1098    pub pubkey: String,
1099    /// The DER or compact hex encoded signature
1100    pub signature: String,
1101}
1102
1103#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1104pub struct CheckMessageRequest {
1105    /// The message that was signed
1106    pub message: String,
1107    /// The public key that signed the message
1108    pub pubkey: String,
1109    /// The DER or compact hex encoded signature
1110    pub signature: String,
1111}
1112
1113#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1114pub struct CheckMessageResponse {
1115    pub is_valid: bool,
1116}
1117
1118#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1119#[derive(Debug, Clone, Serialize)]
1120pub struct UserSettings {
1121    pub spark_private_mode_enabled: bool,
1122}
1123
1124#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1125pub struct UpdateUserSettingsRequest {
1126    pub spark_private_mode_enabled: Option<bool>,
1127}
1128
1129#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1130pub struct ClaimHtlcPaymentRequest {
1131    pub preimage: String,
1132}
1133
1134#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1135pub struct ClaimHtlcPaymentResponse {
1136    pub payment: Payment,
1137}
1138
1139#[derive(Debug, Clone, Deserialize, Serialize)]
1140#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1141pub struct LnurlReceiveMetadata {
1142    pub nostr_zap_request: Option<String>,
1143    pub nostr_zap_receipt: Option<String>,
1144    pub sender_comment: Option<String>,
1145}