Skip to main content

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