1use std::collections::HashMap;
15use std::marker::PhantomData;
16use std::str::FromStr;
17
18use bitcoin::consensus::{deserialize, serialize, Decodable, Encodable};
19use bitcoin::hashes::{sha256, Hash};
20use bitcoin::hex::{DisplayHex, FromHex};
21use bitcoin::Address;
22use bitcoin::{
23 block::Header as BlockHeader, Block, BlockHash, MerkleBlock, Script, Transaction, Txid,
24};
25
26#[allow(unused_imports)]
27use log::{debug, error, info, trace};
28
29use reqwest::{header, Client, Response};
30
31use crate::api::AddressStats;
32use crate::{
33 BlockStatus, BlockSummary, Builder, Error, MerkleProof, OutputStatus, Tx, TxStatus,
34 BASE_BACKOFF_MILLIS, RETRYABLE_ERROR_CODES,
35};
36
37#[derive(Debug, Clone)]
38pub struct AsyncClient<S = DefaultSleeper> {
39 url: String,
41 client: Client,
43 max_retries: usize,
45
46 marker: PhantomData<S>,
48}
49
50impl<S: Sleeper> AsyncClient<S> {
51 pub fn from_builder(builder: Builder) -> Result<Self, Error> {
53 let mut client_builder = Client::builder();
54
55 #[cfg(not(target_arch = "wasm32"))]
56 if let Some(proxy) = &builder.proxy {
57 client_builder = client_builder.proxy(reqwest::Proxy::all(proxy)?);
58 }
59
60 #[cfg(not(target_arch = "wasm32"))]
61 if let Some(timeout) = builder.timeout {
62 client_builder = client_builder.timeout(core::time::Duration::from_secs(timeout));
63 }
64
65 if !builder.headers.is_empty() {
66 let mut headers = header::HeaderMap::new();
67 for (k, v) in builder.headers {
68 let header_name = header::HeaderName::from_lowercase(k.to_lowercase().as_bytes())
69 .map_err(|_| Error::InvalidHttpHeaderName(k))?;
70 let header_value = header::HeaderValue::from_str(&v)
71 .map_err(|_| Error::InvalidHttpHeaderValue(v))?;
72 headers.insert(header_name, header_value);
73 }
74 client_builder = client_builder.default_headers(headers);
75 }
76
77 Ok(AsyncClient {
78 url: builder.base_url,
79 client: client_builder.build()?,
80 max_retries: builder.max_retries,
81 marker: PhantomData,
82 })
83 }
84
85 pub fn from_client(url: String, client: Client) -> Self {
86 AsyncClient {
87 url,
88 client,
89 max_retries: crate::DEFAULT_MAX_RETRIES,
90 marker: PhantomData,
91 }
92 }
93
94 async fn get_response<T: Decodable>(&self, path: &str) -> Result<T, Error> {
106 let url = format!("{}{}", self.url, path);
107 let response = self.get_with_retry(&url).await?;
108
109 if !response.status().is_success() {
110 return Err(Error::HttpResponse {
111 status: response.status().as_u16(),
112 message: response.text().await?,
113 });
114 }
115
116 Ok(deserialize::<T>(&response.bytes().await?)?)
117 }
118
119 async fn get_opt_response<T: Decodable>(&self, path: &str) -> Result<Option<T>, Error> {
125 match self.get_response::<T>(path).await {
126 Ok(res) => Ok(Some(res)),
127 Err(Error::HttpResponse { status: 404, .. }) => Ok(None),
128 Err(e) => Err(e),
129 }
130 }
131
132 async fn get_response_json<T: serde::de::DeserializeOwned>(
143 &self,
144 path: &str,
145 ) -> Result<T, Error> {
146 let url = format!("{}{}", self.url, path);
147 let response = self.get_with_retry(&url).await?;
148
149 if !response.status().is_success() {
150 return Err(Error::HttpResponse {
151 status: response.status().as_u16(),
152 message: response.text().await?,
153 });
154 }
155
156 response.json::<T>().await.map_err(Error::Reqwest)
157 }
158
159 async fn get_opt_response_json<T: serde::de::DeserializeOwned>(
166 &self,
167 url: &str,
168 ) -> Result<Option<T>, Error> {
169 match self.get_response_json(url).await {
170 Ok(res) => Ok(Some(res)),
171 Err(Error::HttpResponse { status: 404, .. }) => Ok(None),
172 Err(e) => Err(e),
173 }
174 }
175
176 async fn get_response_hex<T: Decodable>(&self, path: &str) -> Result<T, Error> {
188 let url = format!("{}{}", self.url, path);
189 let response = self.get_with_retry(&url).await?;
190
191 if !response.status().is_success() {
192 return Err(Error::HttpResponse {
193 status: response.status().as_u16(),
194 message: response.text().await?,
195 });
196 }
197
198 let hex_str = response.text().await?;
199 Ok(deserialize(&Vec::from_hex(&hex_str)?)?)
200 }
201
202 async fn get_opt_response_hex<T: Decodable>(&self, path: &str) -> Result<Option<T>, Error> {
209 match self.get_response_hex(path).await {
210 Ok(res) => Ok(Some(res)),
211 Err(Error::HttpResponse { status: 404, .. }) => Ok(None),
212 Err(e) => Err(e),
213 }
214 }
215
216 async fn get_response_text(&self, path: &str) -> Result<String, Error> {
225 let url = format!("{}{}", self.url, path);
226 let response = self.get_with_retry(&url).await?;
227
228 if !response.status().is_success() {
229 return Err(Error::HttpResponse {
230 status: response.status().as_u16(),
231 message: response.text().await?,
232 });
233 }
234
235 Ok(response.text().await?)
236 }
237
238 async fn get_opt_response_text(&self, path: &str) -> Result<Option<String>, Error> {
245 match self.get_response_text(path).await {
246 Ok(s) => Ok(Some(s)),
247 Err(Error::HttpResponse { status: 404, .. }) => Ok(None),
248 Err(e) => Err(e),
249 }
250 }
251
252 async fn post_request_hex<T: Encodable>(&self, path: &str, body: T) -> Result<(), Error> {
263 let url = format!("{}{}", self.url, path);
264 let body = serialize::<T>(&body).to_lower_hex_string();
265
266 let response = self.client.post(url).body(body).send().await?;
267
268 if !response.status().is_success() {
269 return Err(Error::HttpResponse {
270 status: response.status().as_u16(),
271 message: response.text().await?,
272 });
273 }
274
275 Ok(())
276 }
277
278 pub async fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
280 self.get_opt_response(&format!("/tx/{txid}/raw")).await
281 }
282
283 pub async fn get_tx_no_opt(&self, txid: &Txid) -> Result<Transaction, Error> {
285 match self.get_tx(txid).await {
286 Ok(Some(tx)) => Ok(tx),
287 Ok(None) => Err(Error::TransactionNotFound(*txid)),
288 Err(e) => Err(e),
289 }
290 }
291
292 pub async fn get_txid_at_block_index(
295 &self,
296 block_hash: &BlockHash,
297 index: usize,
298 ) -> Result<Option<Txid>, Error> {
299 match self
300 .get_opt_response_text(&format!("/block/{block_hash}/txid/{index}"))
301 .await?
302 {
303 Some(s) => Ok(Some(Txid::from_str(&s).map_err(Error::HexToArray)?)),
304 None => Ok(None),
305 }
306 }
307
308 pub async fn get_tx_status(&self, txid: &Txid) -> Result<TxStatus, Error> {
310 self.get_response_json(&format!("/tx/{txid}/status")).await
311 }
312
313 pub async fn get_tx_info(&self, txid: &Txid) -> Result<Option<Tx>, Error> {
315 self.get_opt_response_json(&format!("/tx/{txid}")).await
316 }
317
318 pub async fn get_header_by_hash(&self, block_hash: &BlockHash) -> Result<BlockHeader, Error> {
320 self.get_response_hex(&format!("/block/{block_hash}/header"))
321 .await
322 }
323
324 pub async fn get_block_status(&self, block_hash: &BlockHash) -> Result<BlockStatus, Error> {
326 self.get_response_json(&format!("/block/{block_hash}/status"))
327 .await
328 }
329
330 pub async fn get_block_by_hash(&self, block_hash: &BlockHash) -> Result<Option<Block>, Error> {
332 self.get_opt_response(&format!("/block/{block_hash}/raw"))
333 .await
334 }
335
336 pub async fn get_merkle_proof(&self, tx_hash: &Txid) -> Result<Option<MerkleProof>, Error> {
339 self.get_opt_response_json(&format!("/tx/{tx_hash}/merkle-proof"))
340 .await
341 }
342
343 pub async fn get_merkle_block(&self, tx_hash: &Txid) -> Result<Option<MerkleBlock>, Error> {
346 self.get_opt_response_hex(&format!("/tx/{tx_hash}/merkleblock-proof"))
347 .await
348 }
349
350 pub async fn get_output_status(
353 &self,
354 txid: &Txid,
355 index: u64,
356 ) -> Result<Option<OutputStatus>, Error> {
357 self.get_opt_response_json(&format!("/tx/{txid}/outspend/{index}"))
358 .await
359 }
360
361 pub async fn broadcast(&self, transaction: &Transaction) -> Result<(), Error> {
363 self.post_request_hex("/tx", transaction).await
364 }
365
366 pub async fn get_height(&self) -> Result<u32, Error> {
368 self.get_response_text("/blocks/tip/height")
369 .await
370 .map(|height| u32::from_str(&height).map_err(Error::Parsing))?
371 }
372
373 pub async fn get_tip_hash(&self) -> Result<BlockHash, Error> {
375 self.get_response_text("/blocks/tip/hash")
376 .await
377 .map(|block_hash| BlockHash::from_str(&block_hash).map_err(Error::HexToArray))?
378 }
379
380 pub async fn get_block_hash(&self, block_height: u32) -> Result<BlockHash, Error> {
382 self.get_response_text(&format!("/block-height/{block_height}"))
383 .await
384 .map(|block_hash| BlockHash::from_str(&block_hash).map_err(Error::HexToArray))?
385 }
386
387 pub async fn get_address_stats(&self, address: &Address) -> Result<AddressStats, Error> {
390 let path = format!("/address/{address}");
391 self.get_response_json(&path).await
392 }
393
394 pub async fn get_address_txs(
399 &self,
400 address: &Address,
401 last_seen: Option<Txid>,
402 ) -> Result<Vec<Tx>, Error> {
403 let path = match last_seen {
404 Some(last_seen) => format!("/address/{address}/txs/chain/{last_seen}"),
405 None => format!("/address/{address}/txs"),
406 };
407
408 self.get_response_json(&path).await
409 }
410
411 pub async fn scripthash_txs(
416 &self,
417 script: &Script,
418 last_seen: Option<Txid>,
419 ) -> Result<Vec<Tx>, Error> {
420 let script_hash = sha256::Hash::hash(script.as_bytes());
421 let path = match last_seen {
422 Some(last_seen) => format!("/scripthash/{script_hash:x}/txs/chain/{last_seen}"),
423 None => format!("/scripthash/{script_hash:x}/txs"),
424 };
425
426 self.get_response_json(&path).await
427 }
428
429 pub async fn get_fee_estimates(&self) -> Result<HashMap<u16, f64>, Error> {
432 self.get_response_json("/fee-estimates").await
433 }
434
435 pub async fn get_blocks(&self, height: Option<u32>) -> Result<Vec<BlockSummary>, Error> {
441 let path = match height {
442 Some(height) => format!("/blocks/{height}"),
443 None => "/blocks".to_string(),
444 };
445 let blocks: Vec<BlockSummary> = self.get_response_json(&path).await?;
446 if blocks.is_empty() {
447 return Err(Error::InvalidResponse);
448 }
449 Ok(blocks)
450 }
451
452 pub fn url(&self) -> &str {
454 &self.url
455 }
456
457 pub fn client(&self) -> &Client {
459 &self.client
460 }
461
462 async fn get_with_retry(&self, url: &str) -> Result<Response, Error> {
465 let mut delay = BASE_BACKOFF_MILLIS;
466 let mut attempts = 0;
467
468 loop {
469 match self.client.get(url).send().await? {
470 resp if attempts < self.max_retries && is_status_retryable(resp.status()) => {
471 S::sleep(delay).await;
472 attempts += 1;
473 delay *= 2;
474 }
475 resp => return Ok(resp),
476 }
477 }
478 }
479}
480
481fn is_status_retryable(status: reqwest::StatusCode) -> bool {
482 RETRYABLE_ERROR_CODES.contains(&status.as_u16())
483}
484
485pub trait Sleeper: 'static {
486 type Sleep: std::future::Future<Output = ()>;
487 fn sleep(dur: std::time::Duration) -> Self::Sleep;
488}
489
490#[derive(Debug, Clone, Copy)]
491pub struct DefaultSleeper;
492
493#[cfg(any(test, feature = "tokio"))]
494impl Sleeper for DefaultSleeper {
495 type Sleep = tokio::time::Sleep;
496
497 fn sleep(dur: std::time::Duration) -> Self::Sleep {
498 tokio::time::sleep(dur)
499 }
500}