1use crate::bitcoin::Address;
2use crate::bitcoin::Block;
3use crate::bitcoin::BlockHash;
4use crate::bitcoin::Header;
5use crate::bitcoin::Transaction;
6use crate::bitcoin::Txid;
7use crate::error::EsploraError;
8use crate::types::KeychainKind;
9use crate::types::Tx;
10use crate::types::TxStatus;
11use crate::types::Update;
12use crate::types::{FullScanRequest, MerkleProof, OutputStatus, SyncRequest};
13
14use bdk_esplora::esplora_client::{BlockingClient, Builder};
15use bdk_esplora::EsploraExt;
16use bdk_wallet::bitcoin::Transaction as BdkTransaction;
17use bdk_wallet::chain::spk_client::FullScanRequest as BdkFullScanRequest;
18use bdk_wallet::chain::spk_client::FullScanResponse as BdkFullScanResponse;
19use bdk_wallet::chain::spk_client::SyncRequest as BdkSyncRequest;
20use bdk_wallet::chain::spk_client::SyncResponse as BdkSyncResponse;
21use bdk_wallet::Update as BdkUpdate;
22
23use std::collections::{BTreeMap, HashMap};
24use std::sync::Arc;
25
26#[derive(uniffi::Object)]
29pub struct EsploraClient(BlockingClient);
30
31#[uniffi::export]
32impl EsploraClient {
33 #[uniffi::constructor(default(proxy = None))]
36 pub fn new(url: String, proxy: Option<String>) -> Self {
37 let mut builder = Builder::new(url.as_str());
38 if let Some(proxy) = proxy {
39 builder = builder.proxy(proxy.as_str());
40 }
41 Self(builder.build_blocking())
42 }
43
44 pub fn full_scan(
52 &self,
53 request: Arc<FullScanRequest>,
54 stop_gap: u64,
55 parallel_requests: u64,
56 ) -> Result<Arc<Update>, EsploraError> {
57 let request: BdkFullScanRequest<KeychainKind> = request
59 .0
60 .lock()
61 .unwrap()
62 .take()
63 .ok_or(EsploraError::RequestAlreadyConsumed)?;
64
65 let result: BdkFullScanResponse<KeychainKind> =
66 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
67 self.0
68 .full_scan(request, stop_gap as usize, parallel_requests as usize)
69 }))
70 .map_err(|payload| {
71 let error_message = payload
72 .downcast_ref::<String>()
73 .map(String::as_str)
74 .or_else(|| payload.downcast_ref::<&str>().copied())
75 .unwrap_or("panic in esplora client")
76 .to_string();
77
78 EsploraError::Parsing { error_message }
79 })??;
80
81 let update = BdkUpdate {
82 last_active_indices: result.last_active_indices,
83 tx_update: result.tx_update,
84 chain: result.chain_update,
85 };
86
87 Ok(Arc::new(Update(update)))
88 }
89
90 pub fn sync(
96 &self,
97 request: Arc<SyncRequest>,
98 parallel_requests: u64,
99 ) -> Result<Arc<Update>, EsploraError> {
100 let request: BdkSyncRequest<(KeychainKind, u32)> = request
102 .0
103 .lock()
104 .unwrap()
105 .take()
106 .ok_or(EsploraError::RequestAlreadyConsumed)?;
107
108 let result: BdkSyncResponse =
109 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
110 self.0.sync(request, parallel_requests as usize)
111 }))
112 .map_err(|payload| {
113 let error_message = payload
114 .downcast_ref::<String>()
115 .map(String::as_str)
116 .or_else(|| payload.downcast_ref::<&str>().copied())
117 .unwrap_or("panic in esplora client")
118 .to_string();
119
120 EsploraError::Parsing { error_message }
121 })??;
122
123 let update = BdkUpdate {
124 last_active_indices: BTreeMap::default(),
125 tx_update: result.tx_update,
126 chain: result.chain_update,
127 };
128
129 Ok(Arc::new(Update(update)))
130 }
131
132 pub fn broadcast(&self, transaction: &Transaction) -> Result<(), EsploraError> {
134 let bdk_transaction: BdkTransaction = transaction.into();
135 self.0
136 .broadcast(&bdk_transaction)
137 .map_err(EsploraError::from)
138 }
139
140 pub fn get_tx(&self, txid: Arc<Txid>) -> Result<Option<Arc<Transaction>>, EsploraError> {
142 let tx_opt = self.0.get_tx(&txid.0)?;
143 Ok(tx_opt.map(|inner| Arc::new(Transaction::from(inner))))
144 }
145
146 pub fn get_tx_no_opt(&self, txid: Arc<Txid>) -> Result<Arc<Transaction>, EsploraError> {
148 self.0
149 .get_tx_no_opt(&txid.0)
150 .map(Transaction::from)
151 .map(Arc::new)
152 .map_err(EsploraError::from)
153 }
154
155 pub fn get_height(&self) -> Result<u32, EsploraError> {
157 self.0.get_height().map_err(EsploraError::from)
158 }
159
160 pub fn get_tip_hash(&self) -> Result<Arc<BlockHash>, EsploraError> {
162 self.0
163 .get_tip_hash()
164 .map(|hash| Arc::new(BlockHash(hash)))
165 .map_err(EsploraError::from)
166 }
167
168 pub fn get_fee_estimates(&self) -> Result<HashMap<u16, f64>, EsploraError> {
171 self.0.get_fee_estimates().map_err(EsploraError::from)
172 }
173
174 pub fn get_block_hash(&self, block_height: u32) -> Result<Arc<BlockHash>, EsploraError> {
176 self.0
177 .get_block_hash(block_height)
178 .map(|hash| Arc::new(BlockHash(hash)))
179 .map_err(EsploraError::from)
180 }
181
182 pub fn get_block_by_hash(
184 &self,
185 block_hash: Arc<BlockHash>,
186 ) -> Result<Option<Block>, EsploraError> {
187 self.0
188 .get_block_by_hash(&block_hash.0)
189 .map(|block| block.map(|block| block.into()))
190 .map_err(EsploraError::from)
191 }
192
193 pub fn get_txid_at_block_index(
195 &self,
196 block_hash: Arc<BlockHash>,
197 index: u64,
198 ) -> Result<Option<Arc<Txid>>, EsploraError> {
199 self.0
200 .get_txid_at_block_index(&block_hash.0, index as usize)
201 .map(|txid| txid.map(Txid).map(Arc::new))
202 .map_err(EsploraError::from)
203 }
204
205 pub fn get_header_by_hash(&self, block_hash: Arc<BlockHash>) -> Result<Header, EsploraError> {
207 self.0
208 .get_header_by_hash(&block_hash.0)
209 .map(Header::from)
210 .map_err(EsploraError::from)
211 }
212
213 pub fn get_tx_status(&self, txid: Arc<Txid>) -> Result<TxStatus, EsploraError> {
215 self.0
216 .get_tx_status(&txid.0)
217 .map(TxStatus::from)
218 .map_err(EsploraError::from)
219 }
220
221 pub fn get_tx_info(&self, txid: Arc<Txid>) -> Result<Option<Tx>, EsploraError> {
223 self.0
224 .get_tx_info(&txid.0)
225 .map(|tx| tx.map(Tx::from))
226 .map_err(EsploraError::from)
227 }
228
229 pub fn get_address_txs(
234 &self,
235 address: Arc<Address>,
236 last_seen: Option<Arc<Txid>>,
237 ) -> Result<Vec<Tx>, EsploraError> {
238 let last_seen = last_seen.as_ref().map(|txid| txid.0);
239 let txs = self.0.get_address_txs(&address.as_ref().0, last_seen)?;
240
241 Ok(txs.into_iter().map(Tx::from).collect())
242 }
243
244 pub fn get_merkle_proof(&self, txid: &Txid) -> Result<Option<MerkleProof>, EsploraError> {
247 self.0
248 .get_merkle_proof(&txid.0)
249 .map(|proof| proof.map(MerkleProof::from))
250 .map_err(EsploraError::from)
251 }
252
253 pub fn get_output_status(
256 &self,
257 txid: Arc<Txid>,
258 vout: u64,
259 ) -> Result<Option<OutputStatus>, EsploraError> {
260 self.0
261 .get_output_status(&txid.0, vout)
262 .map(|status| status.map(OutputStatus::from))
263 .map_err(EsploraError::from)
264 }
265}