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