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