breez_sdk_liquid/
model.rs

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