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 estimated fee in satoshi. Is set when there is Bitcoin available
733    /// to pay fees. When not set, there are asset fees available to pay fees.
734    pub fees_sat: Option<u64>,
735    /// The optional estimated fee in the asset. Is set when [PayAmount::Asset::estimate_asset_fees]
736    /// is set to `true`, the Payjoin service accepts this asset to pay fees and there
737    /// are funds available in this asset to pay fees.
738    pub estimated_asset_fees: Option<f64>,
739}
740
741/// An argument when calling [crate::sdk::LiquidSdk::send_payment].
742#[derive(Debug, Serialize)]
743pub struct SendPaymentRequest {
744    pub prepare_response: PrepareSendResponse,
745    pub use_asset_fees: Option<bool>,
746}
747
748/// Returned when calling [crate::sdk::LiquidSdk::send_payment].
749#[derive(Debug, Serialize)]
750pub struct SendPaymentResponse {
751    pub payment: Payment,
752}
753
754/// Used to specify the amount to sent or to send all funds.
755#[derive(Debug, Serialize, Clone)]
756pub enum PayAmount {
757    /// The amount in satoshi that will be received
758    Bitcoin { receiver_amount_sat: u64 },
759
760    /// The amount of an asset that will be received
761    Asset {
762        asset_id: String,
763        receiver_amount: f64,
764        estimate_asset_fees: Option<bool>,
765    },
766
767    /// Indicates that all available Bitcoin funds should be sent
768    Drain,
769}
770
771/// An argument when calling [crate::sdk::LiquidSdk::prepare_pay_onchain].
772#[derive(Debug, Serialize, Clone)]
773pub struct PreparePayOnchainRequest {
774    /// The amount to send
775    pub amount: PayAmount,
776    /// The optional fee rate of the Bitcoin claim transaction in sat/vB. Defaults to the swapper estimated claim fee.
777    pub fee_rate_sat_per_vbyte: Option<u32>,
778}
779
780/// Returned when calling [crate::sdk::LiquidSdk::prepare_pay_onchain].
781#[derive(Debug, Serialize, Clone)]
782pub struct PreparePayOnchainResponse {
783    pub receiver_amount_sat: u64,
784    pub claim_fees_sat: u64,
785    pub total_fees_sat: u64,
786}
787
788/// An argument when calling [crate::sdk::LiquidSdk::pay_onchain].
789#[derive(Debug, Serialize)]
790pub struct PayOnchainRequest {
791    pub address: String,
792    pub prepare_response: PreparePayOnchainResponse,
793}
794
795/// An argument when calling [crate::sdk::LiquidSdk::prepare_refund].
796#[derive(Debug, Serialize)]
797pub struct PrepareRefundRequest {
798    /// The address where the swap funds are locked up
799    pub swap_address: String,
800    /// The address to refund the swap funds to
801    pub refund_address: String,
802    /// The fee rate in sat/vB for the refund transaction
803    pub fee_rate_sat_per_vbyte: u32,
804}
805
806/// Returned when calling [crate::sdk::LiquidSdk::prepare_refund].
807#[derive(Debug, Serialize)]
808pub struct PrepareRefundResponse {
809    pub tx_vsize: u32,
810    pub tx_fee_sat: u64,
811    /// The txid of the last broadcasted refund tx, if any
812    pub last_refund_tx_id: Option<String>,
813}
814
815/// An argument when calling [crate::sdk::LiquidSdk::refund].
816#[derive(Debug, Serialize)]
817pub struct RefundRequest {
818    /// The address where the swap funds are locked up
819    pub swap_address: String,
820    /// The address to refund the swap funds to
821    pub refund_address: String,
822    /// The fee rate in sat/vB for the refund transaction
823    pub fee_rate_sat_per_vbyte: u32,
824}
825
826/// Returned when calling [crate::sdk::LiquidSdk::refund].
827#[derive(Debug, Serialize)]
828pub struct RefundResponse {
829    pub refund_tx_id: String,
830}
831
832/// An asset balance to denote the balance for each asset.
833#[derive(Clone, Debug, Default, Serialize, Deserialize)]
834pub struct AssetBalance {
835    pub asset_id: String,
836    pub balance_sat: u64,
837    pub name: Option<String>,
838    pub ticker: Option<String>,
839    pub balance: Option<f64>,
840}
841
842#[derive(Debug, Serialize, Deserialize, Default)]
843pub struct BlockchainInfo {
844    pub liquid_tip: u32,
845    pub bitcoin_tip: u32,
846}
847
848#[derive(Copy, Clone)]
849pub(crate) struct ChainTips {
850    pub liquid_tip: u32,
851    pub bitcoin_tip: u32,
852}
853
854#[derive(Debug, Serialize, Deserialize)]
855pub struct WalletInfo {
856    /// Usable balance. This is the confirmed onchain balance minus `pending_send_sat`.
857    pub balance_sat: u64,
858    /// Amount that is being used for ongoing Send swaps
859    pub pending_send_sat: u64,
860    /// Incoming amount that is pending from ongoing Receive swaps
861    pub pending_receive_sat: u64,
862    /// The wallet's fingerprint. It is used to build the working directory in [Config::get_wallet_dir].
863    pub fingerprint: String,
864    /// The wallet's pubkey. Used to verify signed messages.
865    pub pubkey: String,
866    /// Asset balances of non Liquid Bitcoin assets
867    #[serde(default)]
868    pub asset_balances: Vec<AssetBalance>,
869}
870
871impl WalletInfo {
872    pub(crate) fn validate_sufficient_funds(
873        &self,
874        network: LiquidNetwork,
875        amount_sat: u64,
876        fees_sat: Option<u64>,
877        asset_id: &str,
878    ) -> Result<(), PaymentError> {
879        let fees_sat = fees_sat.unwrap_or(0);
880        if asset_id.eq(&utils::lbtc_asset_id(network).to_string()) {
881            ensure_sdk!(
882                amount_sat + fees_sat <= self.balance_sat,
883                PaymentError::InsufficientFunds
884            );
885        } else {
886            match self
887                .asset_balances
888                .iter()
889                .find(|ab| ab.asset_id.eq(asset_id))
890            {
891                Some(asset_balance) => ensure_sdk!(
892                    amount_sat <= asset_balance.balance_sat && fees_sat <= self.balance_sat,
893                    PaymentError::InsufficientFunds
894                ),
895                None => return Err(PaymentError::InsufficientFunds),
896            }
897        }
898        Ok(())
899    }
900}
901
902/// Returned when calling [crate::sdk::LiquidSdk::get_info].
903#[derive(Debug, Serialize, Deserialize)]
904pub struct GetInfoResponse {
905    /// The wallet information, such as the balance, fingerprint and public key
906    pub wallet_info: WalletInfo,
907    /// The latest synced blockchain information, such as the Liquid/Bitcoin tips
908    #[serde(default)]
909    pub blockchain_info: BlockchainInfo,
910}
911
912/// An argument when calling [crate::sdk::LiquidSdk::sign_message].
913#[derive(Clone, Debug, PartialEq)]
914pub struct SignMessageRequest {
915    pub message: String,
916}
917
918/// Returned when calling [crate::sdk::LiquidSdk::sign_message].
919#[derive(Clone, Debug, PartialEq)]
920pub struct SignMessageResponse {
921    pub signature: String,
922}
923
924/// An argument when calling [crate::sdk::LiquidSdk::check_message].
925#[derive(Clone, Debug, PartialEq)]
926pub struct CheckMessageRequest {
927    /// The message that was signed.
928    pub message: String,
929    /// The public key of the node that signed the message.
930    pub pubkey: String,
931    /// The zbase encoded signature to verify.
932    pub signature: String,
933}
934
935/// Returned when calling [crate::sdk::LiquidSdk::check_message].
936#[derive(Clone, Debug, PartialEq)]
937pub struct CheckMessageResponse {
938    /// Boolean value indicating whether the signature covers the message and
939    /// was signed by the given pubkey.
940    pub is_valid: bool,
941}
942
943/// An argument when calling [crate::sdk::LiquidSdk::backup].
944#[derive(Debug, Serialize)]
945pub struct BackupRequest {
946    /// Path to the backup.
947    ///
948    /// If not set, it defaults to `backup.sql` for mainnet, `backup-testnet.sql` for testnet,
949    /// and `backup-regtest.sql` for regtest.
950    ///
951    /// The file will be saved in [ConnectRequest]'s `data_dir`.
952    pub backup_path: Option<String>,
953}
954
955/// An argument when calling [crate::sdk::LiquidSdk::restore].
956#[derive(Debug, Serialize)]
957pub struct RestoreRequest {
958    pub backup_path: Option<String>,
959}
960
961/// An argument when calling [crate::sdk::LiquidSdk::list_payments].
962#[derive(Default)]
963pub struct ListPaymentsRequest {
964    pub filters: Option<Vec<PaymentType>>,
965    pub states: Option<Vec<PaymentState>>,
966    /// Epoch time, in seconds
967    pub from_timestamp: Option<i64>,
968    /// Epoch time, in seconds
969    pub to_timestamp: Option<i64>,
970    pub offset: Option<u32>,
971    pub limit: Option<u32>,
972    pub details: Option<ListPaymentDetails>,
973    pub sort_ascending: Option<bool>,
974}
975
976/// An argument of [ListPaymentsRequest] when calling [crate::sdk::LiquidSdk::list_payments].
977#[derive(Debug, Serialize)]
978pub enum ListPaymentDetails {
979    /// A Liquid payment
980    Liquid {
981        /// Optional asset id
982        asset_id: Option<String>,
983        /// Optional BIP21 URI or address
984        destination: Option<String>,
985    },
986
987    /// A Bitcoin payment
988    Bitcoin {
989        /// Optional address
990        address: Option<String>,
991    },
992}
993
994/// An argument when calling [crate::sdk::LiquidSdk::get_payment].
995#[derive(Debug, Serialize)]
996pub enum GetPaymentRequest {
997    /// The payment hash of a Lightning payment
998    PaymentHash { payment_hash: String },
999    /// A swap id or its SHA256 hash
1000    SwapId { swap_id: String },
1001}
1002
1003/// Trait that can be used to react to new blocks from Bitcoin and Liquid chains
1004#[sdk_macros::async_trait]
1005pub(crate) trait BlockListener: MaybeSend + MaybeSync {
1006    async fn on_bitcoin_block(&self, height: u32);
1007    async fn on_liquid_block(&self, height: u32);
1008}
1009
1010// A swap enum variant
1011#[derive(Clone, Debug)]
1012pub enum Swap {
1013    Chain(ChainSwap),
1014    Send(SendSwap),
1015    Receive(ReceiveSwap),
1016}
1017impl Swap {
1018    pub(crate) fn id(&self) -> String {
1019        match &self {
1020            Swap::Chain(ChainSwap { id, .. })
1021            | Swap::Send(SendSwap { id, .. })
1022            | Swap::Receive(ReceiveSwap { id, .. }) => id.clone(),
1023        }
1024    }
1025
1026    pub(crate) fn version(&self) -> u64 {
1027        match self {
1028            Swap::Chain(ChainSwap { metadata, .. })
1029            | Swap::Send(SendSwap { metadata, .. })
1030            | Swap::Receive(ReceiveSwap { metadata, .. }) => metadata.version,
1031        }
1032    }
1033
1034    pub(crate) fn set_version(&mut self, version: u64) {
1035        match self {
1036            Swap::Chain(chain_swap) => {
1037                chain_swap.metadata.version = version;
1038            }
1039            Swap::Send(send_swap) => {
1040                send_swap.metadata.version = version;
1041            }
1042            Swap::Receive(receive_swap) => {
1043                receive_swap.metadata.version = version;
1044            }
1045        }
1046    }
1047
1048    pub(crate) fn is_local(&self) -> bool {
1049        match self {
1050            Swap::Chain(ChainSwap { metadata, .. })
1051            | Swap::Send(SendSwap { metadata, .. })
1052            | Swap::Receive(ReceiveSwap { metadata, .. }) => metadata.is_local,
1053        }
1054    }
1055
1056    pub(crate) fn last_updated_at(&self) -> u32 {
1057        match self {
1058            Swap::Chain(ChainSwap { metadata, .. })
1059            | Swap::Send(SendSwap { metadata, .. })
1060            | Swap::Receive(ReceiveSwap { metadata, .. }) => metadata.last_updated_at,
1061        }
1062    }
1063}
1064impl From<ChainSwap> for Swap {
1065    fn from(swap: ChainSwap) -> Self {
1066        Self::Chain(swap)
1067    }
1068}
1069impl From<SendSwap> for Swap {
1070    fn from(swap: SendSwap) -> Self {
1071        Self::Send(swap)
1072    }
1073}
1074impl From<ReceiveSwap> for Swap {
1075    fn from(swap: ReceiveSwap) -> Self {
1076        Self::Receive(swap)
1077    }
1078}
1079
1080#[derive(Clone, Debug)]
1081pub(crate) enum SwapScriptV2 {
1082    Bitcoin(BtcSwapScript),
1083    Liquid(LBtcSwapScript),
1084}
1085impl SwapScriptV2 {
1086    pub(crate) fn as_bitcoin_script(&self) -> Result<BtcSwapScript> {
1087        match self {
1088            SwapScriptV2::Bitcoin(script) => Ok(script.clone()),
1089            _ => Err(anyhow!("Invalid chain")),
1090        }
1091    }
1092
1093    pub(crate) fn as_liquid_script(&self) -> Result<LBtcSwapScript> {
1094        match self {
1095            SwapScriptV2::Liquid(script) => Ok(script.clone()),
1096            _ => Err(anyhow!("Invalid chain")),
1097        }
1098    }
1099}
1100
1101#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
1102pub enum Direction {
1103    Incoming = 0,
1104    Outgoing = 1,
1105}
1106impl ToSql for Direction {
1107    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1108        Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1109    }
1110}
1111impl FromSql for Direction {
1112    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1113        match value {
1114            ValueRef::Integer(i) => match i as u8 {
1115                0 => Ok(Direction::Incoming),
1116                1 => Ok(Direction::Outgoing),
1117                _ => Err(FromSqlError::OutOfRange(i)),
1118            },
1119            _ => Err(FromSqlError::InvalidType),
1120        }
1121    }
1122}
1123
1124#[derive(Clone, Debug, Default)]
1125pub(crate) struct SwapMetadata {
1126    /// Version used for optimistic concurrency control within local db
1127    pub(crate) version: u64,
1128    pub(crate) last_updated_at: u32,
1129    pub(crate) is_local: bool,
1130}
1131
1132/// A chain swap
1133///
1134/// See <https://docs.boltz.exchange/v/api/lifecycle#chain-swaps>
1135#[derive(Clone, Debug, Derivative)]
1136#[derivative(PartialEq)]
1137pub struct ChainSwap {
1138    pub(crate) id: String,
1139    pub(crate) direction: Direction,
1140    /// The Bitcoin claim address is only set for Outgoing Chain Swaps
1141    pub(crate) claim_address: Option<String>,
1142    pub(crate) lockup_address: String,
1143    /// The Liquid refund address is only set for Outgoing Chain Swaps
1144    pub(crate) refund_address: Option<String>,
1145    pub(crate) timeout_block_height: u32,
1146    pub(crate) preimage: String,
1147    pub(crate) description: Option<String>,
1148    /// Payer amount defined at swap creation
1149    pub(crate) payer_amount_sat: u64,
1150    /// The actual payer amount as seen on the user lockup tx. Might differ from `payer_amount_sat`
1151    /// in the case of an over/underpayment
1152    pub(crate) actual_payer_amount_sat: Option<u64>,
1153    /// Receiver amount defined at swap creation
1154    pub(crate) receiver_amount_sat: u64,
1155    /// The final receiver amount, in case of an amountless swap for which fees have been accepted
1156    pub(crate) accepted_receiver_amount_sat: Option<u64>,
1157    pub(crate) claim_fees_sat: u64,
1158    /// The [ChainPair] chosen on swap creation
1159    pub(crate) pair_fees_json: String,
1160    pub(crate) accept_zero_conf: bool,
1161    /// JSON representation of [crate::persist::chain::InternalCreateChainResponse]
1162    pub(crate) create_response_json: String,
1163    /// Persisted only when the server lockup tx is successfully broadcast
1164    pub(crate) server_lockup_tx_id: Option<String>,
1165    /// Persisted only when the user lockup tx is successfully broadcast
1166    pub(crate) user_lockup_tx_id: Option<String>,
1167    /// Persisted as soon as a claim tx is broadcast
1168    pub(crate) claim_tx_id: Option<String>,
1169    /// Persisted as soon as a refund tx is broadcast
1170    pub(crate) refund_tx_id: Option<String>,
1171    pub(crate) created_at: u32,
1172    pub(crate) state: PaymentState,
1173    pub(crate) claim_private_key: String,
1174    pub(crate) refund_private_key: String,
1175    pub(crate) auto_accepted_fees: bool,
1176    /// Swap metadata that is only valid when reading one from the local database
1177    #[derivative(PartialEq = "ignore")]
1178    pub(crate) metadata: SwapMetadata,
1179}
1180impl ChainSwap {
1181    pub(crate) fn get_claim_keypair(&self) -> SdkResult<Keypair> {
1182        utils::decode_keypair(&self.claim_private_key)
1183    }
1184
1185    pub(crate) fn get_refund_keypair(&self) -> SdkResult<Keypair> {
1186        utils::decode_keypair(&self.refund_private_key)
1187    }
1188
1189    pub(crate) fn get_boltz_create_response(&self) -> Result<CreateChainResponse> {
1190        let internal_create_response: crate::persist::chain::InternalCreateChainResponse =
1191            serde_json::from_str(&self.create_response_json).map_err(|e| {
1192                anyhow!("Failed to deserialize InternalCreateSubmarineResponse: {e:?}")
1193            })?;
1194
1195        Ok(CreateChainResponse {
1196            id: self.id.clone(),
1197            claim_details: internal_create_response.claim_details,
1198            lockup_details: internal_create_response.lockup_details,
1199        })
1200    }
1201
1202    pub(crate) fn get_boltz_pair(&self) -> Result<ChainPair> {
1203        let pair: ChainPair = serde_json::from_str(&self.pair_fees_json)
1204            .map_err(|e| anyhow!("Failed to deserialize ChainPair: {e:?}"))?;
1205
1206        Ok(pair)
1207    }
1208
1209    pub(crate) fn get_claim_swap_script(&self) -> SdkResult<SwapScriptV2> {
1210        let chain_swap_details = self.get_boltz_create_response()?.claim_details;
1211        let our_pubkey = self.get_claim_keypair()?.public_key();
1212        let swap_script = match self.direction {
1213            Direction::Incoming => SwapScriptV2::Liquid(LBtcSwapScript::chain_from_swap_resp(
1214                Side::Claim,
1215                chain_swap_details,
1216                our_pubkey.into(),
1217            )?),
1218            Direction::Outgoing => SwapScriptV2::Bitcoin(BtcSwapScript::chain_from_swap_resp(
1219                Side::Claim,
1220                chain_swap_details,
1221                our_pubkey.into(),
1222            )?),
1223        };
1224        Ok(swap_script)
1225    }
1226
1227    pub(crate) fn get_lockup_swap_script(&self) -> SdkResult<SwapScriptV2> {
1228        let chain_swap_details = self.get_boltz_create_response()?.lockup_details;
1229        let our_pubkey = self.get_refund_keypair()?.public_key();
1230        let swap_script = match self.direction {
1231            Direction::Incoming => SwapScriptV2::Bitcoin(BtcSwapScript::chain_from_swap_resp(
1232                Side::Lockup,
1233                chain_swap_details,
1234                our_pubkey.into(),
1235            )?),
1236            Direction::Outgoing => SwapScriptV2::Liquid(LBtcSwapScript::chain_from_swap_resp(
1237                Side::Lockup,
1238                chain_swap_details,
1239                our_pubkey.into(),
1240            )?),
1241        };
1242        Ok(swap_script)
1243    }
1244
1245    /// Returns the lockup script pubkey for Receive Chain Swaps
1246    pub(crate) fn get_receive_lockup_swap_script_pubkey(
1247        &self,
1248        network: LiquidNetwork,
1249    ) -> SdkResult<ScriptBuf> {
1250        let swap_script = self.get_lockup_swap_script()?.as_bitcoin_script()?;
1251        let script_pubkey = swap_script
1252            .to_address(network.as_bitcoin_chain())
1253            .map_err(|e| SdkError::generic(format!("Error getting script address: {e:?}")))?
1254            .script_pubkey();
1255        Ok(script_pubkey)
1256    }
1257
1258    pub(crate) fn to_refundable(&self, amount_sat: u64) -> RefundableSwap {
1259        RefundableSwap {
1260            swap_address: self.lockup_address.clone(),
1261            timestamp: self.created_at,
1262            amount_sat,
1263            last_refund_tx_id: self.refund_tx_id.clone(),
1264        }
1265    }
1266
1267    pub(crate) fn from_boltz_struct_to_json(
1268        create_response: &CreateChainResponse,
1269        expected_swap_id: &str,
1270    ) -> Result<String, PaymentError> {
1271        let internal_create_response =
1272            crate::persist::chain::InternalCreateChainResponse::try_convert_from_boltz(
1273                create_response,
1274                expected_swap_id,
1275            )?;
1276
1277        let create_response_json =
1278            serde_json::to_string(&internal_create_response).map_err(|e| {
1279                PaymentError::Generic {
1280                    err: format!("Failed to serialize InternalCreateChainResponse: {e:?}"),
1281                }
1282            })?;
1283
1284        Ok(create_response_json)
1285    }
1286
1287    pub(crate) fn is_waiting_fee_acceptance(&self) -> bool {
1288        self.payer_amount_sat == 0 && self.accepted_receiver_amount_sat.is_none()
1289    }
1290}
1291
1292#[derive(Clone, Debug, Default)]
1293pub(crate) struct ChainSwapUpdate {
1294    pub(crate) swap_id: String,
1295    pub(crate) to_state: PaymentState,
1296    pub(crate) server_lockup_tx_id: Option<String>,
1297    pub(crate) user_lockup_tx_id: Option<String>,
1298    pub(crate) claim_address: Option<String>,
1299    pub(crate) claim_tx_id: Option<String>,
1300    pub(crate) refund_tx_id: Option<String>,
1301}
1302
1303/// A submarine swap, used for Send
1304#[derive(Clone, Debug, Derivative)]
1305#[derivative(PartialEq)]
1306pub struct SendSwap {
1307    pub(crate) id: String,
1308    /// Bolt11 or Bolt12 invoice. This is determined by whether `bolt12_offer` is set or not.
1309    pub(crate) invoice: String,
1310    /// The bolt12 offer, if this swap sends to a Bolt12 offer
1311    pub(crate) bolt12_offer: Option<String>,
1312    pub(crate) payment_hash: Option<String>,
1313    pub(crate) destination_pubkey: Option<String>,
1314    pub(crate) description: Option<String>,
1315    pub(crate) preimage: Option<String>,
1316    pub(crate) payer_amount_sat: u64,
1317    pub(crate) receiver_amount_sat: u64,
1318    /// The [SubmarinePair] chosen on swap creation
1319    pub(crate) pair_fees_json: String,
1320    /// JSON representation of [crate::persist::send::InternalCreateSubmarineResponse]
1321    pub(crate) create_response_json: String,
1322    /// Persisted only when the lockup tx is successfully broadcast
1323    pub(crate) lockup_tx_id: Option<String>,
1324    /// Persisted only when a refund tx is needed
1325    pub(crate) refund_address: Option<String>,
1326    /// Persisted after the refund tx is broadcast
1327    pub(crate) refund_tx_id: Option<String>,
1328    pub(crate) created_at: u32,
1329    pub(crate) timeout_block_height: u64,
1330    pub(crate) state: PaymentState,
1331    pub(crate) refund_private_key: String,
1332    /// Swap metadata that is only valid when reading one from the local database
1333    #[derivative(PartialEq = "ignore")]
1334    pub(crate) metadata: SwapMetadata,
1335}
1336impl SendSwap {
1337    pub(crate) fn get_refund_keypair(&self) -> Result<Keypair, SdkError> {
1338        utils::decode_keypair(&self.refund_private_key)
1339    }
1340
1341    pub(crate) fn get_boltz_create_response(&self) -> Result<CreateSubmarineResponse> {
1342        let internal_create_response: crate::persist::send::InternalCreateSubmarineResponse =
1343            serde_json::from_str(&self.create_response_json).map_err(|e| {
1344                anyhow!("Failed to deserialize InternalCreateSubmarineResponse: {e:?}")
1345            })?;
1346
1347        let res = CreateSubmarineResponse {
1348            id: self.id.clone(),
1349            accept_zero_conf: internal_create_response.accept_zero_conf,
1350            address: internal_create_response.address.clone(),
1351            bip21: internal_create_response.bip21.clone(),
1352            claim_public_key: crate::utils::json_to_pubkey(
1353                &internal_create_response.claim_public_key,
1354            )?,
1355            expected_amount: internal_create_response.expected_amount,
1356            referral_id: internal_create_response.referral_id,
1357            swap_tree: internal_create_response.swap_tree.clone().into(),
1358            timeout_block_height: internal_create_response.timeout_block_height,
1359            blinding_key: internal_create_response.blinding_key.clone(),
1360        };
1361        Ok(res)
1362    }
1363
1364    pub(crate) fn get_swap_script(&self) -> Result<LBtcSwapScript, SdkError> {
1365        LBtcSwapScript::submarine_from_swap_resp(
1366            &self.get_boltz_create_response()?,
1367            self.get_refund_keypair()?.public_key().into(),
1368        )
1369        .map_err(|e| {
1370            SdkError::generic(format!(
1371                "Failed to create swap script for Send Swap {}: {e:?}",
1372                self.id
1373            ))
1374        })
1375    }
1376
1377    pub(crate) fn from_boltz_struct_to_json(
1378        create_response: &CreateSubmarineResponse,
1379        expected_swap_id: &str,
1380    ) -> Result<String, PaymentError> {
1381        let internal_create_response =
1382            crate::persist::send::InternalCreateSubmarineResponse::try_convert_from_boltz(
1383                create_response,
1384                expected_swap_id,
1385            )?;
1386
1387        let create_response_json =
1388            serde_json::to_string(&internal_create_response).map_err(|e| {
1389                PaymentError::Generic {
1390                    err: format!("Failed to serialize InternalCreateSubmarineResponse: {e:?}"),
1391                }
1392            })?;
1393
1394        Ok(create_response_json)
1395    }
1396}
1397
1398/// A reverse swap, used for Receive
1399#[derive(Clone, Debug, Derivative)]
1400#[derivative(PartialEq)]
1401pub struct ReceiveSwap {
1402    pub(crate) id: String,
1403    pub(crate) preimage: String,
1404    /// JSON representation of [crate::persist::receive::InternalCreateReverseResponse]
1405    pub(crate) create_response_json: String,
1406    pub(crate) claim_private_key: String,
1407    pub(crate) invoice: String,
1408    /// The bolt12 offer, if used to create the swap
1409    pub(crate) bolt12_offer: Option<String>,
1410    pub(crate) payment_hash: Option<String>,
1411    pub(crate) destination_pubkey: Option<String>,
1412    pub(crate) description: Option<String>,
1413    /// The amount of the invoice
1414    pub(crate) payer_amount_sat: u64,
1415    pub(crate) receiver_amount_sat: u64,
1416    /// The [ReversePair] chosen on swap creation
1417    pub(crate) pair_fees_json: String,
1418    pub(crate) claim_fees_sat: u64,
1419    /// Persisted only when a claim tx is needed
1420    pub(crate) claim_address: Option<String>,
1421    /// Persisted after the claim tx is broadcast
1422    pub(crate) claim_tx_id: Option<String>,
1423    /// The transaction id of the swapper's tx broadcast
1424    pub(crate) lockup_tx_id: Option<String>,
1425    /// The address reserved for a magic routing hint payment
1426    pub(crate) mrh_address: String,
1427    /// Persisted only if a transaction is sent to the `mrh_address`
1428    pub(crate) mrh_tx_id: Option<String>,
1429    /// Until the lockup tx is seen in the mempool, it contains the swap creation time.
1430    /// Afterwards, it shows the lockup tx creation time.
1431    pub(crate) created_at: u32,
1432    pub(crate) timeout_block_height: u32,
1433    pub(crate) state: PaymentState,
1434    /// Swap metadata that is only valid when reading one from the local database
1435    #[derivative(PartialEq = "ignore")]
1436    pub(crate) metadata: SwapMetadata,
1437}
1438impl ReceiveSwap {
1439    pub(crate) fn get_claim_keypair(&self) -> Result<Keypair, PaymentError> {
1440        utils::decode_keypair(&self.claim_private_key).map_err(Into::into)
1441    }
1442
1443    pub(crate) fn claim_script(&self) -> Result<elements::Script> {
1444        Ok(self
1445            .get_swap_script()?
1446            .funding_addrs
1447            .ok_or(anyhow!("No funding address found"))?
1448            .script_pubkey())
1449    }
1450
1451    pub(crate) fn get_boltz_create_response(&self) -> Result<CreateReverseResponse, PaymentError> {
1452        let internal_create_response: crate::persist::receive::InternalCreateReverseResponse =
1453            serde_json::from_str(&self.create_response_json).map_err(|e| {
1454                PaymentError::Generic {
1455                    err: format!("Failed to deserialize InternalCreateReverseResponse: {e:?}"),
1456                }
1457            })?;
1458
1459        let res = CreateReverseResponse {
1460            id: self.id.clone(),
1461            invoice: Some(self.invoice.clone()),
1462            swap_tree: internal_create_response.swap_tree.clone().into(),
1463            lockup_address: internal_create_response.lockup_address.clone(),
1464            refund_public_key: crate::utils::json_to_pubkey(
1465                &internal_create_response.refund_public_key,
1466            )?,
1467            timeout_block_height: internal_create_response.timeout_block_height,
1468            onchain_amount: internal_create_response.onchain_amount,
1469            blinding_key: internal_create_response.blinding_key.clone(),
1470        };
1471        Ok(res)
1472    }
1473
1474    pub(crate) fn get_swap_script(&self) -> Result<LBtcSwapScript, PaymentError> {
1475        let keypair = self.get_claim_keypair()?;
1476        let create_response =
1477            self.get_boltz_create_response()
1478                .map_err(|e| PaymentError::Generic {
1479                    err: format!(
1480                        "Failed to create swap script for Receive Swap {}: {e:?}",
1481                        self.id
1482                    ),
1483                })?;
1484        LBtcSwapScript::reverse_from_swap_resp(&create_response, keypair.public_key().into())
1485            .map_err(|e| PaymentError::Generic {
1486                err: format!(
1487                    "Failed to create swap script for Receive Swap {}: {e:?}",
1488                    self.id
1489                ),
1490            })
1491    }
1492
1493    pub(crate) fn from_boltz_struct_to_json(
1494        create_response: &CreateReverseResponse,
1495        expected_swap_id: &str,
1496        expected_invoice: Option<&str>,
1497    ) -> Result<String, PaymentError> {
1498        let internal_create_response =
1499            crate::persist::receive::InternalCreateReverseResponse::try_convert_from_boltz(
1500                create_response,
1501                expected_swap_id,
1502                expected_invoice,
1503            )?;
1504
1505        let create_response_json =
1506            serde_json::to_string(&internal_create_response).map_err(|e| {
1507                PaymentError::Generic {
1508                    err: format!("Failed to serialize InternalCreateReverseResponse: {e:?}"),
1509                }
1510            })?;
1511
1512        Ok(create_response_json)
1513    }
1514}
1515
1516/// Returned when calling [crate::sdk::LiquidSdk::list_refundables].
1517#[derive(Clone, Debug, PartialEq, Serialize)]
1518pub struct RefundableSwap {
1519    pub swap_address: String,
1520    pub timestamp: u32,
1521    /// Amount that is refundable, from all UTXOs
1522    pub amount_sat: u64,
1523    /// The txid of the last broadcasted refund tx, if any
1524    pub last_refund_tx_id: Option<String>,
1525}
1526
1527/// A BOLT12 offer
1528#[derive(Clone, Debug, Derivative)]
1529#[derivative(PartialEq)]
1530pub(crate) struct Bolt12Offer {
1531    /// The BOLT12 offer string
1532    pub(crate) id: String,
1533    /// The description of the offer
1534    pub(crate) description: String,
1535    /// The private key used to create the offer
1536    pub(crate) private_key: String,
1537    /// The optional webhook URL where the offer invoice request will be sent
1538    pub(crate) webhook_url: Option<String>,
1539    /// The creation time of the offer
1540    pub(crate) created_at: u32,
1541}
1542impl Bolt12Offer {
1543    pub(crate) fn get_keypair(&self) -> Result<Keypair, SdkError> {
1544        utils::decode_keypair(&self.private_key)
1545    }
1546}
1547impl TryFrom<Bolt12Offer> for Offer {
1548    type Error = SdkError;
1549
1550    fn try_from(val: Bolt12Offer) -> Result<Self, Self::Error> {
1551        Offer::from_str(&val.id)
1552            .map_err(|e| SdkError::generic(format!("Failed to parse BOLT12 offer: {e:?}")))
1553    }
1554}
1555
1556/// The payment state of an individual payment.
1557#[derive(Clone, Copy, Debug, Default, EnumString, Eq, PartialEq, Serialize, Hash)]
1558#[strum(serialize_all = "lowercase")]
1559pub enum PaymentState {
1560    #[default]
1561    Created = 0,
1562
1563    /// ## Receive Swaps
1564    ///
1565    /// Covers the cases when
1566    /// - the lockup tx is seen in the mempool or
1567    /// - our claim tx is broadcast
1568    ///
1569    /// When the claim tx is broadcast, `claim_tx_id` is set in the swap.
1570    ///
1571    /// ## Send Swaps
1572    ///
1573    /// This is the status when our lockup tx was broadcast
1574    ///
1575    /// ## Chain Swaps
1576    ///
1577    /// This is the status when the user lockup tx was broadcast
1578    ///
1579    /// ## No swap data available
1580    ///
1581    /// If no associated swap is found, this indicates the underlying tx is not confirmed yet.
1582    Pending = 1,
1583
1584    /// ## Receive Swaps
1585    ///
1586    /// Covers the case when the claim tx is confirmed.
1587    ///
1588    /// ## Send and Chain Swaps
1589    ///
1590    /// This is the status when the claim tx is broadcast and we see it in the mempool.
1591    ///
1592    /// ## No swap data available
1593    ///
1594    /// If no associated swap is found, this indicates the underlying tx is confirmed.
1595    Complete = 2,
1596
1597    /// ## Receive Swaps
1598    ///
1599    /// This is the status when the swap failed for any reason and the Receive could not complete.
1600    ///
1601    /// ## Send and Chain Swaps
1602    ///
1603    /// This is the status when a swap refund was initiated and the refund tx is confirmed.
1604    Failed = 3,
1605
1606    /// ## Send and Outgoing Chain Swaps
1607    ///
1608    /// This covers the case when the swap state is still Created and the swap fails to reach the
1609    /// Pending state in time. The TimedOut state indicates the lockup tx should never be broadcast.
1610    TimedOut = 4,
1611
1612    /// ## Incoming Chain Swaps
1613    ///
1614    /// This covers the case when the swap failed for any reason and there is a user lockup tx.
1615    /// The swap in this case has to be manually refunded with a provided Bitcoin address
1616    Refundable = 5,
1617
1618    /// ## Send and Chain Swaps
1619    ///
1620    /// This is the status when a refund was initiated and/or our refund tx was broadcast
1621    ///
1622    /// When the refund tx is broadcast, `refund_tx_id` is set in the swap.
1623    RefundPending = 6,
1624
1625    /// ## Chain Swaps
1626    ///
1627    /// This is the state when the user needs to accept new fees before the payment can proceed.
1628    ///
1629    /// Use [LiquidSdk::fetch_payment_proposed_fees](crate::sdk::LiquidSdk::fetch_payment_proposed_fees)
1630    /// to find out the current fees and
1631    /// [LiquidSdk::accept_payment_proposed_fees](crate::sdk::LiquidSdk::accept_payment_proposed_fees)
1632    /// to accept them, allowing the payment to proceed.
1633    ///
1634    /// Otherwise, this payment can be immediately refunded using
1635    /// [prepare_refund](crate::sdk::LiquidSdk::prepare_refund)/[refund](crate::sdk::LiquidSdk::refund).
1636    WaitingFeeAcceptance = 7,
1637}
1638
1639impl ToSql for PaymentState {
1640    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1641        Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1642    }
1643}
1644impl FromSql for PaymentState {
1645    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1646        match value {
1647            ValueRef::Integer(i) => match i as u8 {
1648                0 => Ok(PaymentState::Created),
1649                1 => Ok(PaymentState::Pending),
1650                2 => Ok(PaymentState::Complete),
1651                3 => Ok(PaymentState::Failed),
1652                4 => Ok(PaymentState::TimedOut),
1653                5 => Ok(PaymentState::Refundable),
1654                6 => Ok(PaymentState::RefundPending),
1655                7 => Ok(PaymentState::WaitingFeeAcceptance),
1656                _ => Err(FromSqlError::OutOfRange(i)),
1657            },
1658            _ => Err(FromSqlError::InvalidType),
1659        }
1660    }
1661}
1662
1663impl PaymentState {
1664    pub(crate) fn is_refundable(&self) -> bool {
1665        matches!(
1666            self,
1667            PaymentState::Refundable
1668                | PaymentState::RefundPending
1669                | PaymentState::WaitingFeeAcceptance
1670        )
1671    }
1672}
1673
1674#[derive(Debug, Copy, Clone, Eq, EnumString, Display, Hash, PartialEq, Serialize)]
1675#[strum(serialize_all = "lowercase")]
1676pub enum PaymentType {
1677    Receive = 0,
1678    Send = 1,
1679}
1680impl From<Direction> for PaymentType {
1681    fn from(value: Direction) -> Self {
1682        match value {
1683            Direction::Incoming => Self::Receive,
1684            Direction::Outgoing => Self::Send,
1685        }
1686    }
1687}
1688impl ToSql for PaymentType {
1689    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1690        Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1691    }
1692}
1693impl FromSql for PaymentType {
1694    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1695        match value {
1696            ValueRef::Integer(i) => match i as u8 {
1697                0 => Ok(PaymentType::Receive),
1698                1 => Ok(PaymentType::Send),
1699                _ => Err(FromSqlError::OutOfRange(i)),
1700            },
1701            _ => Err(FromSqlError::InvalidType),
1702        }
1703    }
1704}
1705
1706#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
1707pub enum PaymentStatus {
1708    Pending = 0,
1709    Complete = 1,
1710}
1711impl ToSql for PaymentStatus {
1712    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
1713        Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
1714    }
1715}
1716impl FromSql for PaymentStatus {
1717    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
1718        match value {
1719            ValueRef::Integer(i) => match i as u8 {
1720                0 => Ok(PaymentStatus::Pending),
1721                1 => Ok(PaymentStatus::Complete),
1722                _ => Err(FromSqlError::OutOfRange(i)),
1723            },
1724            _ => Err(FromSqlError::InvalidType),
1725        }
1726    }
1727}
1728
1729#[derive(Debug, Clone, Serialize)]
1730pub struct PaymentTxData {
1731    /// The tx ID of the transaction
1732    pub tx_id: String,
1733
1734    /// The point in time when the underlying tx was included in a block.
1735    pub timestamp: Option<u32>,
1736
1737    /// The asset id
1738    pub asset_id: String,
1739
1740    /// The onchain tx amount.
1741    ///
1742    /// In case of an outbound payment (Send), this is the payer amount. Otherwise it's the receiver amount.
1743    pub amount: u64,
1744
1745    /// The onchain fees of this tx
1746    pub fees_sat: u64,
1747
1748    pub payment_type: PaymentType,
1749
1750    /// Onchain tx status
1751    pub is_confirmed: bool,
1752
1753    /// Data to use in the `blinded` param when unblinding the transaction in an explorer.
1754    /// See: <https://docs.liquid.net/docs/unblinding-transactions>
1755    pub unblinding_data: Option<String>,
1756}
1757
1758#[derive(Debug, Clone, Serialize)]
1759pub enum PaymentSwapType {
1760    Receive,
1761    Send,
1762    Chain,
1763}
1764
1765#[derive(Debug, Clone, Serialize)]
1766pub struct PaymentSwapData {
1767    pub swap_id: String,
1768
1769    pub swap_type: PaymentSwapType,
1770
1771    /// Swap creation timestamp
1772    pub created_at: u32,
1773
1774    /// The height of the block at which the swap will no longer be valid
1775    pub expiration_blockheight: u32,
1776
1777    pub preimage: Option<String>,
1778    pub invoice: Option<String>,
1779    pub bolt12_offer: Option<String>,
1780    pub payment_hash: Option<String>,
1781    pub destination_pubkey: Option<String>,
1782    pub description: String,
1783
1784    /// Amount sent by the swap payer
1785    pub payer_amount_sat: u64,
1786
1787    /// Amount received by the swap receiver
1788    pub receiver_amount_sat: u64,
1789
1790    /// The swapper service fee
1791    pub swapper_fees_sat: u64,
1792
1793    pub refund_tx_id: Option<String>,
1794    pub refund_tx_amount_sat: Option<u64>,
1795
1796    /// Present only for chain swaps.
1797    /// In case of an outgoing chain swap, it's the Bitcoin address which will receive the funds
1798    /// In case of an incoming chain swap, it's the Liquid address which will receive the funds
1799    pub claim_address: Option<String>,
1800
1801    /// Payment status derived from the swap status
1802    pub status: PaymentState,
1803}
1804
1805/// Represents the payment LNURL info
1806#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
1807pub struct LnUrlInfo {
1808    pub ln_address: Option<String>,
1809    pub lnurl_pay_comment: Option<String>,
1810    pub lnurl_pay_domain: Option<String>,
1811    pub lnurl_pay_metadata: Option<String>,
1812    pub lnurl_pay_success_action: Option<SuccessActionProcessed>,
1813    pub lnurl_pay_unprocessed_success_action: Option<SuccessAction>,
1814    pub lnurl_withdraw_endpoint: Option<String>,
1815}
1816
1817/// Configuration for asset metadata. Each asset metadata item represents an entry in the
1818/// [Liquid Asset Registry](https://docs.liquid.net/docs/blockstream-liquid-asset-registry).
1819/// An example Liquid Asset in the registry would be [Tether USD](https://assets.blockstream.info/ce091c998b83c78bb71a632313ba3760f1763d9cfcffae02258ffa9865a37bd2.json>).
1820#[derive(Debug, Clone, Serialize)]
1821pub struct AssetMetadata {
1822    /// The asset id of the registered asset
1823    pub asset_id: String,
1824    /// The name of the asset
1825    pub name: String,
1826    /// The ticker of the asset
1827    pub ticker: String,
1828    /// The precision used to display the asset amount.
1829    /// For example, precision of 2 shifts the decimal 2 places left from the satoshi amount.
1830    pub precision: u8,
1831    /// The optional ID of the fiat currency used to represent the asset
1832    pub fiat_id: Option<String>,
1833}
1834
1835impl AssetMetadata {
1836    pub fn amount_to_sat(&self, amount: f64) -> u64 {
1837        (amount * (10_u64.pow(self.precision.into()) as f64)) as u64
1838    }
1839
1840    pub fn amount_from_sat(&self, amount_sat: u64) -> f64 {
1841        amount_sat as f64 / (10_u64.pow(self.precision.into()) as f64)
1842    }
1843}
1844
1845/// Represents the Liquid payment asset info. The asset info is derived from
1846/// the available [AssetMetadata] that is set in the [Config].
1847#[derive(Clone, Debug, PartialEq, Serialize)]
1848pub struct AssetInfo {
1849    /// The name of the asset
1850    pub name: String,
1851    /// The ticker of the asset
1852    pub ticker: String,
1853    /// The amount calculated from the satoshi amount of the transaction, having its
1854    /// decimal shifted to the left by the [precision](AssetMetadata::precision)
1855    pub amount: f64,
1856    /// The optional fees when paid using the asset, having its
1857    /// decimal shifted to the left by the [precision](AssetMetadata::precision)
1858    pub fees: Option<f64>,
1859}
1860
1861/// The specific details of a payment, depending on its type
1862#[derive(Debug, Clone, PartialEq, Serialize)]
1863#[allow(clippy::large_enum_variant)]
1864pub enum PaymentDetails {
1865    /// Swapping to or from Lightning
1866    Lightning {
1867        swap_id: String,
1868
1869        /// Represents the invoice description
1870        description: String,
1871
1872        /// The height of the block at which the swap will no longer be valid
1873        liquid_expiration_blockheight: u32,
1874
1875        /// The preimage of the paid invoice (proof of payment).
1876        preimage: Option<String>,
1877
1878        /// Represents the Bolt11/Bolt12 invoice associated with a payment
1879        /// In the case of a Send payment, this is the invoice paid by the swapper
1880        /// In the case of a Receive payment, this is the invoice paid by the user
1881        invoice: Option<String>,
1882
1883        bolt12_offer: Option<String>,
1884
1885        /// The payment hash of the invoice
1886        payment_hash: Option<String>,
1887
1888        /// The invoice destination/payee pubkey
1889        destination_pubkey: Option<String>,
1890
1891        /// The payment LNURL info
1892        lnurl_info: Option<LnUrlInfo>,
1893
1894        /// The BIP353 address used to resolve this payment
1895        bip353_address: Option<String>,
1896
1897        /// For a Receive payment, this is the claim tx id in case it has already been broadcast
1898        claim_tx_id: Option<String>,
1899
1900        /// For a Send swap which was refunded, this is the refund tx id
1901        refund_tx_id: Option<String>,
1902
1903        /// For a Send swap which was refunded, this is the refund amount
1904        refund_tx_amount_sat: Option<u64>,
1905    },
1906    /// Direct onchain payment to a Liquid address
1907    Liquid {
1908        /// Represents either a Liquid BIP21 URI or pure address
1909        destination: String,
1910
1911        /// Represents the BIP21 `message` field
1912        description: String,
1913
1914        /// The asset id
1915        asset_id: String,
1916
1917        /// The asset info derived from the [AssetMetadata]
1918        asset_info: Option<AssetInfo>,
1919
1920        /// The payment LNURL info
1921        lnurl_info: Option<LnUrlInfo>,
1922
1923        /// The BIP353 address used to resolve this payment
1924        bip353_address: Option<String>,
1925    },
1926    /// Swapping to or from the Bitcoin chain
1927    Bitcoin {
1928        swap_id: String,
1929
1930        /// Represents the invoice description
1931        description: String,
1932
1933        /// For an amountless receive swap, this indicates if fees were automatically accepted.
1934        /// Fees are auto accepted when the swapper proposes fees that are within the initial
1935        /// estimate, plus the `onchain_fee_rate_leeway_sat_per_vbyte` set in the [Config], if any.
1936        auto_accepted_fees: bool,
1937
1938        /// The height of the Liquid block at which the swap will no longer be valid
1939        /// It should always be populated in case of an outgoing chain swap
1940        liquid_expiration_blockheight: Option<u32>,
1941
1942        /// The height of the Bitcoin block at which the swap will no longer be valid
1943        /// It should always be populated in case of an incoming chain swap
1944        bitcoin_expiration_blockheight: Option<u32>,
1945
1946        /// The claim tx id in case it has already been broadcast
1947        claim_tx_id: Option<String>,
1948
1949        /// For a Send swap which was refunded, this is the refund tx id
1950        refund_tx_id: Option<String>,
1951
1952        /// For a Send swap which was refunded, this is the refund amount
1953        refund_tx_amount_sat: Option<u64>,
1954    },
1955}
1956
1957impl PaymentDetails {
1958    pub(crate) fn get_swap_id(&self) -> Option<String> {
1959        match self {
1960            Self::Lightning { swap_id, .. } | Self::Bitcoin { swap_id, .. } => {
1961                Some(swap_id.clone())
1962            }
1963            Self::Liquid { .. } => None,
1964        }
1965    }
1966
1967    pub(crate) fn get_refund_tx_amount_sat(&self) -> Option<u64> {
1968        match self {
1969            Self::Lightning {
1970                refund_tx_amount_sat,
1971                ..
1972            }
1973            | Self::Bitcoin {
1974                refund_tx_amount_sat,
1975                ..
1976            } => *refund_tx_amount_sat,
1977            Self::Liquid { .. } => None,
1978        }
1979    }
1980
1981    pub(crate) fn get_description(&self) -> Option<String> {
1982        match self {
1983            Self::Lightning { description, .. }
1984            | Self::Bitcoin { description, .. }
1985            | Self::Liquid { description, .. } => Some(description.clone()),
1986        }
1987    }
1988
1989    pub(crate) fn is_lbtc_asset_id(&self, network: LiquidNetwork) -> bool {
1990        match self {
1991            Self::Liquid { asset_id, .. } => {
1992                asset_id.eq(&utils::lbtc_asset_id(network).to_string())
1993            }
1994            _ => true,
1995        }
1996    }
1997}
1998
1999/// Represents an SDK payment.
2000///
2001/// By default, this is an onchain tx. It may represent a swap, if swap metadata is available.
2002#[derive(Debug, Clone, PartialEq, Serialize)]
2003pub struct Payment {
2004    /// The destination associated with the payment, if it was created via our SDK.
2005    /// Can be either a Liquid/Bitcoin address, a Liquid BIP21 URI or an invoice
2006    pub destination: Option<String>,
2007
2008    pub tx_id: Option<String>,
2009
2010    /// Data to use in the `blinded` param when unblinding the transaction in an explorer.
2011    /// See: <https://docs.liquid.net/docs/unblinding-transactions>
2012    pub unblinding_data: Option<String>,
2013
2014    /// Composite timestamp that can be used for sorting or displaying the payment.
2015    ///
2016    /// If this payment has an associated swap, it is the swap creation time. Otherwise, the point
2017    /// in time when the underlying tx was included in a block. If there is no associated swap
2018    /// available and the underlying tx is not yet confirmed, the value is `now()`.
2019    pub timestamp: u32,
2020
2021    /// The payment amount, which corresponds to the onchain tx amount.
2022    ///
2023    /// In case of an outbound payment (Send), this is the payer amount. Otherwise it's the receiver amount.
2024    pub amount_sat: u64,
2025
2026    /// Represents the fees paid by this wallet for this payment.
2027    ///
2028    /// ### Swaps
2029    /// If there is an associated Send Swap, these fees represent the total fees paid by this wallet
2030    /// (the sender). It is the difference between the amount that was sent and the amount received.
2031    ///
2032    /// If there is an associated Receive Swap, these fees represent the total fees paid by this wallet
2033    /// (the receiver). It is also the difference between the amount that was sent and the amount received.
2034    ///
2035    /// ### Pure onchain txs
2036    /// If no swap is associated with this payment:
2037    /// - for Send payments, this is the onchain tx fee
2038    /// - for Receive payments, this is zero
2039    pub fees_sat: u64,
2040
2041    /// Service fees paid to the swapper service. This is only set for swaps (i.e. doesn't apply to
2042    /// direct Liquid payments).
2043    pub swapper_fees_sat: Option<u64>,
2044
2045    /// If it is a `Send` or `Receive` payment
2046    pub payment_type: PaymentType,
2047
2048    /// Composite status representing the overall status of the payment.
2049    ///
2050    /// If the tx has no associated swap, this reflects the onchain tx status (confirmed or not).
2051    ///
2052    /// If the tx has an associated swap, this is determined by the swap status (pending or complete).
2053    pub status: PaymentState,
2054
2055    /// The details of a payment, depending on its [destination](Payment::destination) and
2056    /// [type](Payment::payment_type)
2057    pub details: PaymentDetails,
2058}
2059impl Payment {
2060    pub(crate) fn from_pending_swap(
2061        swap: PaymentSwapData,
2062        payment_type: PaymentType,
2063        payment_details: PaymentDetails,
2064    ) -> Payment {
2065        let amount_sat = match payment_type {
2066            PaymentType::Receive => swap.receiver_amount_sat,
2067            PaymentType::Send => swap.payer_amount_sat,
2068        };
2069
2070        Payment {
2071            destination: swap.invoice.clone(),
2072            tx_id: None,
2073            unblinding_data: None,
2074            timestamp: swap.created_at,
2075            amount_sat,
2076            fees_sat: swap
2077                .payer_amount_sat
2078                .saturating_sub(swap.receiver_amount_sat),
2079            swapper_fees_sat: Some(swap.swapper_fees_sat),
2080            payment_type,
2081            status: swap.status,
2082            details: payment_details,
2083        }
2084    }
2085
2086    pub(crate) fn from_tx_data(
2087        tx: PaymentTxData,
2088        swap: Option<PaymentSwapData>,
2089        details: PaymentDetails,
2090    ) -> Payment {
2091        let (amount_sat, fees_sat) = match swap.as_ref() {
2092            Some(s) => match tx.payment_type {
2093                // For receive swaps, to avoid some edge case issues related to potential past
2094                // overpayments, we use the actual claim value as the final received amount
2095                // for fee calculation.
2096                PaymentType::Receive => (tx.amount, s.payer_amount_sat.saturating_sub(tx.amount)),
2097                PaymentType::Send => (
2098                    s.receiver_amount_sat,
2099                    s.payer_amount_sat.saturating_sub(s.receiver_amount_sat),
2100                ),
2101            },
2102            None => {
2103                let (amount_sat, fees_sat) = match tx.payment_type {
2104                    PaymentType::Receive => (tx.amount, 0),
2105                    PaymentType::Send => (tx.amount, tx.fees_sat),
2106                };
2107                // If the payment is a Liquid payment, we only show the amount if the asset
2108                // is LBTC and only show the fees if the asset info has no set fees
2109                match details {
2110                    PaymentDetails::Liquid {
2111                        asset_info: Some(ref asset_info),
2112                        ..
2113                    } if asset_info.ticker != "BTC" => (0, asset_info.fees.map_or(fees_sat, |_| 0)),
2114                    _ => (amount_sat, fees_sat),
2115                }
2116            }
2117        };
2118        Payment {
2119            tx_id: Some(tx.tx_id),
2120            unblinding_data: tx.unblinding_data,
2121            // When the swap is present and of type send and receive, we retrieve the destination from the invoice.
2122            // If it's a chain swap instead, we use the `claim_address` field from the swap data (either pure Bitcoin or Liquid address).
2123            // Otherwise, we specify the Liquid address (BIP21 or pure), set in `payment_details.address`.
2124            destination: match &swap {
2125                Some(PaymentSwapData {
2126                    swap_type: PaymentSwapType::Receive,
2127                    invoice,
2128                    ..
2129                }) => invoice.clone(),
2130                Some(PaymentSwapData {
2131                    swap_type: PaymentSwapType::Send,
2132                    invoice,
2133                    bolt12_offer,
2134                    ..
2135                }) => bolt12_offer.clone().or(invoice.clone()),
2136                Some(PaymentSwapData {
2137                    swap_type: PaymentSwapType::Chain,
2138                    claim_address,
2139                    ..
2140                }) => claim_address.clone(),
2141                _ => match &details {
2142                    PaymentDetails::Liquid { destination, .. } => Some(destination.clone()),
2143                    _ => None,
2144                },
2145            },
2146            timestamp: tx
2147                .timestamp
2148                .or(swap.as_ref().map(|s| s.created_at))
2149                .unwrap_or(utils::now()),
2150            amount_sat,
2151            fees_sat,
2152            swapper_fees_sat: swap.as_ref().map(|s| s.swapper_fees_sat),
2153            payment_type: tx.payment_type,
2154            status: match &swap {
2155                Some(swap) => swap.status,
2156                None => match tx.is_confirmed {
2157                    true => PaymentState::Complete,
2158                    false => PaymentState::Pending,
2159                },
2160            },
2161            details,
2162        }
2163    }
2164
2165    pub(crate) fn get_refund_tx_id(&self) -> Option<String> {
2166        match self.details.clone() {
2167            PaymentDetails::Lightning { refund_tx_id, .. } => Some(refund_tx_id),
2168            PaymentDetails::Bitcoin { refund_tx_id, .. } => Some(refund_tx_id),
2169            PaymentDetails::Liquid { .. } => None,
2170        }
2171        .flatten()
2172    }
2173}
2174
2175/// Returned when calling [crate::sdk::LiquidSdk::recommended_fees].
2176#[derive(Deserialize, Serialize, Clone, Debug)]
2177#[serde(rename_all = "camelCase")]
2178pub struct RecommendedFees {
2179    pub fastest_fee: u64,
2180    pub half_hour_fee: u64,
2181    pub hour_fee: u64,
2182    pub economy_fee: u64,
2183    pub minimum_fee: u64,
2184}
2185
2186/// An argument of [PrepareBuyBitcoinRequest] when calling [crate::sdk::LiquidSdk::prepare_buy_bitcoin].
2187#[derive(Debug, Clone, Copy, EnumString, PartialEq, Serialize)]
2188pub enum BuyBitcoinProvider {
2189    #[strum(serialize = "moonpay")]
2190    Moonpay,
2191}
2192
2193/// An argument when calling [crate::sdk::LiquidSdk::prepare_buy_bitcoin].
2194#[derive(Debug, Serialize)]
2195pub struct PrepareBuyBitcoinRequest {
2196    pub provider: BuyBitcoinProvider,
2197    pub amount_sat: u64,
2198}
2199
2200/// Returned when calling [crate::sdk::LiquidSdk::prepare_buy_bitcoin].
2201#[derive(Clone, Debug, Serialize)]
2202pub struct PrepareBuyBitcoinResponse {
2203    pub provider: BuyBitcoinProvider,
2204    pub amount_sat: u64,
2205    pub fees_sat: u64,
2206}
2207
2208/// An argument when calling [crate::sdk::LiquidSdk::buy_bitcoin].
2209#[derive(Clone, Debug, Serialize)]
2210pub struct BuyBitcoinRequest {
2211    pub prepare_response: PrepareBuyBitcoinResponse,
2212
2213    /// The optional URL to redirect to after completing the buy.
2214    ///
2215    /// For Moonpay, see <https://dev.moonpay.com/docs/on-ramp-configure-user-journey-params>
2216    pub redirect_url: Option<String>,
2217}
2218
2219/// Internal SDK log entry used in the Uniffi and Dart bindings
2220#[derive(Clone, Debug)]
2221pub struct LogEntry {
2222    pub line: String,
2223    pub level: String,
2224}
2225
2226#[derive(Clone, Debug, Serialize, Deserialize)]
2227struct InternalLeaf {
2228    pub output: String,
2229    pub version: u8,
2230}
2231impl From<InternalLeaf> for Leaf {
2232    fn from(value: InternalLeaf) -> Self {
2233        Leaf {
2234            output: value.output,
2235            version: value.version,
2236        }
2237    }
2238}
2239impl From<Leaf> for InternalLeaf {
2240    fn from(value: Leaf) -> Self {
2241        InternalLeaf {
2242            output: value.output,
2243            version: value.version,
2244        }
2245    }
2246}
2247
2248#[derive(Clone, Debug, Serialize, Deserialize)]
2249pub(super) struct InternalSwapTree {
2250    claim_leaf: InternalLeaf,
2251    refund_leaf: InternalLeaf,
2252}
2253impl From<InternalSwapTree> for SwapTree {
2254    fn from(value: InternalSwapTree) -> Self {
2255        SwapTree {
2256            claim_leaf: value.claim_leaf.into(),
2257            refund_leaf: value.refund_leaf.into(),
2258        }
2259    }
2260}
2261impl From<SwapTree> for InternalSwapTree {
2262    fn from(value: SwapTree) -> Self {
2263        InternalSwapTree {
2264            claim_leaf: value.claim_leaf.into(),
2265            refund_leaf: value.refund_leaf.into(),
2266        }
2267    }
2268}
2269
2270/// An argument when calling [crate::sdk::LiquidSdk::prepare_lnurl_pay].
2271#[derive(Debug, Serialize)]
2272pub struct PrepareLnUrlPayRequest {
2273    /// The [LnUrlPayRequestData] returned by [parse]
2274    pub data: LnUrlPayRequestData,
2275    /// The amount to send
2276    pub amount: PayAmount,
2277    /// A BIP353 address, in case one was used in order to fetch the LNURL Pay request data.
2278    /// Returned by [parse].
2279    pub bip353_address: Option<String>,
2280    /// An optional comment for this payment
2281    pub comment: Option<String>,
2282    /// Validates that, if there is a URL success action, the URL domain matches
2283    /// the LNURL callback domain. Defaults to `true`
2284    pub validate_success_action_url: Option<bool>,
2285}
2286
2287/// Returned when calling [crate::sdk::LiquidSdk::prepare_lnurl_pay].
2288#[derive(Debug, Serialize)]
2289pub struct PrepareLnUrlPayResponse {
2290    /// The destination of the payment
2291    pub destination: SendDestination,
2292    /// The fees in satoshis to send the payment
2293    pub fees_sat: u64,
2294    /// The [LnUrlPayRequestData] returned by [parse]
2295    pub data: LnUrlPayRequestData,
2296    /// An optional comment for this payment
2297    pub comment: Option<String>,
2298    /// The unprocessed LUD-09 success action. This will be processed and decrypted if
2299    /// needed after calling [crate::sdk::LiquidSdk::lnurl_pay]
2300    pub success_action: Option<SuccessAction>,
2301}
2302
2303/// An argument when calling [crate::sdk::LiquidSdk::lnurl_pay].
2304#[derive(Debug, Serialize)]
2305pub struct LnUrlPayRequest {
2306    /// The response from calling [crate::sdk::LiquidSdk::prepare_lnurl_pay]
2307    pub prepare_response: PrepareLnUrlPayResponse,
2308}
2309
2310/// Contains the result of the entire LNURL-pay interaction, as reported by the LNURL endpoint.
2311///
2312/// * `EndpointSuccess` indicates the payment is complete. The endpoint may return a `SuccessActionProcessed`,
2313///   in which case, the wallet has to present it to the user as described in
2314///   <https://github.com/lnurl/luds/blob/luds/09.md>
2315///
2316/// * `EndpointError` indicates a generic issue the LNURL endpoint encountered, including a freetext
2317///   field with the reason.
2318///
2319/// * `PayError` indicates that an error occurred while trying to pay the invoice from the LNURL endpoint.
2320///   This includes the payment hash of the failed invoice and the failure reason.
2321#[derive(Serialize)]
2322#[allow(clippy::large_enum_variant)]
2323pub enum LnUrlPayResult {
2324    EndpointSuccess { data: LnUrlPaySuccessData },
2325    EndpointError { data: LnUrlErrorData },
2326    PayError { data: LnUrlPayErrorData },
2327}
2328
2329#[derive(Serialize)]
2330pub struct LnUrlPaySuccessData {
2331    pub payment: Payment,
2332    pub success_action: Option<SuccessActionProcessed>,
2333}
2334
2335#[derive(Debug, Clone)]
2336pub enum Transaction {
2337    Liquid(boltz_client::elements::Transaction),
2338    Bitcoin(boltz_client::bitcoin::Transaction),
2339}
2340
2341impl Transaction {
2342    pub(crate) fn txid(&self) -> String {
2343        match self {
2344            Transaction::Liquid(tx) => tx.txid().to_hex(),
2345            Transaction::Bitcoin(tx) => tx.compute_txid().to_hex(),
2346        }
2347    }
2348}
2349
2350#[derive(Debug, Clone)]
2351pub enum Utxo {
2352    Liquid(
2353        Box<(
2354            boltz_client::elements::OutPoint,
2355            boltz_client::elements::TxOut,
2356        )>,
2357    ),
2358    Bitcoin(
2359        (
2360            boltz_client::bitcoin::OutPoint,
2361            boltz_client::bitcoin::TxOut,
2362        ),
2363    ),
2364}
2365
2366impl Utxo {
2367    pub(crate) fn as_bitcoin(
2368        &self,
2369    ) -> Option<&(
2370        boltz_client::bitcoin::OutPoint,
2371        boltz_client::bitcoin::TxOut,
2372    )> {
2373        match self {
2374            Utxo::Liquid(_) => None,
2375            Utxo::Bitcoin(utxo) => Some(utxo),
2376        }
2377    }
2378
2379    pub(crate) fn as_liquid(
2380        &self,
2381    ) -> Option<
2382        Box<(
2383            boltz_client::elements::OutPoint,
2384            boltz_client::elements::TxOut,
2385        )>,
2386    > {
2387        match self {
2388            Utxo::Bitcoin(_) => None,
2389            Utxo::Liquid(utxo) => Some(utxo.clone()),
2390        }
2391    }
2392}
2393
2394/// An argument when calling [crate::sdk::LiquidSdk::fetch_payment_proposed_fees].
2395#[derive(Debug, Clone)]
2396pub struct FetchPaymentProposedFeesRequest {
2397    pub swap_id: String,
2398}
2399
2400/// Returned when calling [crate::sdk::LiquidSdk::fetch_payment_proposed_fees].
2401#[derive(Debug, Clone, Serialize)]
2402pub struct FetchPaymentProposedFeesResponse {
2403    pub swap_id: String,
2404    pub fees_sat: u64,
2405    /// Amount sent by the swap payer
2406    pub payer_amount_sat: u64,
2407    /// Amount that will be received if these fees are accepted
2408    pub receiver_amount_sat: u64,
2409}
2410
2411/// An argument when calling [crate::sdk::LiquidSdk::accept_payment_proposed_fees].
2412#[derive(Debug, Clone)]
2413pub struct AcceptPaymentProposedFeesRequest {
2414    pub response: FetchPaymentProposedFeesResponse,
2415}
2416
2417#[derive(Clone, Debug)]
2418pub struct History<T> {
2419    pub txid: T,
2420    /// Confirmation height of txid
2421    ///
2422    /// -1 means unconfirmed with unconfirmed parents
2423    ///  0 means unconfirmed with confirmed parents
2424    pub height: i32,
2425}
2426pub(crate) type LBtcHistory = History<elements::Txid>;
2427pub(crate) type BtcHistory = History<bitcoin::Txid>;
2428
2429impl<T> History<T> {
2430    pub(crate) fn confirmed(&self) -> bool {
2431        self.height > 0
2432    }
2433}
2434#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
2435impl From<electrum_client::GetHistoryRes> for BtcHistory {
2436    fn from(value: electrum_client::GetHistoryRes) -> Self {
2437        Self {
2438            txid: value.tx_hash,
2439            height: value.height,
2440        }
2441    }
2442}
2443impl From<lwk_wollet::History> for LBtcHistory {
2444    fn from(value: lwk_wollet::History) -> Self {
2445        Self::from(&value)
2446    }
2447}
2448impl From<&lwk_wollet::History> for LBtcHistory {
2449    fn from(value: &lwk_wollet::History) -> Self {
2450        Self {
2451            txid: value.txid,
2452            height: value.height,
2453        }
2454    }
2455}
2456pub(crate) type BtcScript = bitcoin::ScriptBuf;
2457pub(crate) type LBtcScript = elements::Script;
2458
2459#[derive(Clone, Debug)]
2460pub struct BtcScriptBalance {
2461    /// Confirmed balance in Satoshis for the address.
2462    pub confirmed: u64,
2463    /// Unconfirmed balance in Satoshis for the address.
2464    ///
2465    /// Some servers (e.g. `electrs`) return this as a negative value.
2466    pub unconfirmed: i64,
2467}
2468#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
2469impl From<electrum_client::GetBalanceRes> for BtcScriptBalance {
2470    fn from(val: electrum_client::GetBalanceRes) -> Self {
2471        Self {
2472            confirmed: val.confirmed,
2473            unconfirmed: val.unconfirmed,
2474        }
2475    }
2476}
2477
2478#[macro_export]
2479macro_rules! get_updated_fields {
2480    ($($var:ident),* $(,)?) => {{
2481        let mut options = Vec::new();
2482        $(
2483            if $var.is_some() {
2484                options.push(stringify!($var).to_string());
2485            }
2486        )*
2487        match options.len() > 0 {
2488            true => Some(options),
2489            false => None,
2490        }
2491    }};
2492}