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