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