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 self.0
67 .full_scan(request, stop_gap as usize, parallel_requests as usize)?;
68
69 let update = BdkUpdate {
70 last_active_indices: result.last_active_indices,
71 tx_update: result.tx_update,
72 chain: result.chain_update,
73 };
74
75 Ok(Arc::new(Update(update)))
76 }
77
78 pub fn sync(
84 &self,
85 request: Arc<SyncRequest>,
86 parallel_requests: u64,
87 ) -> Result<Arc<Update>, EsploraError> {
88 let request: BdkSyncRequest<(KeychainKind, u32)> = request
90 .0
91 .lock()
92 .unwrap()
93 .take()
94 .ok_or(EsploraError::RequestAlreadyConsumed)?;
95
96 let result: BdkSyncResponse = self.0.sync(request, parallel_requests as usize)?;
97
98 let update = BdkUpdate {
99 last_active_indices: BTreeMap::default(),
100 tx_update: result.tx_update,
101 chain: result.chain_update,
102 };
103
104 Ok(Arc::new(Update(update)))
105 }
106
107 pub fn broadcast(&self, transaction: &Transaction) -> Result<(), EsploraError> {
109 let bdk_transaction: BdkTransaction = transaction.into();
110 self.0
111 .broadcast(&bdk_transaction)
112 .map_err(EsploraError::from)
113 }
114
115 pub fn get_tx(&self, txid: Arc<Txid>) -> Result<Option<Arc<Transaction>>, EsploraError> {
117 let tx_opt = self.0.get_tx(&txid.0)?;
118 Ok(tx_opt.map(|inner| Arc::new(Transaction::from(inner))))
119 }
120
121 pub fn get_tx_no_opt(&self, txid: Arc<Txid>) -> Result<Arc<Transaction>, EsploraError> {
123 self.0
124 .get_tx_no_opt(&txid.0)
125 .map(Transaction::from)
126 .map(Arc::new)
127 .map_err(EsploraError::from)
128 }
129
130 pub fn get_height(&self) -> Result<u32, EsploraError> {
132 self.0.get_height().map_err(EsploraError::from)
133 }
134
135 pub fn get_tip_hash(&self) -> Result<Arc<BlockHash>, EsploraError> {
137 self.0
138 .get_tip_hash()
139 .map(|hash| Arc::new(BlockHash(hash)))
140 .map_err(EsploraError::from)
141 }
142
143 pub fn get_fee_estimates(&self) -> Result<HashMap<u16, f64>, EsploraError> {
146 self.0.get_fee_estimates().map_err(EsploraError::from)
147 }
148
149 pub fn get_block_hash(&self, block_height: u32) -> Result<Arc<BlockHash>, EsploraError> {
151 self.0
152 .get_block_hash(block_height)
153 .map(|hash| Arc::new(BlockHash(hash)))
154 .map_err(EsploraError::from)
155 }
156
157 pub fn get_block_by_hash(
159 &self,
160 block_hash: Arc<BlockHash>,
161 ) -> Result<Option<Block>, EsploraError> {
162 self.0
163 .get_block_by_hash(&block_hash.0)
164 .map(|block| block.map(|block| block.into()))
165 .map_err(EsploraError::from)
166 }
167
168 pub fn get_txid_at_block_index(
170 &self,
171 block_hash: Arc<BlockHash>,
172 index: u64,
173 ) -> Result<Option<Arc<Txid>>, EsploraError> {
174 self.0
175 .get_txid_at_block_index(&block_hash.0, index as usize)
176 .map(|txid| txid.map(Txid).map(Arc::new))
177 .map_err(EsploraError::from)
178 }
179
180 pub fn get_header_by_hash(&self, block_hash: Arc<BlockHash>) -> Result<Header, EsploraError> {
182 self.0
183 .get_header_by_hash(&block_hash.0)
184 .map(Header::from)
185 .map_err(EsploraError::from)
186 }
187
188 pub fn get_tx_status(&self, txid: Arc<Txid>) -> Result<TxStatus, EsploraError> {
190 self.0
191 .get_tx_status(&txid.0)
192 .map(TxStatus::from)
193 .map_err(EsploraError::from)
194 }
195
196 pub fn get_tx_info(&self, txid: Arc<Txid>) -> Result<Option<Tx>, EsploraError> {
198 self.0
199 .get_tx_info(&txid.0)
200 .map(|tx| tx.map(Tx::from))
201 .map_err(EsploraError::from)
202 }
203
204 pub fn get_address_txs(
209 &self,
210 address: Arc<Address>,
211 last_seen: Option<Arc<Txid>>,
212 ) -> Result<Vec<Tx>, EsploraError> {
213 let last_seen = last_seen.as_ref().map(|txid| txid.0);
214 let txs = self.0.get_address_txs(&address.as_ref().0, last_seen)?;
215
216 Ok(txs.into_iter().map(Tx::from).collect())
217 }
218
219 pub fn get_merkle_proof(&self, txid: &Txid) -> Result<Option<MerkleProof>, EsploraError> {
222 self.0
223 .get_merkle_proof(&txid.0)
224 .map(|proof| proof.map(MerkleProof::from))
225 .map_err(EsploraError::from)
226 }
227
228 pub fn get_output_status(
231 &self,
232 txid: Arc<Txid>,
233 vout: u64,
234 ) -> Result<Option<OutputStatus>, EsploraError> {
235 self.0
236 .get_output_status(&txid.0, vout)
237 .map(|status| status.map(OutputStatus::from))
238 .map_err(EsploraError::from)
239 }
240}