breez_sdk_liquid/
model.rs

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