1use crate::bitcoin::{BlockHash, Header, Transaction, Txid};
2use crate::error::ElectrumError;
3use crate::types::KeychainKind;
4use crate::types::Update;
5use crate::types::{FullScanRequest, SyncRequest};
6
7use bdk_electrum::electrum_client::HeaderNotification as BdkHeaderNotification;
8use bdk_electrum::electrum_client::ServerFeaturesRes as BdkServerFeaturesRes;
9use bdk_electrum::BdkElectrumClient as BdkBdkElectrumClient;
10use bdk_wallet::bitcoin::Transaction as BdkTransaction;
11use bdk_wallet::chain::spk_client::FullScanRequest as BdkFullScanRequest;
12use bdk_wallet::chain::spk_client::FullScanResponse as BdkFullScanResponse;
13use bdk_wallet::chain::spk_client::SyncRequest as BdkSyncRequest;
14use bdk_wallet::chain::spk_client::SyncResponse as BdkSyncResponse;
15use bdk_wallet::Update as BdkUpdate;
16
17use bdk_electrum::electrum_client::ElectrumApi;
18use bdk_wallet::bitcoin::hex::{Case, DisplayHex};
19use std::collections::BTreeMap;
20use std::convert::TryFrom;
21use std::sync::Arc;
22use std::time::Duration;
23
24#[derive(uniffi::Object)]
27pub struct ElectrumClient(BdkBdkElectrumClient<bdk_electrum::electrum_client::Client>);
28
29#[uniffi::export]
30impl ElectrumClient {
31 #[uniffi::constructor(default(socks5 = None, timeout = None, retry = None, validate_domain = true))]
37 pub fn new(
38 url: String,
39 socks5: Option<String>,
40 timeout: Option<u8>,
41 retry: Option<u8>,
42 validate_domain: bool,
43 ) -> Result<Self, ElectrumError> {
44 let mut config = bdk_electrum::electrum_client::ConfigBuilder::new();
45 config = config.validate_domain(validate_domain);
46 if let Some(timeout) = timeout {
47 config = config.timeout(Some(Duration::from_secs(timeout.into())));
48 }
49 if let Some(retry) = retry {
50 config = config.retry(retry);
51 }
52 if let Some(socks5) = socks5 {
53 config = config.socks5(Some(bdk_electrum::electrum_client::Socks5Config::new(
54 socks5.as_str(),
55 )));
56 }
57 let inner_client =
58 bdk_electrum::electrum_client::Client::from_config(url.as_str(), config.build())?;
59 let client = BdkBdkElectrumClient::new(inner_client);
60 Ok(Self(client))
61 }
62
63 pub fn full_scan(
79 &self,
80 request: Arc<FullScanRequest>,
81 stop_gap: u64,
82 batch_size: u64,
83 fetch_prev_txouts: bool,
84 ) -> Result<Arc<Update>, ElectrumError> {
85 let request: BdkFullScanRequest<KeychainKind> = request
87 .0
88 .lock()
89 .unwrap()
90 .take()
91 .ok_or(ElectrumError::RequestAlreadyConsumed)?;
92
93 let full_scan_result: BdkFullScanResponse<KeychainKind> = self.0.full_scan(
94 request,
95 stop_gap as usize,
96 batch_size as usize,
97 fetch_prev_txouts,
98 )?;
99
100 let update = BdkUpdate {
101 last_active_indices: full_scan_result.last_active_indices,
102 tx_update: full_scan_result.tx_update,
103 chain: full_scan_result.chain_update,
104 };
105
106 Ok(Arc::new(Update(update)))
107 }
108
109 pub fn sync(
125 &self,
126 request: Arc<SyncRequest>,
127 batch_size: u64,
128 fetch_prev_txouts: bool,
129 ) -> Result<Arc<Update>, ElectrumError> {
130 let request: BdkSyncRequest<(KeychainKind, u32)> = request
132 .0
133 .lock()
134 .unwrap()
135 .take()
136 .ok_or(ElectrumError::RequestAlreadyConsumed)?;
137
138 let sync_result: BdkSyncResponse =
139 self.0
140 .sync(request, batch_size as usize, fetch_prev_txouts)?;
141
142 let update = BdkUpdate {
143 last_active_indices: BTreeMap::default(),
144 tx_update: sync_result.tx_update,
145 chain: sync_result.chain_update,
146 };
147
148 Ok(Arc::new(Update(update)))
149 }
150
151 pub fn transaction_broadcast(&self, tx: &Transaction) -> Result<Arc<Txid>, ElectrumError> {
153 let bdk_transaction: BdkTransaction = tx.into();
154 self.0
155 .transaction_broadcast(&bdk_transaction)
156 .map_err(ElectrumError::from)
157 .map(|txid| Arc::new(Txid(txid)))
158 }
159
160 pub fn fetch_tx(&self, txid: Arc<Txid>) -> Result<Arc<Transaction>, ElectrumError> {
164 let tx = self.0.fetch_tx(txid.0).map_err(ElectrumError::from)?;
165 Ok(Arc::new(Transaction::from(tx.as_ref().clone())))
166 }
167
168 pub fn server_features(&self) -> Result<ServerFeaturesRes, ElectrumError> {
170 let res = self
171 .0
172 .inner
173 .server_features()
174 .map_err(ElectrumError::from)?;
175
176 ServerFeaturesRes::try_from(res)
177 }
178
179 pub fn estimate_fee(&self, number: u64) -> Result<f64, ElectrumError> {
181 self.0
182 .inner
183 .estimate_fee(number as usize, None)
184 .map_err(ElectrumError::from)
185 }
186
187 pub fn block_header(&self, height: u64) -> Result<Header, ElectrumError> {
189 self.0
190 .inner
191 .block_header(height as usize)
192 .map_err(ElectrumError::from)
193 .map(Header::from)
194 }
195
196 pub fn block_headers_subscribe(&self) -> Result<HeaderNotification, ElectrumError> {
198 self.0
199 .inner
200 .block_headers_subscribe()
201 .map_err(ElectrumError::from)
202 .map(HeaderNotification::from)
203 }
204
205 pub fn block_headers_pop(&self) -> Result<Option<HeaderNotification>, ElectrumError> {
208 self.0
209 .inner
210 .block_headers_pop()
211 .map_err(ElectrumError::from)
212 .map(|notification| notification.map(HeaderNotification::from))
213 }
214
215 pub fn ping(&self) -> Result<(), ElectrumError> {
217 self.0.inner.ping().map_err(ElectrumError::from)
218 }
219
220 pub fn relay_fee(&self) -> Result<f64, ElectrumError> {
222 self.0.inner.relay_fee().map_err(ElectrumError::from)
223 }
224
225 pub fn transaction_get_raw(&self, txid: Arc<Txid>) -> Result<Vec<u8>, ElectrumError> {
227 self.0
228 .inner
229 .transaction_get_raw(&txid.0)
230 .map_err(ElectrumError::from)
231 }
232}
233
234#[derive(uniffi::Record)]
236pub struct ServerFeaturesRes {
237 pub server_version: String,
239 pub genesis_hash: Arc<BlockHash>,
241 pub protocol_min: String,
243 pub protocol_max: String,
245 pub hash_function: Option<String>,
247 pub pruning: Option<i64>,
249}
250
251impl TryFrom<BdkServerFeaturesRes> for ServerFeaturesRes {
252 type Error = ElectrumError;
253
254 fn try_from(value: BdkServerFeaturesRes) -> Result<ServerFeaturesRes, ElectrumError> {
255 let hash_str = value.genesis_hash.to_hex_string(Case::Lower);
256 let blockhash = hash_str
257 .parse::<bdk_wallet::bitcoin::BlockHash>()
258 .map_err(|err| ElectrumError::InvalidResponse {
259 error_message: format!(
260 "invalid genesis hash returned by server: {hash_str} ({err})"
261 ),
262 })?;
263
264 Ok(ServerFeaturesRes {
265 server_version: value.server_version,
266 genesis_hash: Arc::new(BlockHash(blockhash)),
267 protocol_min: value.protocol_min,
268 protocol_max: value.protocol_max,
269 hash_function: value.hash_function,
270 pruning: value.pruning,
271 })
272 }
273}
274
275#[derive(uniffi::Record)]
277pub struct HeaderNotification {
278 pub height: u64,
280 pub header: Header,
282}
283
284impl From<BdkHeaderNotification> for HeaderNotification {
285 fn from(value: BdkHeaderNotification) -> HeaderNotification {
286 HeaderNotification {
287 height: value.height as u64,
288 header: value.header.into(),
289 }
290 }
291}