breez_sdk_liquid/
model.rs

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