breez_sdk_liquid/swapper/boltz/
client.rs

1use crate::swapper::boltz::CONNECTION_TIMEOUT;
2use crate::{
3    bitcoin, elements,
4    model::{BlockchainExplorer, Config, BREEZ_LIQUID_ESPLORA_URL},
5};
6use boltz_client::{
7    error::Error,
8    network::{
9        esplora::{EsploraBitcoinClient, EsploraLiquidClient},
10        BitcoinChain, BitcoinClient as BoltzBitcoinClient, LiquidChain,
11        LiquidClient as BoltzLiquidClient,
12    },
13    reqwest,
14};
15use log::error;
16use sdk_macros::async_trait;
17
18pub(crate) enum LiquidClient {
19    #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
20    Electrum(Box<boltz_client::network::electrum::ElectrumLiquidClient>),
21    Esplora(Box<EsploraLiquidClient>),
22}
23
24impl LiquidClient {
25    pub(crate) fn new(config: &Config) -> Result<Self, Error> {
26        Ok(match &config.liquid_explorer {
27            #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
28            BlockchainExplorer::Electrum { url } => {
29                let (tls, validate_domain) = config.electrum_tls_options();
30                Self::Electrum(Box::new(
31                    boltz_client::network::electrum::ElectrumLiquidClient::new(
32                        config.network.into(),
33                        url,
34                        tls,
35                        validate_domain,
36                        CONNECTION_TIMEOUT.as_secs() as u8,
37                    )?,
38                ))
39            }
40            BlockchainExplorer::Esplora { url, .. } => {
41                let mut builder = reqwest::Client::builder();
42                if url == BREEZ_LIQUID_ESPLORA_URL {
43                    match &config.breez_api_key {
44                        Some(api_key) => {
45                            let mut headers = reqwest::header::HeaderMap::new();
46                            let api_key = format!("Bearer {api_key}").parse().map_err(|err| {
47                                Error::Generic(format!("Could not set api key header: {err}"))
48                            })?;
49                            headers.insert(reqwest::header::AUTHORIZATION, api_key);
50                            builder = builder.default_headers(headers)
51                        }
52                        None => {
53                            let err = "Cannot start Breez Esplora client: Breez API key is not set";
54                            error!("{err}");
55                            return Err(Error::Generic(err.to_string()));
56                        }
57                    };
58                }
59                let client = builder.build().map_err(|err| {
60                    Error::Generic(format!("Could not initialize HTTP client: {err}"))
61                })?;
62
63                Self::Esplora(Box::new(EsploraLiquidClient::with_client(
64                    client,
65                    config.network.into(),
66                    url,
67                    CONNECTION_TIMEOUT.as_secs(),
68                )))
69            }
70        })
71    }
72}
73
74#[async_trait]
75impl BoltzLiquidClient for LiquidClient {
76    async fn get_address_utxo(
77        &self,
78        address: &elements::Address,
79    ) -> Result<Option<(elements::OutPoint, elements::TxOut)>, Error> {
80        match self {
81            #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
82            Self::Electrum(c) => c.get_address_utxo(address).await,
83            Self::Esplora(c) => c.get_address_utxo(address).await,
84        }
85    }
86
87    async fn get_genesis_hash(&self) -> Result<elements::BlockHash, Error> {
88        match self {
89            #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
90            Self::Electrum(c) => c.get_genesis_hash().await,
91            Self::Esplora(c) => c.get_genesis_hash().await,
92        }
93    }
94
95    async fn broadcast_tx(&self, signed_tx: &elements::Transaction) -> Result<String, Error> {
96        match self {
97            #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
98            Self::Electrum(c) => c.broadcast_tx(signed_tx).await,
99            Self::Esplora(c) => c.broadcast_tx(signed_tx).await,
100        }
101    }
102
103    fn network(&self) -> LiquidChain {
104        match self {
105            #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
106            Self::Electrum(c) => c.network(),
107            Self::Esplora(c) => c.network(),
108        }
109    }
110}
111
112pub(crate) enum BitcoinClient {
113    #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
114    Electrum(Box<boltz_client::network::electrum::ElectrumBitcoinClient>),
115    Esplora(Box<EsploraBitcoinClient>),
116}
117
118impl BitcoinClient {
119    pub(crate) fn new(config: &Config) -> Result<Self, Error> {
120        Ok(match &config.bitcoin_explorer {
121            #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
122            BlockchainExplorer::Electrum { url } => {
123                let (tls, validate_domain) = config.electrum_tls_options();
124                Self::Electrum(Box::new(
125                    boltz_client::network::electrum::ElectrumBitcoinClient::new(
126                        config.network.as_bitcoin_chain(),
127                        url,
128                        tls,
129                        validate_domain,
130                        CONNECTION_TIMEOUT.as_secs() as u8,
131                    )?,
132                ))
133            }
134            BlockchainExplorer::Esplora { url, .. } => {
135                Self::Esplora(Box::new(EsploraBitcoinClient::new(
136                    config.network.as_bitcoin_chain(),
137                    url,
138                    CONNECTION_TIMEOUT.as_secs(),
139                )))
140            }
141        })
142    }
143}
144
145#[async_trait]
146impl BoltzBitcoinClient for BitcoinClient {
147    async fn get_address_balance(&self, address: &bitcoin::Address) -> Result<(u64, i64), Error> {
148        match self {
149            #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
150            Self::Electrum(c) => c.get_address_balance(address).await,
151            Self::Esplora(c) => c.get_address_balance(address).await,
152        }
153    }
154
155    async fn get_address_utxos(
156        &self,
157        address: &bitcoin::Address,
158    ) -> Result<Vec<(bitcoin::OutPoint, bitcoin::TxOut)>, Error> {
159        match self {
160            #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
161            Self::Electrum(c) => c.get_address_utxos(address).await,
162            Self::Esplora(c) => c.get_address_utxos(address).await,
163        }
164    }
165
166    async fn broadcast_tx(&self, signed_tx: &bitcoin::Transaction) -> Result<bitcoin::Txid, Error> {
167        match self {
168            #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
169            Self::Electrum(c) => c.broadcast_tx(signed_tx).await,
170            Self::Esplora(c) => c.broadcast_tx(signed_tx).await,
171        }
172    }
173
174    fn network(&self) -> BitcoinChain {
175        match self {
176            #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
177            Self::Electrum(c) => c.network(),
178            Self::Esplora(c) => c.network(),
179        }
180    }
181}