breez_sdk_liquid/swapper/boltz/
client.rs1use 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}