breez_sdk_liquid/
model.rs

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