breez_sdk_liquid/
model.rs

1use anyhow::{anyhow, bail, Result};
2use bitcoin::{bip32, ScriptBuf};
3use boltz_client::{
4    boltz::{ChainPair, BOLTZ_MAINNET_URL_V2, BOLTZ_TESTNET_URL_V2},
5    network::{BitcoinChain, Chain, LiquidChain},
6    swaps::boltz::{
7        CreateChainResponse, CreateReverseResponse, CreateSubmarineResponse, Leaf, Side, SwapTree,
8    },
9    BtcSwapScript, Keypair, LBtcSwapScript,
10};
11use derivative::Derivative;
12use elements::AssetId;
13use lwk_wollet::ElementsNetwork;
14use maybe_sync::{MaybeSend, MaybeSync};
15use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef};
16use rusqlite::ToSql;
17use sdk_common::prelude::*;
18use sdk_common::utils::Arc;
19use sdk_common::{bitcoin::hashes::hex::ToHex, lightning_with_bolt12::offers::offer::Offer};
20use serde::{Deserialize, Serialize};
21use std::cmp::PartialEq;
22use std::path::PathBuf;
23use std::str::FromStr;
24use strum_macros::{Display, EnumString};
25use tokio_with_wasm::alias as tokio;
26
27use crate::{
28    bitcoin,
29    chain::bitcoin::esplora::EsploraBitcoinChainService,
30    chain::liquid::esplora::EsploraLiquidChainService,
31    chain::{bitcoin::BitcoinChainService, liquid::LiquidChainService},
32    elements,
33    error::{PaymentError, SdkError, SdkResult},
34    persist::model::PaymentTxBalance,
35    prelude::DEFAULT_EXTERNAL_INPUT_PARSERS,
36    receive_swap::DEFAULT_ZERO_CONF_MAX_SAT,
37    side_swap::api::{SIDESWAP_MAINNET_URL, SIDESWAP_TESTNET_URL},
38    utils,
39};
40
41// Uses f64 for the maximum precision when converting between units
42pub const LIQUID_FEE_RATE_SAT_PER_VBYTE: f64 = 0.1;
43pub const LIQUID_FEE_RATE_MSAT_PER_VBYTE: f32 = (LIQUID_FEE_RATE_SAT_PER_VBYTE * 1000.0) as f32;
44pub const BREEZ_SYNC_SERVICE_URL: &str = "https://datasync.breez.technology";
45pub const BREEZ_LIQUID_ESPLORA_URL: &str = "https://lq1.breez.technology/liquid/api";
46pub const BREEZ_SWAP_PROXY_URL: &str = "https://swap.breez.technology/v2";
47pub const DEFAULT_ONCHAIN_FEE_RATE_LEEWAY_SAT: u64 = 500;
48
49const SIDESWAP_API_KEY: &str = "97fb6a1dfa37ee6656af92ef79675cc03b8ac4c52e04655f41edbd5af888dcc2";
50
51#[derive(Clone, Debug, Serialize)]
52pub enum BlockchainExplorer {
53    #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
54    Electrum { url: String },
55    Esplora {
56        url: String,
57        /// Whether or not to use the "waterfalls" extension
58        use_waterfalls: bool,
59    },
60}
61
62/// Configuration for the Liquid SDK
63#[derive(Clone, Debug, Serialize)]
64pub struct Config {
65    pub liquid_explorer: BlockchainExplorer,
66    pub bitcoin_explorer: BlockchainExplorer,
67    /// Directory in which the DB and log files are stored.
68    ///
69    /// Prefix can be a relative or absolute path to this directory.
70    pub working_dir: String,
71    pub network: LiquidNetwork,
72    /// Send payment timeout. See [LiquidSdk::send_payment](crate::sdk::LiquidSdk::send_payment)
73    pub payment_timeout_sec: u64,
74    /// The url of the real-time sync service. Defaults to [BREEZ_SYNC_SERVICE_URL]
75    /// Setting this field to `None` will disable the service
76    pub sync_service_url: Option<String>,
77    /// Maximum amount in satoshi to accept zero-conf payments with
78    /// Defaults to [DEFAULT_ZERO_CONF_MAX_SAT]
79    pub zero_conf_max_amount_sat: Option<u64>,
80    /// The Breez API key used for making requests to the sync service
81    pub breez_api_key: Option<String>,
82    /// A set of external input parsers that are used by [LiquidSdk::parse](crate::sdk::LiquidSdk::parse) when the input
83    /// is not recognized. See [ExternalInputParser] for more details on how to configure
84    /// external parsing.
85    pub external_input_parsers: Option<Vec<ExternalInputParser>>,
86    /// The SDK includes some default external input parsers
87    /// ([DEFAULT_EXTERNAL_INPUT_PARSERS](crate::sdk::DEFAULT_EXTERNAL_INPUT_PARSERS)).
88    /// Set this to false in order to prevent their use.
89    pub use_default_external_input_parsers: bool,
90    /// For payments where the onchain fees can only be estimated on creation, this can be used
91    /// in order to automatically allow slightly more expensive fees. If the actual fee ends up
92    /// being above the sum of the initial estimate and this leeway, the payment will require
93    /// user fee acceptance. See [WaitingFeeAcceptance](PaymentState::WaitingFeeAcceptance).
94    ///
95    /// Defaults to [DEFAULT_ONCHAIN_FEE_RATE_LEEWAY_SAT].
96    pub onchain_fee_rate_leeway_sat: Option<u64>,
97    /// A set of asset metadata used by [LiquidSdk::parse](crate::sdk::LiquidSdk::parse) when the input is a
98    /// [LiquidAddressData] and the [asset_id](LiquidAddressData::asset_id) differs from the Liquid Bitcoin asset.
99    /// See [AssetMetadata] for more details on how define asset metadata.
100    /// By default the asset metadata for Liquid Bitcoin and Tether USD are included.
101    pub asset_metadata: Option<Vec<AssetMetadata>>,
102    /// The SideSwap API key used for making requests to the SideSwap payjoin service
103    pub sideswap_api_key: Option<String>,
104    /// Set this to false to disable the use of Magic Routing Hints (MRH) to send payments. Enabled by default.
105    pub use_magic_routing_hints: bool,
106}
107
108impl Config {
109    #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
110    pub fn mainnet(breez_api_key: Option<String>) -> Self {
111        Config {
112            liquid_explorer: BlockchainExplorer::Electrum {
113                url: "elements-mainnet.breez.technology:50002".to_string(),
114            },
115            bitcoin_explorer: BlockchainExplorer::Electrum {
116                url: "bitcoin-mainnet.blockstream.info:50002".to_string(),
117            },
118            working_dir: ".".to_string(),
119            network: LiquidNetwork::Mainnet,
120            payment_timeout_sec: 15,
121            sync_service_url: Some(BREEZ_SYNC_SERVICE_URL.to_string()),
122            zero_conf_max_amount_sat: None,
123            breez_api_key,
124            external_input_parsers: None,
125            use_default_external_input_parsers: true,
126            onchain_fee_rate_leeway_sat: None,
127            asset_metadata: None,
128            sideswap_api_key: Some(SIDESWAP_API_KEY.to_string()),
129            use_magic_routing_hints: true,
130        }
131    }
132
133    pub fn mainnet_esplora(breez_api_key: Option<String>) -> Self {
134        Config {
135            liquid_explorer: BlockchainExplorer::Esplora {
136                url: BREEZ_LIQUID_ESPLORA_URL.to_string(),
137                use_waterfalls: true,
138            },
139            bitcoin_explorer: BlockchainExplorer::Esplora {
140                url: "https://blockstream.info/api/".to_string(),
141                use_waterfalls: false,
142            },
143            working_dir: ".".to_string(),
144            network: LiquidNetwork::Mainnet,
145            payment_timeout_sec: 15,
146            sync_service_url: Some(BREEZ_SYNC_SERVICE_URL.to_string()),
147            zero_conf_max_amount_sat: None,
148            breez_api_key,
149            external_input_parsers: None,
150            use_default_external_input_parsers: true,
151            onchain_fee_rate_leeway_sat: None,
152            asset_metadata: None,
153            sideswap_api_key: Some(SIDESWAP_API_KEY.to_string()),
154            use_magic_routing_hints: true,
155        }
156    }
157
158    #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
159    pub fn testnet(breez_api_key: Option<String>) -> Self {
160        Config {
161            liquid_explorer: BlockchainExplorer::Electrum {
162                url: "elements-testnet.blockstream.info:50002".to_string(),
163            },
164            bitcoin_explorer: BlockchainExplorer::Electrum {
165                url: "bitcoin-testnet.blockstream.info:50002".to_string(),
166            },
167            working_dir: ".".to_string(),
168            network: LiquidNetwork::Testnet,
169            payment_timeout_sec: 15,
170            sync_service_url: Some(BREEZ_SYNC_SERVICE_URL.to_string()),
171            zero_conf_max_amount_sat: None,
172            breez_api_key,
173            external_input_parsers: None,
174            use_default_external_input_parsers: true,
175            onchain_fee_rate_leeway_sat: None,
176            asset_metadata: None,
177            sideswap_api_key: Some(SIDESWAP_API_KEY.to_string()),
178            use_magic_routing_hints: true,
179        }
180    }
181
182    pub fn testnet_esplora(breez_api_key: Option<String>) -> Self {
183        Config {
184            liquid_explorer: BlockchainExplorer::Esplora {
185                url: "https://blockstream.info/liquidtestnet/api".to_string(),
186                use_waterfalls: false,
187            },
188            bitcoin_explorer: BlockchainExplorer::Esplora {
189                url: "https://blockstream.info/testnet/api/".to_string(),
190                use_waterfalls: false,
191            },
192            working_dir: ".".to_string(),
193            network: LiquidNetwork::Testnet,
194            payment_timeout_sec: 15,
195            sync_service_url: Some(BREEZ_SYNC_SERVICE_URL.to_string()),
196            zero_conf_max_amount_sat: None,
197            breez_api_key,
198            external_input_parsers: None,
199            use_default_external_input_parsers: true,
200            onchain_fee_rate_leeway_sat: None,
201            asset_metadata: None,
202            sideswap_api_key: Some(SIDESWAP_API_KEY.to_string()),
203            use_magic_routing_hints: true,
204        }
205    }
206
207    #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
208    pub fn regtest() -> Self {
209        Config {
210            liquid_explorer: BlockchainExplorer::Electrum {
211                url: "localhost:19002".to_string(),
212            },
213            bitcoin_explorer: BlockchainExplorer::Electrum {
214                url: "localhost:19001".to_string(),
215            },
216            working_dir: ".".to_string(),
217            network: LiquidNetwork::Regtest,
218            payment_timeout_sec: 15,
219            sync_service_url: Some("http://localhost:8088".to_string()),
220            zero_conf_max_amount_sat: None,
221            breez_api_key: None,
222            external_input_parsers: None,
223            use_default_external_input_parsers: true,
224            onchain_fee_rate_leeway_sat: None,
225            asset_metadata: None,
226            sideswap_api_key: None,
227            use_magic_routing_hints: true,
228        }
229    }
230
231    pub fn regtest_esplora() -> Self {
232        Config {
233            liquid_explorer: BlockchainExplorer::Esplora {
234                url: "http://localhost:3120/api".to_string(),
235                use_waterfalls: true,
236            },
237            bitcoin_explorer: BlockchainExplorer::Esplora {
238                url: "http://localhost:4002/api".to_string(),
239                use_waterfalls: false,
240            },
241            working_dir: ".".to_string(),
242            network: LiquidNetwork::Regtest,
243            payment_timeout_sec: 15,
244            sync_service_url: Some("http://localhost:8089".to_string()),
245            zero_conf_max_amount_sat: None,
246            breez_api_key: None,
247            external_input_parsers: None,
248            use_default_external_input_parsers: true,
249            onchain_fee_rate_leeway_sat: None,
250            asset_metadata: None,
251            sideswap_api_key: None,
252            use_magic_routing_hints: true,
253        }
254    }
255
256    pub fn get_wallet_dir(&self, base_dir: &str, fingerprint_hex: &str) -> anyhow::Result<String> {
257        Ok(PathBuf::from(base_dir)
258            .join(match self.network {
259                LiquidNetwork::Mainnet => "mainnet",
260                LiquidNetwork::Testnet => "testnet",
261                LiquidNetwork::Regtest => "regtest",
262            })
263            .join(fingerprint_hex)
264            .to_str()
265            .ok_or(anyhow::anyhow!(
266                "Could not get retrieve current wallet directory"
267            ))?
268            .to_string())
269    }
270
271    pub fn zero_conf_max_amount_sat(&self) -> u64 {
272        self.zero_conf_max_amount_sat
273            .unwrap_or(DEFAULT_ZERO_CONF_MAX_SAT)
274    }
275
276    pub(crate) fn lbtc_asset_id(&self) -> String {
277        utils::lbtc_asset_id(self.network).to_string()
278    }
279
280    pub(crate) fn get_all_external_input_parsers(&self) -> Vec<ExternalInputParser> {
281        let mut external_input_parsers = Vec::new();
282        if self.use_default_external_input_parsers {
283            let default_parsers = DEFAULT_EXTERNAL_INPUT_PARSERS
284                .iter()
285                .map(|(id, regex, url)| ExternalInputParser {
286                    provider_id: id.to_string(),
287                    input_regex: regex.to_string(),
288                    parser_url: url.to_string(),
289                })
290                .collect::<Vec<_>>();
291            external_input_parsers.extend(default_parsers);
292        }
293        external_input_parsers.extend(self.external_input_parsers.clone().unwrap_or_default());
294
295        external_input_parsers
296    }
297
298    pub(crate) fn default_boltz_url(&self) -> &str {
299        match self.network {
300            LiquidNetwork::Mainnet => {
301                if self.breez_api_key.is_some() {
302                    BREEZ_SWAP_PROXY_URL
303                } else {
304                    BOLTZ_MAINNET_URL_V2
305                }
306            }
307            LiquidNetwork::Testnet => BOLTZ_TESTNET_URL_V2,
308            // On regtest use the swapproxy instance by default
309            LiquidNetwork::Regtest => "http://localhost:8387/v2",
310        }
311    }
312
313    pub fn sync_enabled(&self) -> bool {
314        self.sync_service_url.is_some()
315    }
316
317    pub(crate) fn bitcoin_chain_service(&self) -> Arc<dyn BitcoinChainService> {
318        match self.bitcoin_explorer {
319            BlockchainExplorer::Esplora { .. } => {
320                Arc::new(EsploraBitcoinChainService::new(self.clone()))
321            }
322            #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
323            BlockchainExplorer::Electrum { .. } => Arc::new(
324                crate::chain::bitcoin::electrum::ElectrumBitcoinChainService::new(self.clone()),
325            ),
326        }
327    }
328
329    pub(crate) fn liquid_chain_service(&self) -> Result<Arc<dyn LiquidChainService>> {
330        match &self.liquid_explorer {
331            BlockchainExplorer::Esplora { url, .. } => {
332                if url == BREEZ_LIQUID_ESPLORA_URL && self.breez_api_key.is_none() {
333                    bail!("Cannot start the Breez Esplora chain service without providing an API key. See https://sdk-doc-liquid.breez.technology/guide/getting_started.html#api-key")
334                }
335                Ok(Arc::new(EsploraLiquidChainService::new(self.clone())))
336            }
337            #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
338            BlockchainExplorer::Electrum { .. } => Ok(Arc::new(
339                crate::chain::liquid::electrum::ElectrumLiquidChainService::new(self.clone()),
340            )),
341        }
342    }
343
344    #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
345    pub(crate) fn electrum_tls_options(&self) -> (/*tls*/ bool, /*validate_domain*/ bool) {
346        match self.network {
347            LiquidNetwork::Mainnet | LiquidNetwork::Testnet => (true, true),
348            LiquidNetwork::Regtest => (false, false),
349        }
350    }
351
352    #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
353    pub(crate) fn electrum_client(
354        &self,
355        url: &str,
356    ) -> Result<lwk_wollet::ElectrumClient, lwk_wollet::Error> {
357        let (tls, validate_domain) = self.electrum_tls_options();
358        let electrum_url = lwk_wollet::ElectrumUrl::new(url, tls, validate_domain)?;
359        lwk_wollet::ElectrumClient::with_options(
360            &electrum_url,
361            lwk_wollet::ElectrumOptions { timeout: Some(3) },
362        )
363    }
364
365    pub(crate) fn sideswap_url(&self) -> &'static str {
366        match self.network {
367            LiquidNetwork::Mainnet => SIDESWAP_MAINNET_URL,
368            LiquidNetwork::Testnet => SIDESWAP_TESTNET_URL,
369            LiquidNetwork::Regtest => unimplemented!(),
370        }
371    }
372}
373
374/// Network chosen for this Liquid SDK instance. Note that it represents both the Liquid and the
375/// Bitcoin network used.
376#[derive(Debug, Display, Copy, Clone, PartialEq, Serialize)]
377pub enum LiquidNetwork {
378    /// Mainnet Bitcoin and Liquid chains
379    Mainnet,
380    /// Testnet Bitcoin and Liquid chains
381    Testnet,
382    /// Regtest Bitcoin and Liquid chains
383    Regtest,
384}
385impl LiquidNetwork {
386    pub fn as_bitcoin_chain(&self) -> BitcoinChain {
387        match self {
388            LiquidNetwork::Mainnet => BitcoinChain::Bitcoin,
389            LiquidNetwork::Testnet => BitcoinChain::BitcoinTestnet,
390            LiquidNetwork::Regtest => BitcoinChain::BitcoinRegtest,
391        }
392    }
393}
394
395impl From<LiquidNetwork> for ElementsNetwork {
396    fn from(value: LiquidNetwork) -> Self {
397        match value {
398            LiquidNetwork::Mainnet => ElementsNetwork::Liquid,
399            LiquidNetwork::Testnet => ElementsNetwork::LiquidTestnet,
400            LiquidNetwork::Regtest => ElementsNetwork::ElementsRegtest {
401                policy_asset: AssetId::from_str(
402                    "5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225",
403                )
404                .unwrap(),
405            },
406        }
407    }
408}
409
410impl From<LiquidNetwork> for Chain {
411    fn from(value: LiquidNetwork) -> Self {
412        Chain::Liquid(value.into())
413    }
414}
415
416impl From<LiquidNetwork> for LiquidChain {
417    fn from(value: LiquidNetwork) -> Self {
418        match value {
419            LiquidNetwork::Mainnet => LiquidChain::Liquid,
420            LiquidNetwork::Testnet => LiquidChain::LiquidTestnet,
421            LiquidNetwork::Regtest => LiquidChain::LiquidRegtest,
422        }
423    }
424}
425
426impl TryFrom<&str> for LiquidNetwork {
427    type Error = anyhow::Error;
428
429    fn try_from(value: &str) -> Result<LiquidNetwork, anyhow::Error> {
430        match value.to_lowercase().as_str() {
431            "mainnet" => Ok(LiquidNetwork::Mainnet),
432            "testnet" => Ok(LiquidNetwork::Testnet),
433            "regtest" => Ok(LiquidNetwork::Regtest),
434            _ => Err(anyhow!("Invalid network")),
435        }
436    }
437}
438
439impl From<LiquidNetwork> for Network {
440    fn from(value: LiquidNetwork) -> Self {
441        match value {
442            LiquidNetwork::Mainnet => Self::Bitcoin,
443            LiquidNetwork::Testnet => Self::Testnet,
444            LiquidNetwork::Regtest => Self::Regtest,
445        }
446    }
447}
448
449impl From<LiquidNetwork> for sdk_common::bitcoin::Network {
450    fn from(value: LiquidNetwork) -> Self {
451        match value {
452            LiquidNetwork::Mainnet => Self::Bitcoin,
453            LiquidNetwork::Testnet => Self::Testnet,
454            LiquidNetwork::Regtest => Self::Regtest,
455        }
456    }
457}
458
459impl From<LiquidNetwork> for boltz_client::bitcoin::Network {
460    fn from(value: LiquidNetwork) -> Self {
461        match value {
462            LiquidNetwork::Mainnet => Self::Bitcoin,
463            LiquidNetwork::Testnet => Self::Testnet,
464            LiquidNetwork::Regtest => Self::Regtest,
465        }
466    }
467}
468
469/// Trait that can be used to react to various [SdkEvent]s emitted by the SDK.
470pub trait EventListener: MaybeSend + MaybeSync {
471    fn on_event(&self, e: SdkEvent);
472}
473
474/// Event emitted by the SDK. Add an [EventListener] by calling [crate::sdk::LiquidSdk::add_event_listener]
475/// to listen for emitted events.
476#[derive(Clone, Debug, PartialEq)]
477pub enum SdkEvent {
478    PaymentFailed {
479        details: Payment,
480    },
481    PaymentPending {
482        details: Payment,
483    },
484    PaymentRefundable {
485        details: Payment,
486    },
487    PaymentRefunded {
488        details: Payment,
489    },
490    PaymentRefundPending {
491        details: Payment,
492    },
493    PaymentSucceeded {
494        details: Payment,
495    },
496    PaymentWaitingConfirmation {
497        details: Payment,
498    },
499    PaymentWaitingFeeAcceptance {
500        details: Payment,
501    },
502    /// Synced with mempool and onchain data
503    Synced,
504    /// Synced with real-time data sync
505    DataSynced {
506        /// Indicates new data was pulled from other instances.
507        did_pull_new_records: bool,
508    },
509}
510
511#[derive(thiserror::Error, Debug)]
512pub enum SignerError {
513    #[error("Signer error: {err}")]
514    Generic { err: String },
515}
516
517impl From<anyhow::Error> for SignerError {
518    fn from(err: anyhow::Error) -> Self {
519        SignerError::Generic {
520            err: err.to_string(),
521        }
522    }
523}
524
525impl From<bip32::Error> for SignerError {
526    fn from(err: bip32::Error) -> Self {
527        SignerError::Generic {
528            err: err.to_string(),
529        }
530    }
531}
532
533/// A trait that can be used to sign messages and verify signatures.
534/// The sdk user can implement this trait to use their own signer.
535pub trait Signer: MaybeSend + MaybeSync {
536    /// The master xpub encoded as 78 bytes length as defined in bip32 specification.
537    /// For reference: <https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#user-content-Serialization_format>
538    fn xpub(&self) -> Result<Vec<u8>, SignerError>;
539
540    /// The derived xpub encoded as 78 bytes length as defined in bip32 specification.
541    /// The derivation path is a string represents the shorter notation of the key tree to derive. For example:
542    /// m/49'/1'/0'/0/0
543    /// m/48'/1'/0'/0/0
544    /// For reference: <https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#user-content-The_key_tree>
545    fn derive_xpub(&self, derivation_path: String) -> Result<Vec<u8>, SignerError>;
546
547    /// Sign an ECDSA message using the private key derived from the given derivation path
548    fn sign_ecdsa(&self, msg: Vec<u8>, derivation_path: String) -> Result<Vec<u8>, SignerError>;
549
550    /// Sign an ECDSA message using the private key derived from the master key
551    fn sign_ecdsa_recoverable(&self, msg: Vec<u8>) -> Result<Vec<u8>, SignerError>;
552
553    /// Return the master blinding key for SLIP77: <https://github.com/satoshilabs/slips/blob/master/slip-0077.md>
554    fn slip77_master_blinding_key(&self) -> Result<Vec<u8>, SignerError>;
555
556    /// HMAC-SHA256 using the private key derived from the given derivation path
557    /// This is used to calculate the linking key of lnurl-auth specification: <https://github.com/lnurl/luds/blob/luds/05.md>
558    fn hmac_sha256(&self, msg: Vec<u8>, derivation_path: String) -> Result<Vec<u8>, SignerError>;
559
560    /// Encrypts a message using (ECIES)[ecies::encrypt]
561    fn ecies_encrypt(&self, msg: Vec<u8>) -> Result<Vec<u8>, SignerError>;
562
563    /// Decrypts a message using (ECIES)[ecies::decrypt]
564    fn ecies_decrypt(&self, msg: Vec<u8>) -> Result<Vec<u8>, SignerError>;
565}
566
567/// An argument when calling [crate::sdk::LiquidSdk::connect].
568/// The resquest takes either a `mnemonic` and `passphrase`, or a `seed`.
569pub struct ConnectRequest {
570    /// The SDK [Config]
571    pub config: Config,
572    /// The optional Liquid wallet mnemonic
573    pub mnemonic: Option<String>,
574    /// The optional passphrase for the mnemonic
575    pub passphrase: Option<String>,
576    /// The optional Liquid wallet seed
577    pub seed: Option<Vec<u8>>,
578}
579
580pub struct ConnectWithSignerRequest {
581    pub config: Config,
582}
583
584/// A reserved address. Once an address is reserved, it can only be
585/// reallocated to another payment after the block height expiration.
586#[derive(Clone, Debug)]
587pub(crate) struct ReservedAddress {
588    /// The address that is reserved
589    pub(crate) address: String,
590    /// The block height that the address is reserved until
591    pub(crate) expiry_block_height: u32,
592}
593
594/// The send/receive methods supported by the SDK
595#[derive(Clone, Debug, Serialize)]
596pub enum PaymentMethod {
597    #[deprecated(since = "0.8.1", note = "Use `Bolt11Invoice` instead")]
598    Lightning,
599    Bolt11Invoice,
600    Bolt12Offer,
601    BitcoinAddress,
602    LiquidAddress,
603}
604
605#[derive(Debug, Serialize, Clone)]
606pub enum ReceiveAmount {
607    /// The amount in satoshi that should be paid
608    Bitcoin { payer_amount_sat: u64 },
609
610    /// The amount of an asset that should be paid
611    Asset {
612        asset_id: String,
613        payer_amount: Option<f64>,
614    },
615}
616
617/// An argument when calling [crate::sdk::LiquidSdk::prepare_receive_payment].
618#[derive(Debug, Serialize)]
619pub struct PrepareReceiveRequest {
620    pub payment_method: PaymentMethod,
621    /// The amount to be paid in either Bitcoin or another asset
622    pub amount: Option<ReceiveAmount>,
623}
624
625/// Returned when calling [crate::sdk::LiquidSdk::prepare_receive_payment].
626#[derive(Debug, Serialize, Clone)]
627pub struct PrepareReceiveResponse {
628    pub payment_method: PaymentMethod,
629    /// Generally represents the total fees that would be paid to send or receive this payment.
630    ///
631    /// In case of Zero-Amount Receive Chain swaps, the swapper service fee (`swapper_feerate` times
632    /// the amount) is paid in addition to `fees_sat`. The swapper service feerate is already known
633    /// in the beginning, but the exact swapper service fee will only be known when the
634    /// `payer_amount_sat` is known.
635    ///
636    /// In all other types of swaps, the swapper service fee is included in `fees_sat`.
637    pub fees_sat: u64,
638    /// The amount to be paid in either Bitcoin or another asset
639    pub amount: Option<ReceiveAmount>,
640    /// The minimum amount the payer can send for this swap to succeed.
641    ///
642    /// When the method is [PaymentMethod::LiquidAddress], this is empty.
643    pub min_payer_amount_sat: Option<u64>,
644    /// The maximum amount the payer can send for this swap to succeed.
645    ///
646    /// When the method is [PaymentMethod::LiquidAddress], this is empty.
647    pub max_payer_amount_sat: Option<u64>,
648    /// The percentage of the sent amount that will count towards the service fee.
649    ///
650    /// When the method is [PaymentMethod::LiquidAddress], this is empty.
651    pub swapper_feerate: Option<f64>,
652}
653
654/// An argument when calling [crate::sdk::LiquidSdk::receive_payment].
655#[derive(Debug, Serialize)]
656pub struct ReceivePaymentRequest {
657    pub prepare_response: PrepareReceiveResponse,
658    /// The description for this payment request
659    pub description: Option<String>,
660    /// If set to true, then the hash of the description will be used
661    pub use_description_hash: Option<bool>,
662    /// An optional payer note, typically included in a LNURL-Pay request
663    pub payer_note: Option<String>,
664}
665
666/// Returned when calling [crate::sdk::LiquidSdk::receive_payment].
667#[derive(Debug, Serialize)]
668pub struct ReceivePaymentResponse {
669    /// Either a BIP21 URI (Liquid or Bitcoin), a Liquid address
670    /// or an invoice, depending on the [PrepareReceiveResponse] parameters
671    pub destination: String,
672    /// The Liquid block height at which the payment will expire
673    pub liquid_expiration_blockheight: Option<u32>,
674    /// The Bitcoin block height at which the payment will expire
675    pub bitcoin_expiration_blockheight: Option<u32>,
676}
677
678/// An argument when calling [crate::sdk::LiquidSdk::create_bolt12_invoice].
679#[derive(Debug, Serialize)]
680pub struct CreateBolt12InvoiceRequest {
681    /// The BOLT12 offer
682    pub offer: String,
683    /// The invoice request created from the offer
684    pub invoice_request: String,
685}
686
687/// Returned when calling [crate::sdk::LiquidSdk::create_bolt12_invoice].
688#[derive(Debug, Serialize, Clone)]
689pub struct CreateBolt12InvoiceResponse {
690    /// The BOLT12 invoice
691    pub invoice: String,
692}
693
694/// The minimum and maximum in satoshis of a Lightning or onchain payment.
695#[derive(Debug, Serialize)]
696pub struct Limits {
697    pub min_sat: u64,
698    pub max_sat: u64,
699    pub max_zero_conf_sat: u64,
700}
701
702/// Returned when calling [crate::sdk::LiquidSdk::fetch_lightning_limits].
703#[derive(Debug, Serialize)]
704pub struct LightningPaymentLimitsResponse {
705    /// Amount limits for a Send Payment to be valid
706    pub send: Limits,
707    /// Amount limits for a Receive Payment to be valid
708    pub receive: Limits,
709}
710
711/// Returned when calling [crate::sdk::LiquidSdk::fetch_onchain_limits].
712#[derive(Debug, Serialize)]
713pub struct OnchainPaymentLimitsResponse {
714    /// Amount limits for a Send Onchain Payment to be valid
715    pub send: Limits,
716    /// Amount limits for a Receive Onchain Payment to be valid
717    pub receive: Limits,
718}
719
720/// An argument when calling [crate::sdk::LiquidSdk::prepare_send_payment].
721#[derive(Debug, Serialize, Clone)]
722pub struct PrepareSendRequest {
723    /// The destination we intend to pay to.
724    /// Supports BIP21 URIs, BOLT11 invoices, BOLT12 offers and Liquid addresses
725    pub destination: String,
726    /// Should only be set when paying directly onchain or to a BIP21 URI
727    /// where no amount is specified, or when the caller wishes to drain
728    pub amount: Option<PayAmount>,
729}
730
731/// Specifies the supported destinations which can be payed by the SDK
732#[derive(Clone, Debug, Serialize)]
733pub enum SendDestination {
734    LiquidAddress {
735        address_data: liquid::LiquidAddressData,
736        /// A BIP353 address, in case one was used to resolve this Liquid address
737        bip353_address: Option<String>,
738    },
739    Bolt11 {
740        invoice: LNInvoice,
741        /// A BIP353 address, in case one was used to resolve this BOLT11
742        bip353_address: Option<String>,
743    },
744    Bolt12 {
745        offer: LNOffer,
746        receiver_amount_sat: u64,
747        /// A BIP353 address, in case one was used to resolve this BOLT12
748        bip353_address: Option<String>,
749    },
750}
751
752/// Returned when calling [crate::sdk::LiquidSdk::prepare_send_payment].
753#[derive(Debug, Serialize, Clone)]
754pub struct PrepareSendResponse {
755    pub destination: SendDestination,
756    /// The optional amount to be sent in either Bitcoin or another asset
757    pub amount: Option<PayAmount>,
758    /// The optional estimated fee in satoshi. Is set when there is Bitcoin available
759    /// to pay fees. When not set, there are asset fees available to pay fees.
760    pub fees_sat: Option<u64>,
761    /// The optional estimated fee in the asset. Is set when [PayAmount::Asset::estimate_asset_fees]
762    /// is set to `true`, the Payjoin service accepts this asset to pay fees and there
763    /// are funds available in this asset to pay fees.
764    pub estimated_asset_fees: Option<f64>,
765    /// The amount of funds required (in satoshi) to execute a SideSwap payment, excluding fees.
766    /// Only present when [PayAmount::Asset::pay_with_bitcoin] is set to `true`.
767    pub exchange_amount_sat: Option<u64>,
768}
769
770/// An argument when calling [crate::sdk::LiquidSdk::send_payment].
771#[derive(Debug, Serialize)]
772pub struct SendPaymentRequest {
773    pub prepare_response: PrepareSendResponse,
774    /// If set to true, the payment will be sent using the SideSwap payjoin service
775    pub use_asset_fees: Option<bool>,
776    /// An optional payer note, which is to be included in a BOLT12 invoice request
777    pub payer_note: Option<String>,
778}
779
780/// Returned when calling [crate::sdk::LiquidSdk::send_payment].
781#[derive(Debug, Serialize)]
782pub struct SendPaymentResponse {
783    pub payment: Payment,
784}
785
786pub(crate) struct SendPaymentViaSwapRequest {
787    pub(crate) invoice: String,
788    pub(crate) bolt12_offer: Option<String>,
789    pub(crate) payment_hash: String,
790    pub(crate) description: Option<String>,
791    pub(crate) receiver_amount_sat: u64,
792    pub(crate) fees_sat: u64,
793}
794
795pub(crate) struct PayLiquidRequest {
796    pub address_data: LiquidAddressData,
797    pub to_asset: String,
798    pub receiver_amount_sat: u64,
799    pub asset_pay_fees: bool,
800    pub fees_sat: Option<u64>,
801}
802
803pub(crate) struct PaySideSwapRequest {
804    pub address_data: LiquidAddressData,
805    pub to_asset: String,
806    pub receiver_amount_sat: u64,
807    pub fees_sat: u64,
808    pub amount: Option<PayAmount>,
809}
810
811/// Used to specify the amount to sent or to send all funds.
812#[derive(Debug, Serialize, Clone)]
813pub enum PayAmount {
814    /// The amount in satoshi that will be received
815    Bitcoin { receiver_amount_sat: u64 },
816
817    /// The amount of an asset that will be received
818    Asset {
819        /// The asset id specifying which asset will be sent
820        to_asset: String,
821        receiver_amount: f64,
822        estimate_asset_fees: Option<bool>,
823        /// The asset id whose balance we want to send funds with.
824        /// Defaults to the value provided for [PayAmount::Asset::to_asset]
825        from_asset: Option<String>,
826    },
827
828    /// Indicates that all available Bitcoin funds should be sent
829    Drain,
830}
831
832impl PayAmount {
833    pub(crate) fn is_sideswap_payment(&self) -> bool {
834        match self {
835            PayAmount::Asset {
836                to_asset,
837                from_asset,
838                ..
839            } => from_asset.as_ref().is_some_and(|asset| asset != to_asset),
840            _ => false,
841        }
842    }
843}
844
845/// An argument when calling [crate::sdk::LiquidSdk::prepare_pay_onchain].
846#[derive(Debug, Serialize, Clone)]
847pub struct PreparePayOnchainRequest {
848    /// The amount to send
849    pub amount: PayAmount,
850    /// The optional fee rate of the Bitcoin claim transaction in sat/vB. Defaults to the swapper estimated claim fee.
851    pub fee_rate_sat_per_vbyte: Option<u32>,
852}
853
854/// Returned when calling [crate::sdk::LiquidSdk::prepare_pay_onchain].
855#[derive(Debug, Serialize, Clone)]
856pub struct PreparePayOnchainResponse {
857    pub receiver_amount_sat: u64,
858    pub claim_fees_sat: u64,
859    pub total_fees_sat: u64,
860}
861
862/// An argument when calling [crate::sdk::LiquidSdk::pay_onchain].
863#[derive(Debug, Serialize)]
864pub struct PayOnchainRequest {
865    pub address: String,
866    pub prepare_response: PreparePayOnchainResponse,
867}
868
869/// An argument when calling [crate::sdk::LiquidSdk::prepare_refund].
870#[derive(Debug, Serialize)]
871pub struct PrepareRefundRequest {
872    /// The address where the swap funds are locked up
873    pub swap_address: String,
874    /// The address to refund the swap funds to
875    pub refund_address: String,
876    /// The fee rate in sat/vB for the refund transaction
877    pub fee_rate_sat_per_vbyte: u32,
878}
879
880/// Returned when calling [crate::sdk::LiquidSdk::prepare_refund].
881#[derive(Debug, Serialize)]
882pub struct PrepareRefundResponse {
883    pub tx_vsize: u32,
884    pub tx_fee_sat: u64,
885    /// The txid of the last broadcasted refund tx, if any
886    pub last_refund_tx_id: Option<String>,
887}
888
889/// An argument when calling [crate::sdk::LiquidSdk::refund].
890#[derive(Debug, Serialize)]
891pub struct RefundRequest {
892    /// The address where the swap funds are locked up
893    pub swap_address: String,
894    /// The address to refund the swap funds to
895    pub refund_address: String,
896    /// The fee rate in sat/vB for the refund transaction
897    pub fee_rate_sat_per_vbyte: u32,
898}
899
900/// Returned when calling [crate::sdk::LiquidSdk::refund].
901#[derive(Debug, Serialize)]
902pub struct RefundResponse {
903    pub refund_tx_id: String,
904}
905
906/// An asset balance to denote the balance for each asset.
907#[derive(Clone, Debug, Default, Serialize, Deserialize)]
908pub struct AssetBalance {
909    pub asset_id: String,
910    pub balance_sat: u64,
911    pub name: Option<String>,
912    pub ticker: Option<String>,
913    pub balance: Option<f64>,
914}
915
916#[derive(Debug, Serialize, Deserialize, Default)]
917pub struct BlockchainInfo {
918    pub liquid_tip: u32,
919    pub bitcoin_tip: u32,
920}
921
922#[derive(Copy, Clone)]
923pub(crate) struct ChainTips {
924    pub liquid_tip: u32,
925    pub bitcoin_tip: Option<u32>,
926}
927
928#[derive(Debug, Serialize, Deserialize)]
929pub struct WalletInfo {
930    /// Usable balance. This is the confirmed onchain balance minus `pending_send_sat`.
931    pub balance_sat: u64,
932    /// Amount that is being used for ongoing Send swaps
933    pub pending_send_sat: u64,
934    /// Incoming amount that is pending from ongoing Receive swaps
935    pub pending_receive_sat: u64,
936    /// The wallet's fingerprint. It is used to build the working directory in [Config::get_wallet_dir].
937    pub fingerprint: String,
938    /// The wallet's pubkey. Used to verify signed messages.
939    pub pubkey: String,
940    /// Asset balances of non Liquid Bitcoin assets
941    #[serde(default)]
942    pub asset_balances: Vec<AssetBalance>,
943}
944
945impl WalletInfo {
946    pub(crate) fn validate_sufficient_funds(
947        &self,
948        network: LiquidNetwork,
949        amount_sat: u64,
950        fees_sat: Option<u64>,
951        asset_id: &str,
952    ) -> Result<(), PaymentError> {
953        let fees_sat = fees_sat.unwrap_or(0);
954        if asset_id.eq(&utils::lbtc_asset_id(network).to_string()) {
955            ensure_sdk!(
956                amount_sat + fees_sat <= self.balance_sat,
957                PaymentError::InsufficientFunds
958            );
959        } else {
960            match self
961                .asset_balances
962                .iter()
963                .find(|ab| ab.asset_id.eq(asset_id))
964            {
965                Some(asset_balance) => ensure_sdk!(
966                    amount_sat <= asset_balance.balance_sat && fees_sat <= self.balance_sat,
967                    PaymentError::InsufficientFunds
968                ),
969                None => return Err(PaymentError::InsufficientFunds),
970            }
971        }
972        Ok(())
973    }
974}
975
976/// Returned when calling [crate::sdk::LiquidSdk::get_info].
977#[derive(Debug, Serialize, Deserialize)]
978pub struct GetInfoResponse {
979    /// The wallet information, such as the balance, fingerprint and public key
980    pub wallet_info: WalletInfo,
981    /// The latest synced blockchain information, such as the Liquid/Bitcoin tips
982    #[serde(default)]
983    pub blockchain_info: BlockchainInfo,
984}
985
986/// An argument when calling [crate::sdk::LiquidSdk::sign_message].
987#[derive(Clone, Debug, PartialEq)]
988pub struct SignMessageRequest {
989    pub message: String,
990}
991
992/// Returned when calling [crate::sdk::LiquidSdk::sign_message].
993#[derive(Clone, Debug, PartialEq)]
994pub struct SignMessageResponse {
995    pub signature: String,
996}
997
998/// An argument when calling [crate::sdk::LiquidSdk::check_message].
999#[derive(Clone, Debug, PartialEq)]
1000pub struct CheckMessageRequest {
1001    /// The message that was signed.
1002    pub message: String,
1003    /// The public key of the node that signed the message.
1004    pub pubkey: String,
1005    /// The zbase encoded signature to verify.
1006    pub signature: String,
1007}
1008
1009/// Returned when calling [crate::sdk::LiquidSdk::check_message].
1010#[derive(Clone, Debug, PartialEq)]
1011pub struct CheckMessageResponse {
1012    /// Boolean value indicating whether the signature covers the message and
1013    /// was signed by the given pubkey.
1014    pub is_valid: bool,
1015}
1016
1017/// An argument when calling [crate::sdk::LiquidSdk::backup].
1018#[derive(Debug, Serialize)]
1019pub struct BackupRequest {
1020    /// Path to the backup.
1021    ///
1022    /// If not set, it defaults to `backup.sql` for mainnet, `backup-testnet.sql` for testnet,
1023    /// and `backup-regtest.sql` for regtest.
1024    ///
1025    /// The file will be saved in [ConnectRequest]'s `data_dir`.
1026    pub backup_path: Option<String>,
1027}
1028
1029/// An argument when calling [crate::sdk::LiquidSdk::restore].
1030#[derive(Debug, Serialize)]
1031pub struct RestoreRequest {
1032    pub backup_path: Option<String>,
1033}
1034
1035/// An argument when calling [crate::sdk::LiquidSdk::list_payments].
1036#[derive(Default)]
1037pub struct ListPaymentsRequest {
1038    pub filters: Option<Vec<PaymentType>>,
1039    pub states: Option<Vec<PaymentState>>,
1040    /// Epoch time, in seconds
1041    pub from_timestamp: Option<i64>,
1042    /// Epoch time, in seconds
1043    pub to_timestamp: Option<i64>,
1044    pub offset: Option<u32>,
1045    pub limit: Option<u32>,
1046    pub details: Option<ListPaymentDetails>,
1047    pub sort_ascending: Option<bool>,
1048}
1049
1050/// An argument of [ListPaymentsRequest] when calling [crate::sdk::LiquidSdk::list_payments].
1051#[derive(Debug, Serialize)]
1052pub enum ListPaymentDetails {
1053    /// A Liquid payment
1054    Liquid {
1055        /// Optional asset id
1056        asset_id: Option<String>,
1057        /// Optional BIP21 URI or address
1058        destination: Option<String>,
1059    },
1060
1061    /// A Bitcoin payment
1062    Bitcoin {
1063        /// Optional address
1064        address: Option<String>,
1065    },
1066}
1067
1068/// An argument when calling [crate::sdk::LiquidSdk::get_payment].
1069#[derive(Debug, Serialize)]
1070pub enum GetPaymentRequest {
1071    /// The payment hash of a Lightning payment
1072    PaymentHash { payment_hash: String },
1073    /// A swap id or its SHA256 hash
1074    SwapId { swap_id: String },
1075}
1076
1077/// Trait that can be used to react to new blocks from Bitcoin and Liquid chains
1078#[sdk_macros::async_trait]
1079pub(crate) trait BlockListener: MaybeSend + MaybeSync {
1080    async fn on_bitcoin_block(&self, height: u32);
1081    async fn on_liquid_block(&self, height: u32);
1082}
1083
1084// A swap enum variant
1085#[derive(Clone, Debug)]
1086pub enum Swap {
1087    Chain(ChainSwap),
1088    Send(SendSwap),
1089    Receive(ReceiveSwap),
1090}
1091impl Swap {
1092    pub(crate) fn id(&self) -> String {
1093        match &self {
1094            Swap::Chain(ChainSwap { id, .. })
1095            | Swap::Send(SendSwap { id, .. })
1096            | Swap::Receive(ReceiveSwap { id, .. }) => id.clone(),
1097        }
1098    }
1099
1100    pub(crate) fn version(&self) -> u64 {
1101        match self {
1102            Swap::Chain(ChainSwap { metadata, .. })
1103            | Swap::Send(SendSwap { metadata, .. })
1104            | Swap::Receive(ReceiveSwap { metadata, .. }) => metadata.version,
1105        }
1106    }
1107
1108    pub(crate) fn set_version(&mut self, version: u64) {
1109        match self {
1110            Swap::Chain(chain_swap) => {
1111                chain_swap.metadata.version = version;
1112            }
1113            Swap::Send(send_swap) => {
1114                send_swap.metadata.version = version;
1115            }
1116            Swap::Receive(receive_swap) => {
1117                receive_swap.metadata.version = version;
1118            }
1119        }
1120    }
1121
1122    pub(crate) fn last_updated_at(&self) -> u32 {
1123        match self {
1124            Swap::Chain(ChainSwap { metadata, .. })
1125            | Swap::Send(SendSwap { metadata, .. })
1126            | Swap::Receive(ReceiveSwap { metadata, .. }) => metadata.last_updated_at,
1127        }
1128    }
1129}
1130impl From<ChainSwap> for Swap {
1131    fn from(swap: ChainSwap) -> Self {
1132        Self::Chain(swap)
1133    }
1134}
1135impl From<SendSwap> for Swap {
1136    fn from(swap: SendSwap) -> Self {
1137        Self::Send(swap)
1138    }
1139}
1140impl From<ReceiveSwap> for Swap {
1141    fn from(swap: ReceiveSwap) -> Self {
1142        Self::Receive(swap)
1143    }
1144}
1145
1146#[derive(Clone, Debug)]
1147pub(crate) enum SwapScriptV2 {
1148    Bitcoin(BtcSwapScript),
1149    Liquid(LBtcSwapScript),
1150}
1151impl SwapScriptV2 {
1152    pub(crate) fn as_bitcoin_script(&self) -> Result<BtcSwapScript> {
1153        match self {
1154            SwapScriptV2::Bitcoin(script) => Ok(script.clone()),
1155            _ => Err(anyhow!("Invalid chain")),
1156        }
1157    }
1158
1159    pub(crate) fn as_liquid_script(&self) -> Result<LBtcSwapScript> {
1160        match self {
1161            SwapScriptV2::Liquid(script) => Ok(script.clone()),
1162            _ => Err(anyhow!("Invalid chain")),
1163        }
1164    }
1165}
1166
1167#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
1168pub enum Direction {
1169    Incoming = 0,
1170    Outgoing = 1,
1171}
1172impl ToSql for Direction {
1173    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1174        Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1175    }
1176}
1177impl FromSql for Direction {
1178    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1179        match value {
1180            ValueRef::Integer(i) => match i as u8 {
1181                0 => Ok(Direction::Incoming),
1182                1 => Ok(Direction::Outgoing),
1183                _ => Err(FromSqlError::OutOfRange(i)),
1184            },
1185            _ => Err(FromSqlError::InvalidType),
1186        }
1187    }
1188}
1189
1190#[derive(Clone, Debug, Default)]
1191pub(crate) struct SwapMetadata {
1192    /// Version used for optimistic concurrency control within local db
1193    pub(crate) version: u64,
1194    pub(crate) last_updated_at: u32,
1195    pub(crate) is_local: bool,
1196}
1197
1198/// A chain swap
1199///
1200/// See <https://docs.boltz.exchange/v/api/lifecycle#chain-swaps>
1201#[derive(Clone, Debug, Derivative)]
1202#[derivative(PartialEq)]
1203pub struct ChainSwap {
1204    pub(crate) id: String,
1205    pub(crate) direction: Direction,
1206    /// Always set for Outgoing Chain Swaps. It's the destination Bitcoin address
1207    /// Only set for Incoming Chain Swaps when a claim is broadcasted. It's a local Liquid wallet address
1208    pub(crate) claim_address: Option<String>,
1209    pub(crate) lockup_address: String,
1210    /// The Liquid refund address is only set for Outgoing Chain Swaps
1211    pub(crate) refund_address: Option<String>,
1212    /// The block height at which the swap will expire (origin chain)
1213    pub(crate) timeout_block_height: u32,
1214    /// The block height at which the claim will expire (destination chain)
1215    pub(crate) claim_timeout_block_height: u32,
1216    pub(crate) preimage: String,
1217    pub(crate) description: Option<String>,
1218    /// Payer amount defined at swap creation
1219    pub(crate) payer_amount_sat: u64,
1220    /// The actual payer amount as seen on the user lockup tx. Might differ from `payer_amount_sat`
1221    /// in the case of an over/underpayment
1222    pub(crate) actual_payer_amount_sat: Option<u64>,
1223    /// Receiver amount defined at swap creation
1224    pub(crate) receiver_amount_sat: u64,
1225    /// The final receiver amount, in case of an amountless swap for which fees have been accepted
1226    pub(crate) accepted_receiver_amount_sat: Option<u64>,
1227    pub(crate) claim_fees_sat: u64,
1228    /// The [ChainPair] chosen on swap creation
1229    pub(crate) pair_fees_json: String,
1230    pub(crate) accept_zero_conf: bool,
1231    /// JSON representation of [crate::persist::chain::InternalCreateChainResponse]
1232    pub(crate) create_response_json: String,
1233    /// Persisted only when the server lockup tx is successfully broadcast
1234    pub(crate) server_lockup_tx_id: Option<String>,
1235    /// Persisted only when the user lockup tx is successfully broadcast
1236    pub(crate) user_lockup_tx_id: Option<String>,
1237    /// Persisted as soon as a claim tx is broadcast
1238    pub(crate) claim_tx_id: Option<String>,
1239    /// Persisted as soon as a refund tx is broadcast
1240    pub(crate) refund_tx_id: Option<String>,
1241    pub(crate) created_at: u32,
1242    pub(crate) state: PaymentState,
1243    pub(crate) claim_private_key: String,
1244    pub(crate) refund_private_key: String,
1245    pub(crate) auto_accepted_fees: bool,
1246    /// Swap metadata that is only valid when reading one from the local database
1247    #[derivative(PartialEq = "ignore")]
1248    pub(crate) metadata: SwapMetadata,
1249}
1250impl ChainSwap {
1251    pub(crate) fn get_claim_keypair(&self) -> SdkResult<Keypair> {
1252        utils::decode_keypair(&self.claim_private_key)
1253    }
1254
1255    pub(crate) fn get_refund_keypair(&self) -> SdkResult<Keypair> {
1256        utils::decode_keypair(&self.refund_private_key)
1257    }
1258
1259    pub(crate) fn get_boltz_create_response(&self) -> Result<CreateChainResponse> {
1260        let internal_create_response: crate::persist::chain::InternalCreateChainResponse =
1261            serde_json::from_str(&self.create_response_json).map_err(|e| {
1262                anyhow!("Failed to deserialize InternalCreateSubmarineResponse: {e:?}")
1263            })?;
1264
1265        Ok(CreateChainResponse {
1266            id: self.id.clone(),
1267            claim_details: internal_create_response.claim_details,
1268            lockup_details: internal_create_response.lockup_details,
1269        })
1270    }
1271
1272    pub(crate) fn get_boltz_pair(&self) -> Result<ChainPair> {
1273        let pair: ChainPair = serde_json::from_str(&self.pair_fees_json)
1274            .map_err(|e| anyhow!("Failed to deserialize ChainPair: {e:?}"))?;
1275
1276        Ok(pair)
1277    }
1278
1279    pub(crate) fn get_claim_swap_script(&self) -> SdkResult<SwapScriptV2> {
1280        let chain_swap_details = self.get_boltz_create_response()?.claim_details;
1281        let our_pubkey = self.get_claim_keypair()?.public_key();
1282        let swap_script = match self.direction {
1283            Direction::Incoming => SwapScriptV2::Liquid(LBtcSwapScript::chain_from_swap_resp(
1284                Side::Claim,
1285                chain_swap_details,
1286                our_pubkey.into(),
1287            )?),
1288            Direction::Outgoing => SwapScriptV2::Bitcoin(BtcSwapScript::chain_from_swap_resp(
1289                Side::Claim,
1290                chain_swap_details,
1291                our_pubkey.into(),
1292            )?),
1293        };
1294        Ok(swap_script)
1295    }
1296
1297    pub(crate) fn get_lockup_swap_script(&self) -> SdkResult<SwapScriptV2> {
1298        let chain_swap_details = self.get_boltz_create_response()?.lockup_details;
1299        let our_pubkey = self.get_refund_keypair()?.public_key();
1300        let swap_script = match self.direction {
1301            Direction::Incoming => SwapScriptV2::Bitcoin(BtcSwapScript::chain_from_swap_resp(
1302                Side::Lockup,
1303                chain_swap_details,
1304                our_pubkey.into(),
1305            )?),
1306            Direction::Outgoing => SwapScriptV2::Liquid(LBtcSwapScript::chain_from_swap_resp(
1307                Side::Lockup,
1308                chain_swap_details,
1309                our_pubkey.into(),
1310            )?),
1311        };
1312        Ok(swap_script)
1313    }
1314
1315    /// Returns the lockup script pubkey for Receive Chain Swaps
1316    pub(crate) fn get_receive_lockup_swap_script_pubkey(
1317        &self,
1318        network: LiquidNetwork,
1319    ) -> SdkResult<ScriptBuf> {
1320        let swap_script = self.get_lockup_swap_script()?.as_bitcoin_script()?;
1321        let script_pubkey = swap_script
1322            .to_address(network.as_bitcoin_chain())
1323            .map_err(|e| SdkError::generic(format!("Error getting script address: {e:?}")))?
1324            .script_pubkey();
1325        Ok(script_pubkey)
1326    }
1327
1328    pub(crate) fn to_refundable(&self, amount_sat: u64) -> RefundableSwap {
1329        RefundableSwap {
1330            swap_address: self.lockup_address.clone(),
1331            timestamp: self.created_at,
1332            amount_sat,
1333            last_refund_tx_id: self.refund_tx_id.clone(),
1334        }
1335    }
1336
1337    pub(crate) fn from_boltz_struct_to_json(
1338        create_response: &CreateChainResponse,
1339        expected_swap_id: &str,
1340    ) -> Result<String, PaymentError> {
1341        let internal_create_response =
1342            crate::persist::chain::InternalCreateChainResponse::try_convert_from_boltz(
1343                create_response,
1344                expected_swap_id,
1345            )?;
1346
1347        let create_response_json =
1348            serde_json::to_string(&internal_create_response).map_err(|e| {
1349                PaymentError::Generic {
1350                    err: format!("Failed to serialize InternalCreateChainResponse: {e:?}"),
1351                }
1352            })?;
1353
1354        Ok(create_response_json)
1355    }
1356
1357    pub(crate) fn is_waiting_fee_acceptance(&self) -> bool {
1358        self.payer_amount_sat == 0 && self.accepted_receiver_amount_sat.is_none()
1359    }
1360}
1361
1362#[derive(Clone, Debug, Default)]
1363pub(crate) struct ChainSwapUpdate {
1364    pub(crate) swap_id: String,
1365    pub(crate) to_state: PaymentState,
1366    pub(crate) server_lockup_tx_id: Option<String>,
1367    pub(crate) user_lockup_tx_id: Option<String>,
1368    pub(crate) claim_address: Option<String>,
1369    pub(crate) claim_tx_id: Option<String>,
1370    pub(crate) refund_tx_id: Option<String>,
1371}
1372
1373/// A submarine swap, used for Send
1374#[derive(Clone, Debug, Derivative)]
1375#[derivative(PartialEq)]
1376pub struct SendSwap {
1377    pub(crate) id: String,
1378    /// Bolt11 or Bolt12 invoice. This is determined by whether `bolt12_offer` is set or not.
1379    pub(crate) invoice: String,
1380    /// The bolt12 offer, if this swap sends to a Bolt12 offer
1381    pub(crate) bolt12_offer: Option<String>,
1382    pub(crate) payment_hash: Option<String>,
1383    pub(crate) destination_pubkey: Option<String>,
1384    pub(crate) description: Option<String>,
1385    pub(crate) preimage: Option<String>,
1386    pub(crate) payer_amount_sat: u64,
1387    pub(crate) receiver_amount_sat: u64,
1388    /// The [SubmarinePair] chosen on swap creation
1389    pub(crate) pair_fees_json: String,
1390    /// JSON representation of [crate::persist::send::InternalCreateSubmarineResponse]
1391    pub(crate) create_response_json: String,
1392    /// Persisted only when the lockup tx is successfully broadcast
1393    pub(crate) lockup_tx_id: Option<String>,
1394    /// Persisted only when a refund tx is needed
1395    pub(crate) refund_address: Option<String>,
1396    /// Persisted after the refund tx is broadcast
1397    pub(crate) refund_tx_id: Option<String>,
1398    pub(crate) created_at: u32,
1399    pub(crate) timeout_block_height: u64,
1400    pub(crate) state: PaymentState,
1401    pub(crate) refund_private_key: String,
1402    /// Swap metadata that is only valid when reading one from the local database
1403    #[derivative(PartialEq = "ignore")]
1404    pub(crate) metadata: SwapMetadata,
1405}
1406impl SendSwap {
1407    pub(crate) fn get_refund_keypair(&self) -> Result<Keypair, SdkError> {
1408        utils::decode_keypair(&self.refund_private_key)
1409    }
1410
1411    pub(crate) fn get_boltz_create_response(&self) -> Result<CreateSubmarineResponse> {
1412        let internal_create_response: crate::persist::send::InternalCreateSubmarineResponse =
1413            serde_json::from_str(&self.create_response_json).map_err(|e| {
1414                anyhow!("Failed to deserialize InternalCreateSubmarineResponse: {e:?}")
1415            })?;
1416
1417        let res = CreateSubmarineResponse {
1418            id: self.id.clone(),
1419            accept_zero_conf: internal_create_response.accept_zero_conf,
1420            address: internal_create_response.address.clone(),
1421            bip21: internal_create_response.bip21.clone(),
1422            claim_public_key: crate::utils::json_to_pubkey(
1423                &internal_create_response.claim_public_key,
1424            )?,
1425            expected_amount: internal_create_response.expected_amount,
1426            referral_id: internal_create_response.referral_id,
1427            swap_tree: internal_create_response.swap_tree.clone().into(),
1428            timeout_block_height: internal_create_response.timeout_block_height,
1429            blinding_key: internal_create_response.blinding_key.clone(),
1430        };
1431        Ok(res)
1432    }
1433
1434    pub(crate) fn get_swap_script(&self) -> Result<LBtcSwapScript, SdkError> {
1435        LBtcSwapScript::submarine_from_swap_resp(
1436            &self.get_boltz_create_response()?,
1437            self.get_refund_keypair()?.public_key().into(),
1438        )
1439        .map_err(|e| {
1440            SdkError::generic(format!(
1441                "Failed to create swap script for Send Swap {}: {e:?}",
1442                self.id
1443            ))
1444        })
1445    }
1446
1447    pub(crate) fn from_boltz_struct_to_json(
1448        create_response: &CreateSubmarineResponse,
1449        expected_swap_id: &str,
1450    ) -> Result<String, PaymentError> {
1451        let internal_create_response =
1452            crate::persist::send::InternalCreateSubmarineResponse::try_convert_from_boltz(
1453                create_response,
1454                expected_swap_id,
1455            )?;
1456
1457        let create_response_json =
1458            serde_json::to_string(&internal_create_response).map_err(|e| {
1459                PaymentError::Generic {
1460                    err: format!("Failed to serialize InternalCreateSubmarineResponse: {e:?}"),
1461                }
1462            })?;
1463
1464        Ok(create_response_json)
1465    }
1466}
1467
1468/// A reverse swap, used for Receive
1469#[derive(Clone, Debug, Derivative)]
1470#[derivative(PartialEq)]
1471pub struct ReceiveSwap {
1472    pub(crate) id: String,
1473    pub(crate) preimage: String,
1474    /// JSON representation of [crate::persist::receive::InternalCreateReverseResponse]
1475    pub(crate) create_response_json: String,
1476    pub(crate) claim_private_key: String,
1477    pub(crate) invoice: String,
1478    /// The bolt12 offer, if used to create the swap
1479    pub(crate) bolt12_offer: Option<String>,
1480    pub(crate) payment_hash: Option<String>,
1481    pub(crate) destination_pubkey: Option<String>,
1482    pub(crate) description: Option<String>,
1483    pub(crate) payer_note: Option<String>,
1484    /// The amount of the invoice
1485    pub(crate) payer_amount_sat: u64,
1486    pub(crate) receiver_amount_sat: u64,
1487    /// The [ReversePair] chosen on swap creation
1488    pub(crate) pair_fees_json: String,
1489    pub(crate) claim_fees_sat: u64,
1490    /// Persisted only when a claim tx is needed
1491    pub(crate) claim_address: Option<String>,
1492    /// Persisted after the claim tx is broadcast
1493    pub(crate) claim_tx_id: Option<String>,
1494    /// The transaction id of the swapper's tx broadcast
1495    pub(crate) lockup_tx_id: Option<String>,
1496    /// The address reserved for a magic routing hint payment
1497    pub(crate) mrh_address: String,
1498    /// Persisted only if a transaction is sent to the `mrh_address`
1499    pub(crate) mrh_tx_id: Option<String>,
1500    /// Until the lockup tx is seen in the mempool, it contains the swap creation time.
1501    /// Afterwards, it shows the lockup tx creation time.
1502    pub(crate) created_at: u32,
1503    pub(crate) timeout_block_height: u32,
1504    pub(crate) state: PaymentState,
1505    /// Swap metadata that is only valid when reading one from the local database
1506    #[derivative(PartialEq = "ignore")]
1507    pub(crate) metadata: SwapMetadata,
1508}
1509impl ReceiveSwap {
1510    pub(crate) fn get_claim_keypair(&self) -> Result<Keypair, PaymentError> {
1511        utils::decode_keypair(&self.claim_private_key).map_err(Into::into)
1512    }
1513
1514    pub(crate) fn claim_script(&self) -> Result<elements::Script> {
1515        Ok(self
1516            .get_swap_script()?
1517            .funding_addrs
1518            .ok_or(anyhow!("No funding address found"))?
1519            .script_pubkey())
1520    }
1521
1522    pub(crate) fn get_boltz_create_response(&self) -> Result<CreateReverseResponse, PaymentError> {
1523        let internal_create_response: crate::persist::receive::InternalCreateReverseResponse =
1524            serde_json::from_str(&self.create_response_json).map_err(|e| {
1525                PaymentError::Generic {
1526                    err: format!("Failed to deserialize InternalCreateReverseResponse: {e:?}"),
1527                }
1528            })?;
1529
1530        let res = CreateReverseResponse {
1531            id: self.id.clone(),
1532            invoice: Some(self.invoice.clone()),
1533            swap_tree: internal_create_response.swap_tree.clone().into(),
1534            lockup_address: internal_create_response.lockup_address.clone(),
1535            refund_public_key: crate::utils::json_to_pubkey(
1536                &internal_create_response.refund_public_key,
1537            )?,
1538            timeout_block_height: internal_create_response.timeout_block_height,
1539            onchain_amount: internal_create_response.onchain_amount,
1540            blinding_key: internal_create_response.blinding_key.clone(),
1541        };
1542        Ok(res)
1543    }
1544
1545    pub(crate) fn get_swap_script(&self) -> Result<LBtcSwapScript, PaymentError> {
1546        let keypair = self.get_claim_keypair()?;
1547        let create_response =
1548            self.get_boltz_create_response()
1549                .map_err(|e| PaymentError::Generic {
1550                    err: format!(
1551                        "Failed to create swap script for Receive Swap {}: {e:?}",
1552                        self.id
1553                    ),
1554                })?;
1555        LBtcSwapScript::reverse_from_swap_resp(&create_response, keypair.public_key().into())
1556            .map_err(|e| PaymentError::Generic {
1557                err: format!(
1558                    "Failed to create swap script for Receive Swap {}: {e:?}",
1559                    self.id
1560                ),
1561            })
1562    }
1563
1564    pub(crate) fn from_boltz_struct_to_json(
1565        create_response: &CreateReverseResponse,
1566        expected_swap_id: &str,
1567        expected_invoice: Option<&str>,
1568    ) -> Result<String, PaymentError> {
1569        let internal_create_response =
1570            crate::persist::receive::InternalCreateReverseResponse::try_convert_from_boltz(
1571                create_response,
1572                expected_swap_id,
1573                expected_invoice,
1574            )?;
1575
1576        let create_response_json =
1577            serde_json::to_string(&internal_create_response).map_err(|e| {
1578                PaymentError::Generic {
1579                    err: format!("Failed to serialize InternalCreateReverseResponse: {e:?}"),
1580                }
1581            })?;
1582
1583        Ok(create_response_json)
1584    }
1585}
1586
1587/// Returned when calling [crate::sdk::LiquidSdk::list_refundables].
1588#[derive(Clone, Debug, PartialEq, Serialize)]
1589pub struct RefundableSwap {
1590    pub swap_address: String,
1591    pub timestamp: u32,
1592    /// Amount that is refundable, from all UTXOs
1593    pub amount_sat: u64,
1594    /// The txid of the last broadcasted refund tx, if any
1595    pub last_refund_tx_id: Option<String>,
1596}
1597
1598/// A BOLT12 offer
1599#[derive(Clone, Debug, Derivative)]
1600#[derivative(PartialEq)]
1601pub(crate) struct Bolt12Offer {
1602    /// The BOLT12 offer string
1603    pub(crate) id: String,
1604    /// The description of the offer
1605    pub(crate) description: String,
1606    /// The private key used to create the offer
1607    pub(crate) private_key: String,
1608    /// The optional webhook URL where the offer invoice request will be sent
1609    pub(crate) webhook_url: Option<String>,
1610    /// The creation time of the offer
1611    pub(crate) created_at: u32,
1612}
1613impl Bolt12Offer {
1614    pub(crate) fn get_keypair(&self) -> Result<Keypair, SdkError> {
1615        utils::decode_keypair(&self.private_key)
1616    }
1617}
1618impl TryFrom<Bolt12Offer> for Offer {
1619    type Error = SdkError;
1620
1621    fn try_from(val: Bolt12Offer) -> Result<Self, Self::Error> {
1622        Offer::from_str(&val.id)
1623            .map_err(|e| SdkError::generic(format!("Failed to parse BOLT12 offer: {e:?}")))
1624    }
1625}
1626
1627/// The payment state of an individual payment.
1628#[derive(Clone, Copy, Debug, Default, EnumString, Eq, PartialEq, Serialize, Hash)]
1629#[strum(serialize_all = "lowercase")]
1630pub enum PaymentState {
1631    #[default]
1632    Created = 0,
1633
1634    /// ## Receive Swaps
1635    ///
1636    /// Covers the cases when
1637    /// - the lockup tx is seen in the mempool or
1638    /// - our claim tx is broadcast
1639    ///
1640    /// When the claim tx is broadcast, `claim_tx_id` is set in the swap.
1641    ///
1642    /// ## Send Swaps
1643    ///
1644    /// This is the status when our lockup tx was broadcast
1645    ///
1646    /// ## Chain Swaps
1647    ///
1648    /// This is the status when the user lockup tx was broadcast
1649    ///
1650    /// ## No swap data available
1651    ///
1652    /// If no associated swap is found, this indicates the underlying tx is not confirmed yet.
1653    Pending = 1,
1654
1655    /// ## Receive Swaps
1656    ///
1657    /// Covers the case when the claim tx is confirmed.
1658    ///
1659    /// ## Send and Chain Swaps
1660    ///
1661    /// This is the status when the claim tx is broadcast and we see it in the mempool.
1662    ///
1663    /// ## No swap data available
1664    ///
1665    /// If no associated swap is found, this indicates the underlying tx is confirmed.
1666    Complete = 2,
1667
1668    /// ## Receive Swaps
1669    ///
1670    /// This is the status when the swap failed for any reason and the Receive could not complete.
1671    ///
1672    /// ## Send and Chain Swaps
1673    ///
1674    /// This is the status when a swap refund was initiated and the refund tx is confirmed.
1675    Failed = 3,
1676
1677    /// ## Send and Outgoing Chain Swaps
1678    ///
1679    /// This covers the case when the swap state is still Created and the swap fails to reach the
1680    /// Pending state in time. The TimedOut state indicates the lockup tx should never be broadcast.
1681    TimedOut = 4,
1682
1683    /// ## Incoming Chain Swaps
1684    ///
1685    /// This covers the case when the swap failed for any reason and there is a user lockup tx.
1686    /// The swap in this case has to be manually refunded with a provided Bitcoin address
1687    Refundable = 5,
1688
1689    /// ## Send and Chain Swaps
1690    ///
1691    /// This is the status when a refund was initiated and/or our refund tx was broadcast
1692    ///
1693    /// When the refund tx is broadcast, `refund_tx_id` is set in the swap.
1694    RefundPending = 6,
1695
1696    /// ## Chain Swaps
1697    ///
1698    /// This is the state when the user needs to accept new fees before the payment can proceed.
1699    ///
1700    /// Use [LiquidSdk::fetch_payment_proposed_fees](crate::sdk::LiquidSdk::fetch_payment_proposed_fees)
1701    /// to find out the current fees and
1702    /// [LiquidSdk::accept_payment_proposed_fees](crate::sdk::LiquidSdk::accept_payment_proposed_fees)
1703    /// to accept them, allowing the payment to proceed.
1704    ///
1705    /// Otherwise, this payment can be immediately refunded using
1706    /// [prepare_refund](crate::sdk::LiquidSdk::prepare_refund)/[refund](crate::sdk::LiquidSdk::refund).
1707    WaitingFeeAcceptance = 7,
1708}
1709
1710impl ToSql for PaymentState {
1711    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1712        Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1713    }
1714}
1715impl FromSql for PaymentState {
1716    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1717        match value {
1718            ValueRef::Integer(i) => match i as u8 {
1719                0 => Ok(PaymentState::Created),
1720                1 => Ok(PaymentState::Pending),
1721                2 => Ok(PaymentState::Complete),
1722                3 => Ok(PaymentState::Failed),
1723                4 => Ok(PaymentState::TimedOut),
1724                5 => Ok(PaymentState::Refundable),
1725                6 => Ok(PaymentState::RefundPending),
1726                7 => Ok(PaymentState::WaitingFeeAcceptance),
1727                _ => Err(FromSqlError::OutOfRange(i)),
1728            },
1729            _ => Err(FromSqlError::InvalidType),
1730        }
1731    }
1732}
1733
1734impl PaymentState {
1735    pub(crate) fn is_refundable(&self) -> bool {
1736        matches!(
1737            self,
1738            PaymentState::Refundable
1739                | PaymentState::RefundPending
1740                | PaymentState::WaitingFeeAcceptance
1741        )
1742    }
1743}
1744
1745#[derive(Debug, Copy, Clone, Eq, EnumString, Display, Hash, PartialEq, Serialize)]
1746#[strum(serialize_all = "lowercase")]
1747pub enum PaymentType {
1748    Receive = 0,
1749    Send = 1,
1750}
1751impl From<Direction> for PaymentType {
1752    fn from(value: Direction) -> Self {
1753        match value {
1754            Direction::Incoming => Self::Receive,
1755            Direction::Outgoing => Self::Send,
1756        }
1757    }
1758}
1759impl ToSql for PaymentType {
1760    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1761        Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1762    }
1763}
1764impl FromSql for PaymentType {
1765    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1766        match value {
1767            ValueRef::Integer(i) => match i as u8 {
1768                0 => Ok(PaymentType::Receive),
1769                1 => Ok(PaymentType::Send),
1770                _ => Err(FromSqlError::OutOfRange(i)),
1771            },
1772            _ => Err(FromSqlError::InvalidType),
1773        }
1774    }
1775}
1776
1777#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
1778pub enum PaymentStatus {
1779    Pending = 0,
1780    Complete = 1,
1781}
1782impl ToSql for PaymentStatus {
1783    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1784        Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1785    }
1786}
1787impl FromSql for PaymentStatus {
1788    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1789        match value {
1790            ValueRef::Integer(i) => match i as u8 {
1791                0 => Ok(PaymentStatus::Pending),
1792                1 => Ok(PaymentStatus::Complete),
1793                _ => Err(FromSqlError::OutOfRange(i)),
1794            },
1795            _ => Err(FromSqlError::InvalidType),
1796        }
1797    }
1798}
1799
1800#[derive(Debug, Clone, Serialize)]
1801pub struct PaymentTxData {
1802    /// The tx ID of the transaction
1803    pub tx_id: String,
1804
1805    /// The point in time when the underlying tx was included in a block.
1806    pub timestamp: Option<u32>,
1807
1808    /// The onchain fees of this tx
1809    pub fees_sat: u64,
1810
1811    /// Onchain tx status
1812    pub is_confirmed: bool,
1813
1814    /// Data to use in the `blinded` param when unblinding the transaction in an explorer.
1815    /// See: <https://docs.liquid.net/docs/unblinding-transactions>
1816    pub unblinding_data: Option<String>,
1817}
1818
1819#[derive(Debug, Clone, Serialize)]
1820pub enum PaymentSwapType {
1821    Receive,
1822    Send,
1823    Chain,
1824}
1825
1826#[derive(Debug, Clone, Serialize)]
1827pub struct PaymentSwapData {
1828    pub swap_id: String,
1829
1830    pub swap_type: PaymentSwapType,
1831
1832    /// Swap creation timestamp
1833    pub created_at: u32,
1834
1835    /// The height of the block at which the swap will no longer be valid
1836    /// For chain swaps, this is the timeout on the origin chain
1837    pub expiration_blockheight: u32,
1838
1839    /// For chain swaps, this is the timeout on the destination chain
1840    pub claim_expiration_blockheight: Option<u32>,
1841
1842    pub preimage: Option<String>,
1843    pub invoice: Option<String>,
1844    pub bolt12_offer: Option<String>,
1845    pub payment_hash: Option<String>,
1846    pub destination_pubkey: Option<String>,
1847    pub description: String,
1848    pub payer_note: Option<String>,
1849
1850    /// Amount sent by the swap payer
1851    pub payer_amount_sat: u64,
1852
1853    /// Amount received by the swap receiver
1854    pub receiver_amount_sat: u64,
1855
1856    /// The swapper service fee
1857    pub swapper_fees_sat: u64,
1858
1859    pub refund_tx_id: Option<String>,
1860    pub refund_tx_amount_sat: Option<u64>,
1861
1862    /// Present only for chain swaps.
1863    /// It's the Bitcoin address that receives funds.
1864    pub bitcoin_address: Option<String>,
1865
1866    /// Payment status derived from the swap status
1867    pub status: PaymentState,
1868}
1869
1870/// Represents the payment LNURL info
1871#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
1872pub struct LnUrlInfo {
1873    pub ln_address: Option<String>,
1874    pub lnurl_pay_comment: Option<String>,
1875    pub lnurl_pay_domain: Option<String>,
1876    pub lnurl_pay_metadata: Option<String>,
1877    pub lnurl_pay_success_action: Option<SuccessActionProcessed>,
1878    pub lnurl_pay_unprocessed_success_action: Option<SuccessAction>,
1879    pub lnurl_withdraw_endpoint: Option<String>,
1880}
1881
1882/// Configuration for asset metadata. Each asset metadata item represents an entry in the
1883/// [Liquid Asset Registry](https://docs.liquid.net/docs/blockstream-liquid-asset-registry).
1884/// An example Liquid Asset in the registry would be [Tether USD](https://assets.blockstream.info/ce091c998b83c78bb71a632313ba3760f1763d9cfcffae02258ffa9865a37bd2.json>).
1885#[derive(Debug, Clone, Serialize)]
1886pub struct AssetMetadata {
1887    /// The asset id of the registered asset
1888    pub asset_id: String,
1889    /// The name of the asset
1890    pub name: String,
1891    /// The ticker of the asset
1892    pub ticker: String,
1893    /// The precision used to display the asset amount.
1894    /// For example, precision of 2 shifts the decimal 2 places left from the satoshi amount.
1895    pub precision: u8,
1896    /// The optional ID of the fiat currency used to represent the asset
1897    pub fiat_id: Option<String>,
1898}
1899
1900impl AssetMetadata {
1901    pub fn amount_to_sat(&self, amount: f64) -> u64 {
1902        (amount * (10_u64.pow(self.precision.into()) as f64)) as u64
1903    }
1904
1905    pub fn amount_from_sat(&self, amount_sat: u64) -> f64 {
1906        amount_sat as f64 / (10_u64.pow(self.precision.into()) as f64)
1907    }
1908}
1909
1910/// Represents the Liquid payment asset info. The asset info is derived from
1911/// the available [AssetMetadata] that is set in the [Config].
1912#[derive(Clone, Debug, PartialEq, Serialize)]
1913pub struct AssetInfo {
1914    /// The name of the asset
1915    pub name: String,
1916    /// The ticker of the asset
1917    pub ticker: String,
1918    /// The amount calculated from the satoshi amount of the transaction, having its
1919    /// decimal shifted to the left by the [precision](AssetMetadata::precision)
1920    pub amount: f64,
1921    /// The optional fees when paid using the asset, having its
1922    /// decimal shifted to the left by the [precision](AssetMetadata::precision)
1923    pub fees: Option<f64>,
1924}
1925
1926/// The specific details of a payment, depending on its type
1927#[derive(Debug, Clone, PartialEq, Serialize)]
1928#[allow(clippy::large_enum_variant)]
1929pub enum PaymentDetails {
1930    /// Swapping to or from Lightning
1931    Lightning {
1932        swap_id: String,
1933
1934        /// Represents the invoice description
1935        description: String,
1936
1937        /// The height of the block at which the swap will no longer be valid
1938        liquid_expiration_blockheight: u32,
1939
1940        /// The preimage of the paid invoice (proof of payment).
1941        preimage: Option<String>,
1942
1943        /// Represents the Bolt11/Bolt12 invoice associated with a payment
1944        /// In the case of a Send payment, this is the invoice paid by the swapper
1945        /// In the case of a Receive payment, this is the invoice paid by the user
1946        invoice: Option<String>,
1947
1948        bolt12_offer: Option<String>,
1949
1950        /// The payment hash of the invoice
1951        payment_hash: Option<String>,
1952
1953        /// The invoice destination/payee pubkey
1954        destination_pubkey: Option<String>,
1955
1956        /// The payment LNURL info
1957        lnurl_info: Option<LnUrlInfo>,
1958
1959        /// The BIP353 address used to resolve this payment
1960        bip353_address: Option<String>,
1961
1962        /// The payer note
1963        payer_note: Option<String>,
1964
1965        /// For a Receive payment, this is the claim tx id in case it has already been broadcast
1966        claim_tx_id: Option<String>,
1967
1968        /// For a Send swap which was refunded, this is the refund tx id
1969        refund_tx_id: Option<String>,
1970
1971        /// For a Send swap which was refunded, this is the refund amount
1972        refund_tx_amount_sat: Option<u64>,
1973    },
1974    /// Direct onchain payment to a Liquid address
1975    Liquid {
1976        /// Represents either a Liquid BIP21 URI or pure address
1977        destination: String,
1978
1979        /// Represents the BIP21 `message` field
1980        description: String,
1981
1982        /// The asset id
1983        asset_id: String,
1984
1985        /// The asset info derived from the [AssetMetadata]
1986        asset_info: Option<AssetInfo>,
1987
1988        /// The payment LNURL info
1989        lnurl_info: Option<LnUrlInfo>,
1990
1991        /// The BIP353 address used to resolve this payment
1992        bip353_address: Option<String>,
1993
1994        /// The payer note
1995        payer_note: Option<String>,
1996    },
1997    /// Swapping to or from the Bitcoin chain
1998    Bitcoin {
1999        swap_id: String,
2000
2001        /// The Bitcoin address that receives funds.
2002        bitcoin_address: String,
2003
2004        /// Represents the invoice description
2005        description: String,
2006
2007        /// For an amountless receive swap, this indicates if fees were automatically accepted.
2008        /// Fees are auto accepted when the swapper proposes fees that are within the initial
2009        /// estimate, plus the `onchain_fee_rate_leeway_sat_per_vbyte` set in the [Config], if any.
2010        auto_accepted_fees: bool,
2011
2012        /// The height of the Liquid block at which the swap will no longer be valid
2013        liquid_expiration_blockheight: u32,
2014
2015        /// The height of the Bitcoin block at which the swap will no longer be valid
2016        bitcoin_expiration_blockheight: u32,
2017
2018        /// The lockup tx id that initiates the swap
2019        lockup_tx_id: Option<String>,
2020
2021        /// The claim tx id that claims the server lockup tx
2022        claim_tx_id: Option<String>,
2023
2024        /// For a Send swap which was refunded, this is the refund tx id
2025        refund_tx_id: Option<String>,
2026
2027        /// For a Send swap which was refunded, this is the refund amount
2028        refund_tx_amount_sat: Option<u64>,
2029    },
2030}
2031
2032impl PaymentDetails {
2033    pub(crate) fn get_swap_id(&self) -> Option<String> {
2034        match self {
2035            Self::Lightning { swap_id, .. } | Self::Bitcoin { swap_id, .. } => {
2036                Some(swap_id.clone())
2037            }
2038            Self::Liquid { .. } => None,
2039        }
2040    }
2041
2042    pub(crate) fn get_refund_tx_amount_sat(&self) -> Option<u64> {
2043        match self {
2044            Self::Lightning {
2045                refund_tx_amount_sat,
2046                ..
2047            }
2048            | Self::Bitcoin {
2049                refund_tx_amount_sat,
2050                ..
2051            } => *refund_tx_amount_sat,
2052            Self::Liquid { .. } => None,
2053        }
2054    }
2055
2056    pub(crate) fn get_description(&self) -> Option<String> {
2057        match self {
2058            Self::Lightning { description, .. }
2059            | Self::Bitcoin { description, .. }
2060            | Self::Liquid { description, .. } => Some(description.clone()),
2061        }
2062    }
2063
2064    pub(crate) fn is_lbtc_asset_id(&self, network: LiquidNetwork) -> bool {
2065        match self {
2066            Self::Liquid { asset_id, .. } => {
2067                asset_id.eq(&utils::lbtc_asset_id(network).to_string())
2068            }
2069            _ => true,
2070        }
2071    }
2072}
2073
2074/// Represents an SDK payment.
2075///
2076/// By default, this is an onchain tx. It may represent a swap, if swap metadata is available.
2077#[derive(Debug, Clone, PartialEq, Serialize)]
2078pub struct Payment {
2079    /// The destination associated with the payment, if it was created via our SDK.
2080    /// Can be either a Liquid/Bitcoin address, a Liquid BIP21 URI or an invoice
2081    pub destination: Option<String>,
2082
2083    pub tx_id: Option<String>,
2084
2085    /// Data to use in the `blinded` param when unblinding the transaction in an explorer.
2086    /// See: <https://docs.liquid.net/docs/unblinding-transactions>
2087    pub unblinding_data: Option<String>,
2088
2089    /// Composite timestamp that can be used for sorting or displaying the payment.
2090    ///
2091    /// If this payment has an associated swap, it is the swap creation time. Otherwise, the point
2092    /// in time when the underlying tx was included in a block. If there is no associated swap
2093    /// available and the underlying tx is not yet confirmed, the value is `now()`.
2094    pub timestamp: u32,
2095
2096    /// The payment amount, which corresponds to the onchain tx amount.
2097    ///
2098    /// In case of an outbound payment (Send), this is the payer amount. Otherwise it's the receiver amount.
2099    pub amount_sat: u64,
2100
2101    /// Represents the fees paid by this wallet for this payment.
2102    ///
2103    /// ### Swaps
2104    /// If there is an associated Send Swap, these fees represent the total fees paid by this wallet
2105    /// (the sender). It is the difference between the amount that was sent and the amount received.
2106    ///
2107    /// If there is an associated Receive Swap, these fees represent the total fees paid by this wallet
2108    /// (the receiver). It is also the difference between the amount that was sent and the amount received.
2109    ///
2110    /// ### Pure onchain txs
2111    /// If no swap is associated with this payment:
2112    /// - for Send payments, this is the onchain tx fee
2113    /// - for Receive payments, this is zero
2114    pub fees_sat: u64,
2115
2116    /// Service fees paid to the swapper service. This is only set for swaps (i.e. doesn't apply to
2117    /// direct Liquid payments).
2118    pub swapper_fees_sat: Option<u64>,
2119
2120    /// If it is a `Send` or `Receive` payment
2121    pub payment_type: PaymentType,
2122
2123    /// Composite status representing the overall status of the payment.
2124    ///
2125    /// If the tx has no associated swap, this reflects the onchain tx status (confirmed or not).
2126    ///
2127    /// If the tx has an associated swap, this is determined by the swap status (pending or complete).
2128    pub status: PaymentState,
2129
2130    /// The details of a payment, depending on its [destination](Payment::destination) and
2131    /// [type](Payment::payment_type)
2132    pub details: PaymentDetails,
2133}
2134impl Payment {
2135    pub(crate) fn from_pending_swap(
2136        swap: PaymentSwapData,
2137        payment_type: PaymentType,
2138        payment_details: PaymentDetails,
2139    ) -> Payment {
2140        let amount_sat = match payment_type {
2141            PaymentType::Receive => swap.receiver_amount_sat,
2142            PaymentType::Send => swap.payer_amount_sat,
2143        };
2144
2145        Payment {
2146            destination: swap.invoice.clone(),
2147            tx_id: None,
2148            unblinding_data: None,
2149            timestamp: swap.created_at,
2150            amount_sat,
2151            fees_sat: swap
2152                .payer_amount_sat
2153                .saturating_sub(swap.receiver_amount_sat),
2154            swapper_fees_sat: Some(swap.swapper_fees_sat),
2155            payment_type,
2156            status: swap.status,
2157            details: payment_details,
2158        }
2159    }
2160
2161    pub(crate) fn from_tx_data(
2162        tx: PaymentTxData,
2163        balance: PaymentTxBalance,
2164        swap: Option<PaymentSwapData>,
2165        details: PaymentDetails,
2166    ) -> Payment {
2167        let (amount_sat, fees_sat) = match swap.as_ref() {
2168            Some(s) => match balance.payment_type {
2169                // For receive swaps, to avoid some edge case issues related to potential past
2170                // overpayments, we use the actual claim value as the final received amount
2171                // for fee calculation.
2172                PaymentType::Receive => (
2173                    balance.amount,
2174                    s.payer_amount_sat.saturating_sub(balance.amount),
2175                ),
2176                PaymentType::Send => (
2177                    s.receiver_amount_sat,
2178                    s.payer_amount_sat.saturating_sub(s.receiver_amount_sat),
2179                ),
2180            },
2181            None => {
2182                let (amount_sat, fees_sat) = match balance.payment_type {
2183                    PaymentType::Receive => (balance.amount, 0),
2184                    PaymentType::Send => (balance.amount, tx.fees_sat),
2185                };
2186                // If the payment is a Liquid payment, we only show the amount if the asset
2187                // is LBTC and only show the fees if the asset info has no set fees
2188                match details {
2189                    PaymentDetails::Liquid {
2190                        asset_info: Some(ref asset_info),
2191                        ..
2192                    } if asset_info.ticker != "BTC" => (0, asset_info.fees.map_or(fees_sat, |_| 0)),
2193                    _ => (amount_sat, fees_sat),
2194                }
2195            }
2196        };
2197        Payment {
2198            tx_id: Some(tx.tx_id),
2199            unblinding_data: tx.unblinding_data,
2200            // When the swap is present and of type send and receive, we retrieve the destination from the invoice.
2201            // If it's a chain swap instead, we use the `bitcoin_address` field from the swap data.
2202            // Otherwise, we specify the Liquid address (BIP21 or pure), set in `payment_details.address`.
2203            destination: match &swap {
2204                Some(PaymentSwapData {
2205                    swap_type: PaymentSwapType::Receive,
2206                    invoice,
2207                    ..
2208                }) => invoice.clone(),
2209                Some(PaymentSwapData {
2210                    swap_type: PaymentSwapType::Send,
2211                    invoice,
2212                    bolt12_offer,
2213                    ..
2214                }) => bolt12_offer.clone().or(invoice.clone()),
2215                Some(PaymentSwapData {
2216                    swap_type: PaymentSwapType::Chain,
2217                    bitcoin_address,
2218                    ..
2219                }) => bitcoin_address.clone(),
2220                _ => match &details {
2221                    PaymentDetails::Liquid { destination, .. } => Some(destination.clone()),
2222                    _ => None,
2223                },
2224            },
2225            timestamp: tx
2226                .timestamp
2227                .or(swap.as_ref().map(|s| s.created_at))
2228                .unwrap_or(utils::now()),
2229            amount_sat,
2230            fees_sat,
2231            swapper_fees_sat: swap.as_ref().map(|s| s.swapper_fees_sat),
2232            payment_type: balance.payment_type,
2233            status: match &swap {
2234                Some(swap) => swap.status,
2235                None => match tx.is_confirmed {
2236                    true => PaymentState::Complete,
2237                    false => PaymentState::Pending,
2238                },
2239            },
2240            details,
2241        }
2242    }
2243
2244    pub(crate) fn get_refund_tx_id(&self) -> Option<String> {
2245        match self.details.clone() {
2246            PaymentDetails::Lightning { refund_tx_id, .. } => Some(refund_tx_id),
2247            PaymentDetails::Bitcoin { refund_tx_id, .. } => Some(refund_tx_id),
2248            PaymentDetails::Liquid { .. } => None,
2249        }
2250        .flatten()
2251    }
2252}
2253
2254/// Returned when calling [crate::sdk::LiquidSdk::recommended_fees].
2255#[derive(Deserialize, Serialize, Clone, Debug)]
2256#[serde(rename_all = "camelCase")]
2257pub struct RecommendedFees {
2258    pub fastest_fee: u64,
2259    pub half_hour_fee: u64,
2260    pub hour_fee: u64,
2261    pub economy_fee: u64,
2262    pub minimum_fee: u64,
2263}
2264
2265/// An argument of [PrepareBuyBitcoinRequest] when calling [crate::sdk::LiquidSdk::prepare_buy_bitcoin].
2266#[derive(Debug, Clone, Copy, EnumString, PartialEq, Serialize)]
2267pub enum BuyBitcoinProvider {
2268    #[strum(serialize = "moonpay")]
2269    Moonpay,
2270}
2271
2272/// An argument when calling [crate::sdk::LiquidSdk::prepare_buy_bitcoin].
2273#[derive(Debug, Serialize)]
2274pub struct PrepareBuyBitcoinRequest {
2275    pub provider: BuyBitcoinProvider,
2276    pub amount_sat: u64,
2277}
2278
2279/// Returned when calling [crate::sdk::LiquidSdk::prepare_buy_bitcoin].
2280#[derive(Clone, Debug, Serialize)]
2281pub struct PrepareBuyBitcoinResponse {
2282    pub provider: BuyBitcoinProvider,
2283    pub amount_sat: u64,
2284    pub fees_sat: u64,
2285}
2286
2287/// An argument when calling [crate::sdk::LiquidSdk::buy_bitcoin].
2288#[derive(Clone, Debug, Serialize)]
2289pub struct BuyBitcoinRequest {
2290    pub prepare_response: PrepareBuyBitcoinResponse,
2291    /// The optional URL to redirect to after completing the buy.
2292    ///
2293    /// For Moonpay, see <https://dev.moonpay.com/docs/on-ramp-configure-user-journey-params>
2294    pub redirect_url: Option<String>,
2295}
2296
2297/// Internal SDK log entry used in the Uniffi and Dart bindings
2298#[derive(Clone, Debug)]
2299pub struct LogEntry {
2300    pub line: String,
2301    pub level: String,
2302}
2303
2304#[derive(Clone, Debug, Serialize, Deserialize)]
2305struct InternalLeaf {
2306    pub output: String,
2307    pub version: u8,
2308}
2309impl From<InternalLeaf> for Leaf {
2310    fn from(value: InternalLeaf) -> Self {
2311        Leaf {
2312            output: value.output,
2313            version: value.version,
2314        }
2315    }
2316}
2317impl From<Leaf> for InternalLeaf {
2318    fn from(value: Leaf) -> Self {
2319        InternalLeaf {
2320            output: value.output,
2321            version: value.version,
2322        }
2323    }
2324}
2325
2326#[derive(Clone, Debug, Serialize, Deserialize)]
2327pub(super) struct InternalSwapTree {
2328    claim_leaf: InternalLeaf,
2329    refund_leaf: InternalLeaf,
2330}
2331impl From<InternalSwapTree> for SwapTree {
2332    fn from(value: InternalSwapTree) -> Self {
2333        SwapTree {
2334            claim_leaf: value.claim_leaf.into(),
2335            refund_leaf: value.refund_leaf.into(),
2336        }
2337    }
2338}
2339impl From<SwapTree> for InternalSwapTree {
2340    fn from(value: SwapTree) -> Self {
2341        InternalSwapTree {
2342            claim_leaf: value.claim_leaf.into(),
2343            refund_leaf: value.refund_leaf.into(),
2344        }
2345    }
2346}
2347
2348/// An argument when calling [crate::sdk::LiquidSdk::prepare_lnurl_pay].
2349#[derive(Debug, Serialize)]
2350pub struct PrepareLnUrlPayRequest {
2351    /// The [LnUrlPayRequestData] returned by [parse]
2352    pub data: LnUrlPayRequestData,
2353    /// The amount to send
2354    pub amount: PayAmount,
2355    /// A BIP353 address, in case one was used in order to fetch the LNURL Pay request data.
2356    /// Returned by [parse].
2357    pub bip353_address: Option<String>,
2358    /// An optional comment LUD-12 to be stored with the payment. The comment is included in the
2359    /// invoice request sent to the LNURL endpoint.
2360    pub comment: Option<String>,
2361    /// Validates that, if there is a URL success action, the URL domain matches
2362    /// the LNURL callback domain. Defaults to `true`
2363    pub validate_success_action_url: Option<bool>,
2364}
2365
2366/// Returned when calling [crate::sdk::LiquidSdk::prepare_lnurl_pay].
2367#[derive(Debug, Serialize)]
2368pub struct PrepareLnUrlPayResponse {
2369    /// The destination of the payment
2370    pub destination: SendDestination,
2371    /// The fees in satoshis to send the payment
2372    pub fees_sat: u64,
2373    /// The [LnUrlPayRequestData] returned by [parse]
2374    pub data: LnUrlPayRequestData,
2375    /// The amount to send
2376    pub amount: PayAmount,
2377    /// An optional comment LUD-12 to be stored with the payment. The comment is included in the
2378    /// invoice request sent to the LNURL endpoint.
2379    pub comment: Option<String>,
2380    /// The unprocessed LUD-09 success action. This will be processed and decrypted if
2381    /// needed after calling [crate::sdk::LiquidSdk::lnurl_pay]
2382    pub success_action: Option<SuccessAction>,
2383}
2384
2385/// An argument when calling [crate::sdk::LiquidSdk::lnurl_pay].
2386#[derive(Debug, Serialize)]
2387pub struct LnUrlPayRequest {
2388    /// The response from calling [crate::sdk::LiquidSdk::prepare_lnurl_pay]
2389    pub prepare_response: PrepareLnUrlPayResponse,
2390}
2391
2392/// Contains the result of the entire LNURL-pay interaction, as reported by the LNURL endpoint.
2393///
2394/// * `EndpointSuccess` indicates the payment is complete. The endpoint may return a `SuccessActionProcessed`,
2395///   in which case, the wallet has to present it to the user as described in
2396///   <https://github.com/lnurl/luds/blob/luds/09.md>
2397///
2398/// * `EndpointError` indicates a generic issue the LNURL endpoint encountered, including a freetext
2399///   field with the reason.
2400///
2401/// * `PayError` indicates that an error occurred while trying to pay the invoice from the LNURL endpoint.
2402///   This includes the payment hash of the failed invoice and the failure reason.
2403#[derive(Serialize)]
2404#[allow(clippy::large_enum_variant)]
2405pub enum LnUrlPayResult {
2406    EndpointSuccess { data: LnUrlPaySuccessData },
2407    EndpointError { data: LnUrlErrorData },
2408    PayError { data: LnUrlPayErrorData },
2409}
2410
2411#[derive(Serialize)]
2412pub struct LnUrlPaySuccessData {
2413    pub payment: Payment,
2414    pub success_action: Option<SuccessActionProcessed>,
2415}
2416
2417#[derive(Debug, Clone)]
2418pub enum Transaction {
2419    Liquid(boltz_client::elements::Transaction),
2420    Bitcoin(boltz_client::bitcoin::Transaction),
2421}
2422
2423impl Transaction {
2424    pub(crate) fn txid(&self) -> String {
2425        match self {
2426            Transaction::Liquid(tx) => tx.txid().to_hex(),
2427            Transaction::Bitcoin(tx) => tx.compute_txid().to_hex(),
2428        }
2429    }
2430}
2431
2432#[derive(Debug, Clone)]
2433pub enum Utxo {
2434    Liquid(
2435        Box<(
2436            boltz_client::elements::OutPoint,
2437            boltz_client::elements::TxOut,
2438        )>,
2439    ),
2440    Bitcoin(
2441        (
2442            boltz_client::bitcoin::OutPoint,
2443            boltz_client::bitcoin::TxOut,
2444        ),
2445    ),
2446}
2447
2448impl Utxo {
2449    pub(crate) fn as_bitcoin(
2450        &self,
2451    ) -> Option<&(
2452        boltz_client::bitcoin::OutPoint,
2453        boltz_client::bitcoin::TxOut,
2454    )> {
2455        match self {
2456            Utxo::Liquid(_) => None,
2457            Utxo::Bitcoin(utxo) => Some(utxo),
2458        }
2459    }
2460
2461    pub(crate) fn as_liquid(
2462        &self,
2463    ) -> Option<
2464        Box<(
2465            boltz_client::elements::OutPoint,
2466            boltz_client::elements::TxOut,
2467        )>,
2468    > {
2469        match self {
2470            Utxo::Bitcoin(_) => None,
2471            Utxo::Liquid(utxo) => Some(utxo.clone()),
2472        }
2473    }
2474}
2475
2476/// An argument when calling [crate::sdk::LiquidSdk::fetch_payment_proposed_fees].
2477#[derive(Debug, Clone)]
2478pub struct FetchPaymentProposedFeesRequest {
2479    pub swap_id: String,
2480}
2481
2482/// Returned when calling [crate::sdk::LiquidSdk::fetch_payment_proposed_fees].
2483#[derive(Debug, Clone, Serialize)]
2484pub struct FetchPaymentProposedFeesResponse {
2485    pub swap_id: String,
2486    pub fees_sat: u64,
2487    /// Amount sent by the swap payer
2488    pub payer_amount_sat: u64,
2489    /// Amount that will be received if these fees are accepted
2490    pub receiver_amount_sat: u64,
2491}
2492
2493/// An argument when calling [crate::sdk::LiquidSdk::accept_payment_proposed_fees].
2494#[derive(Debug, Clone)]
2495pub struct AcceptPaymentProposedFeesRequest {
2496    pub response: FetchPaymentProposedFeesResponse,
2497}
2498
2499#[derive(Clone, Debug)]
2500pub struct History<T> {
2501    pub txid: T,
2502    /// Confirmation height of txid
2503    ///
2504    /// -1 means unconfirmed with unconfirmed parents
2505    ///  0 means unconfirmed with confirmed parents
2506    pub height: i32,
2507}
2508pub(crate) type LBtcHistory = History<elements::Txid>;
2509pub(crate) type BtcHistory = History<bitcoin::Txid>;
2510
2511impl<T> History<T> {
2512    pub(crate) fn confirmed(&self) -> bool {
2513        self.height > 0
2514    }
2515}
2516#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
2517impl From<electrum_client::GetHistoryRes> for BtcHistory {
2518    fn from(value: electrum_client::GetHistoryRes) -> Self {
2519        Self {
2520            txid: value.tx_hash,
2521            height: value.height,
2522        }
2523    }
2524}
2525impl From<lwk_wollet::History> for LBtcHistory {
2526    fn from(value: lwk_wollet::History) -> Self {
2527        Self::from(&value)
2528    }
2529}
2530impl From<&lwk_wollet::History> for LBtcHistory {
2531    fn from(value: &lwk_wollet::History) -> Self {
2532        Self {
2533            txid: value.txid,
2534            height: value.height,
2535        }
2536    }
2537}
2538pub(crate) type BtcScript = bitcoin::ScriptBuf;
2539pub(crate) type LBtcScript = elements::Script;
2540
2541#[derive(Clone, Debug)]
2542pub struct BtcScriptBalance {
2543    /// Confirmed balance in Satoshis for the address.
2544    pub confirmed: u64,
2545    /// Unconfirmed balance in Satoshis for the address.
2546    ///
2547    /// Some servers (e.g. `electrs`) return this as a negative value.
2548    pub unconfirmed: i64,
2549}
2550#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
2551impl From<electrum_client::GetBalanceRes> for BtcScriptBalance {
2552    fn from(val: electrum_client::GetBalanceRes) -> Self {
2553        Self {
2554            confirmed: val.confirmed,
2555            unconfirmed: val.unconfirmed,
2556        }
2557    }
2558}
2559
2560pub(crate) struct GetSyncContextRequest {
2561    pub partial_sync: Option<bool>,
2562    pub last_liquid_tip: u32,
2563    pub last_bitcoin_tip: u32,
2564}
2565
2566pub(crate) struct SyncContext {
2567    pub maybe_liquid_tip: Option<u32>,
2568    pub maybe_bitcoin_tip: Option<u32>,
2569    pub recoverable_swaps: Vec<Swap>,
2570    pub is_new_liquid_block: bool,
2571    pub is_new_bitcoin_block: bool,
2572}
2573
2574pub(crate) struct TaskHandle {
2575    pub name: String,
2576    pub handle: tokio::task::JoinHandle<()>,
2577}
2578
2579#[macro_export]
2580macro_rules! get_updated_fields {
2581    ($($var:ident),* $(,)?) => {{
2582        let mut options = Vec::new();
2583        $(
2584            if $var.is_some() {
2585                options.push(stringify!($var).to_string());
2586            }
2587        )*
2588        match options.len() > 0 {
2589            true => Some(options),
2590            false => None,
2591        }
2592    }};
2593}