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