1use std::collections::{HashMap, HashSet};
15use std::marker::PhantomData;
16use std::str::FromStr;
17use std::time::Duration;
18
19use bitcoin::block::Header as BlockHeader;
20use bitcoin::consensus::encode::serialize_hex;
21use bitcoin::consensus::{deserialize, serialize, Decodable};
22use bitcoin::hashes::{sha256, Hash};
23use bitcoin::hex::{DisplayHex, FromHex};
24use bitcoin::{Address, Block, BlockHash, MerkleBlock, Script, Transaction, Txid};
25
26#[allow(unused_imports)]
27use log::{debug, error, info, trace};
28
29use reqwest::{header, Body, Client, Response};
30
31use crate::{
32 AddressStats, BlockInfo, BlockStatus, BlockSummary, Builder, Error, MempoolRecentTx,
33 MempoolStats, MerkleProof, OutputStatus, ScriptHashStats, SubmitPackageResult, Tx, TxStatus,
34 Utxo, BASE_BACKOFF_MILLIS, RETRYABLE_ERROR_CODES,
35};
36
37#[derive(Debug, Clone)]
39pub struct AsyncClient<S = DefaultSleeper> {
40 url: String,
42 client: Client,
44 max_retries: usize,
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 {
87 AsyncClient {
88 url,
89 client,
90 max_retries: crate::DEFAULT_MAX_RETRIES,
91 marker: PhantomData,
92 }
93 }
94
95 async fn get_response<T: Decodable>(&self, path: &str) -> Result<T, Error> {
107 let url = format!("{}{}", self.url, path);
108 let response = self.get_with_retry(&url).await?;
109
110 if !response.status().is_success() {
111 return Err(Error::HttpResponse {
112 status: response.status().as_u16(),
113 message: response.text().await?,
114 });
115 }
116
117 Ok(deserialize::<T>(&response.bytes().await?)?)
118 }
119
120 async fn get_opt_response<T: Decodable>(&self, path: &str) -> Result<Option<T>, Error> {
126 match self.get_response::<T>(path).await {
127 Ok(res) => Ok(Some(res)),
128 Err(Error::HttpResponse { status: 404, .. }) => Ok(None),
129 Err(e) => Err(e),
130 }
131 }
132
133 async fn get_response_json<T: serde::de::DeserializeOwned>(
144 &self,
145 path: &str,
146 ) -> Result<T, Error> {
147 let url = format!("{}{}", self.url, path);
148 let response = self.get_with_retry(&url).await?;
149
150 if !response.status().is_success() {
151 return Err(Error::HttpResponse {
152 status: response.status().as_u16(),
153 message: response.text().await?,
154 });
155 }
156
157 response.json::<T>().await.map_err(Error::Reqwest)
158 }
159
160 async fn get_opt_response_json<T: serde::de::DeserializeOwned>(
167 &self,
168 url: &str,
169 ) -> Result<Option<T>, Error> {
170 match self.get_response_json(url).await {
171 Ok(res) => Ok(Some(res)),
172 Err(Error::HttpResponse { status: 404, .. }) => Ok(None),
173 Err(e) => Err(e),
174 }
175 }
176
177 async fn get_response_hex<T: Decodable>(&self, path: &str) -> Result<T, Error> {
189 let url = format!("{}{}", self.url, path);
190 let response = self.get_with_retry(&url).await?;
191
192 if !response.status().is_success() {
193 return Err(Error::HttpResponse {
194 status: response.status().as_u16(),
195 message: response.text().await?,
196 });
197 }
198
199 let hex_str = response.text().await?;
200 Ok(deserialize(&Vec::from_hex(&hex_str)?)?)
201 }
202
203 async fn get_opt_response_hex<T: Decodable>(&self, path: &str) -> Result<Option<T>, Error> {
210 match self.get_response_hex(path).await {
211 Ok(res) => Ok(Some(res)),
212 Err(Error::HttpResponse { status: 404, .. }) => Ok(None),
213 Err(e) => Err(e),
214 }
215 }
216
217 async fn get_response_text(&self, path: &str) -> Result<String, Error> {
226 let url = format!("{}{}", self.url, path);
227 let response = self.get_with_retry(&url).await?;
228
229 if !response.status().is_success() {
230 return Err(Error::HttpResponse {
231 status: response.status().as_u16(),
232 message: response.text().await?,
233 });
234 }
235
236 Ok(response.text().await?)
237 }
238
239 async fn get_opt_response_text(&self, path: &str) -> Result<Option<String>, Error> {
246 match self.get_response_text(path).await {
247 Ok(s) => Ok(Some(s)),
248 Err(Error::HttpResponse { status: 404, .. }) => Ok(None),
249 Err(e) => Err(e),
250 }
251 }
252
253 async fn post_request_bytes<T: Into<Body>>(
261 &self,
262 path: &str,
263 body: T,
264 query_params: Option<HashSet<(&str, String)>>,
265 ) -> Result<Response, Error> {
266 let url: String = format!("{}{}", self.url, path);
267 let mut request = self.client.post(url).body(body);
268
269 for param in query_params.unwrap_or_default() {
270 request = request.query(¶m);
271 }
272
273 let response = request.send().await?;
274
275 if !response.status().is_success() {
276 return Err(Error::HttpResponse {
277 status: response.status().as_u16(),
278 message: response.text().await?,
279 });
280 }
281
282 Ok(response)
283 }
284
285 pub async fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
287 self.get_opt_response(&format!("/tx/{txid}/raw")).await
288 }
289
290 pub async fn get_tx_no_opt(&self, txid: &Txid) -> Result<Transaction, Error> {
292 match self.get_tx(txid).await {
293 Ok(Some(tx)) => Ok(tx),
294 Ok(None) => Err(Error::TransactionNotFound(*txid)),
295 Err(e) => Err(e),
296 }
297 }
298
299 pub async fn get_txid_at_block_index(
302 &self,
303 block_hash: &BlockHash,
304 index: usize,
305 ) -> Result<Option<Txid>, Error> {
306 match self
307 .get_opt_response_text(&format!("/block/{block_hash}/txid/{index}"))
308 .await?
309 {
310 Some(s) => Ok(Some(Txid::from_str(&s).map_err(Error::HexToArray)?)),
311 None => Ok(None),
312 }
313 }
314
315 pub async fn get_tx_status(&self, txid: &Txid) -> Result<TxStatus, Error> {
317 self.get_response_json(&format!("/tx/{txid}/status")).await
318 }
319
320 pub async fn get_tx_info(&self, txid: &Txid) -> Result<Option<Tx>, Error> {
322 self.get_opt_response_json(&format!("/tx/{txid}")).await
323 }
324
325 pub async fn get_tx_outspends(&self, txid: &Txid) -> Result<Vec<OutputStatus>, Error> {
327 self.get_response_json(&format!("/tx/{txid}/outspends"))
328 .await
329 }
330
331 pub async fn get_header_by_hash(&self, block_hash: &BlockHash) -> Result<BlockHeader, Error> {
333 self.get_response_hex(&format!("/block/{block_hash}/header"))
334 .await
335 }
336
337 pub async fn get_block_status(&self, block_hash: &BlockHash) -> Result<BlockStatus, Error> {
339 self.get_response_json(&format!("/block/{block_hash}/status"))
340 .await
341 }
342
343 pub async fn get_block_by_hash(&self, block_hash: &BlockHash) -> Result<Option<Block>, Error> {
345 self.get_opt_response(&format!("/block/{block_hash}/raw"))
346 .await
347 }
348
349 pub async fn get_merkle_proof(&self, tx_hash: &Txid) -> Result<Option<MerkleProof>, Error> {
352 self.get_opt_response_json(&format!("/tx/{tx_hash}/merkle-proof"))
353 .await
354 }
355
356 pub async fn get_merkle_block(&self, tx_hash: &Txid) -> Result<Option<MerkleBlock>, Error> {
359 self.get_opt_response_hex(&format!("/tx/{tx_hash}/merkleblock-proof"))
360 .await
361 }
362
363 pub async fn get_output_status(
366 &self,
367 txid: &Txid,
368 index: u64,
369 ) -> Result<Option<OutputStatus>, Error> {
370 self.get_opt_response_json(&format!("/tx/{txid}/outspend/{index}"))
371 .await
372 }
373
374 pub async fn broadcast(&self, transaction: &Transaction) -> Result<(), Error> {
376 let body = serialize::<Transaction>(transaction).to_lower_hex_string();
377 match self.post_request_bytes("/tx", body, None).await {
378 Ok(_resp) => Ok(()),
379 Err(e) => Err(e),
380 }
381 }
382
383 pub async fn submit_package(
392 &self,
393 transactions: &[Transaction],
394 maxfeerate: Option<f64>,
395 maxburnamount: Option<f64>,
396 ) -> Result<SubmitPackageResult, Error> {
397 let mut queryparams = HashSet::<(&str, String)>::new();
398 if let Some(maxfeerate) = maxfeerate {
399 queryparams.insert(("maxfeerate", maxfeerate.to_string()));
400 }
401 if let Some(maxburnamount) = maxburnamount {
402 queryparams.insert(("maxburnamount", maxburnamount.to_string()));
403 }
404
405 let serialized_txs = transactions
406 .iter()
407 .map(|tx| serialize_hex(&tx))
408 .collect::<Vec<_>>();
409
410 let response = self
411 .post_request_bytes(
412 "/txs/package",
413 serde_json::to_string(&serialized_txs).unwrap_or_default(),
414 Some(queryparams),
415 )
416 .await?;
417
418 Ok(response.json::<SubmitPackageResult>().await?)
419 }
420
421 pub async fn get_height(&self) -> Result<u32, Error> {
423 self.get_response_text("/blocks/tip/height")
424 .await
425 .map(|height| u32::from_str(&height).map_err(Error::Parsing))?
426 }
427
428 pub async fn get_tip_hash(&self) -> Result<BlockHash, Error> {
430 self.get_response_text("/blocks/tip/hash")
431 .await
432 .map(|block_hash| BlockHash::from_str(&block_hash).map_err(Error::HexToArray))?
433 }
434
435 pub async fn get_block_hash(&self, block_height: u32) -> Result<BlockHash, Error> {
437 self.get_response_text(&format!("/block-height/{block_height}"))
438 .await
439 .map(|block_hash| BlockHash::from_str(&block_hash).map_err(Error::HexToArray))?
440 }
441
442 pub async fn get_address_stats(&self, address: &Address) -> Result<AddressStats, Error> {
445 let path = format!("/address/{address}");
446 self.get_response_json(&path).await
447 }
448
449 pub async fn get_scripthash_stats(&self, script: &Script) -> Result<ScriptHashStats, Error> {
451 let script_hash = sha256::Hash::hash(script.as_bytes());
452 let path = format!("/scripthash/{script_hash}");
453 self.get_response_json(&path).await
454 }
455
456 pub async fn get_address_txs(
461 &self,
462 address: &Address,
463 last_seen: Option<Txid>,
464 ) -> Result<Vec<Tx>, Error> {
465 let path = match last_seen {
466 Some(last_seen) => format!("/address/{address}/txs/chain/{last_seen}"),
467 None => format!("/address/{address}/txs"),
468 };
469
470 self.get_response_json(&path).await
471 }
472
473 pub async fn get_mempool_address_txs(&self, address: &Address) -> Result<Vec<Tx>, Error> {
475 let path = format!("/address/{address}/txs/mempool");
476
477 self.get_response_json(&path).await
478 }
479
480 pub async fn scripthash_txs(
485 &self,
486 script: &Script,
487 last_seen: Option<Txid>,
488 ) -> Result<Vec<Tx>, Error> {
489 let script_hash = sha256::Hash::hash(script.as_bytes());
490 let path = match last_seen {
491 Some(last_seen) => format!("/scripthash/{script_hash:x}/txs/chain/{last_seen}"),
492 None => format!("/scripthash/{script_hash:x}/txs"),
493 };
494
495 self.get_response_json(&path).await
496 }
497
498 pub async fn get_mempool_scripthash_txs(&self, script: &Script) -> Result<Vec<Tx>, Error> {
501 let script_hash = sha256::Hash::hash(script.as_bytes());
502 let path = format!("/scripthash/{script_hash:x}/txs/mempool");
503
504 self.get_response_json(&path).await
505 }
506
507 pub async fn get_mempool_stats(&self) -> Result<MempoolStats, Error> {
509 self.get_response_json("/mempool").await
510 }
511
512 pub async fn get_mempool_recent_txs(&self) -> Result<Vec<MempoolRecentTx>, Error> {
514 self.get_response_json("/mempool/recent").await
515 }
516
517 pub async fn get_mempool_txids(&self) -> Result<Vec<Txid>, Error> {
521 self.get_response_json("/mempool/txids").await
522 }
523
524 pub async fn get_fee_estimates(&self) -> Result<HashMap<u16, f64>, Error> {
527 self.get_response_json("/fee-estimates").await
528 }
529
530 pub async fn get_block_info(&self, blockhash: &BlockHash) -> Result<BlockInfo, Error> {
532 let path = format!("/block/{blockhash}");
533
534 self.get_response_json(&path).await
535 }
536
537 pub async fn get_block_txids(&self, blockhash: &BlockHash) -> Result<Vec<Txid>, Error> {
539 let path = format!("/block/{blockhash}/txids");
540
541 self.get_response_json(&path).await
542 }
543
544 pub async fn get_block_txs(
550 &self,
551 blockhash: &BlockHash,
552 start_index: Option<u32>,
553 ) -> Result<Vec<Tx>, Error> {
554 let path = match start_index {
555 None => format!("/block/{blockhash}/txs"),
556 Some(start_index) => format!("/block/{blockhash}/txs/{start_index}"),
557 };
558
559 self.get_response_json(&path).await
560 }
561
562 pub async fn get_blocks(&self, height: Option<u32>) -> Result<Vec<BlockSummary>, Error> {
568 let path = match height {
569 Some(height) => format!("/blocks/{height}"),
570 None => "/blocks".to_string(),
571 };
572 let blocks: Vec<BlockSummary> = self.get_response_json(&path).await?;
573 if blocks.is_empty() {
574 return Err(Error::InvalidResponse);
575 }
576 Ok(blocks)
577 }
578
579 pub async fn get_address_utxos(&self, address: &Address) -> Result<Vec<Utxo>, Error> {
581 let path = format!("/address/{address}/utxo");
582
583 self.get_response_json(&path).await
584 }
585
586 pub async fn get_scripthash_utxos(&self, script: &Script) -> Result<Vec<Utxo>, Error> {
588 let script_hash = sha256::Hash::hash(script.as_bytes());
589 let path = format!("/scripthash/{script_hash}/utxo");
590
591 self.get_response_json(&path).await
592 }
593
594 pub fn url(&self) -> &str {
596 &self.url
597 }
598
599 pub fn client(&self) -> &Client {
601 &self.client
602 }
603
604 async fn get_with_retry(&self, url: &str) -> Result<Response, Error> {
607 let mut delay = BASE_BACKOFF_MILLIS;
608 let mut attempts = 0;
609
610 loop {
611 match self.client.get(url).send().await? {
612 resp if attempts < self.max_retries && is_status_retryable(resp.status()) => {
613 S::sleep(delay).await;
614 attempts += 1;
615 delay *= 2;
616 }
617 resp => return Ok(resp),
618 }
619 }
620 }
621}
622
623fn is_status_retryable(status: reqwest::StatusCode) -> bool {
624 RETRYABLE_ERROR_CODES.contains(&status.as_u16())
625}
626
627pub trait Sleeper: 'static {
629 type Sleep: std::future::Future<Output = ()>;
631 fn sleep(dur: Duration) -> Self::Sleep;
633}
634
635#[derive(Debug, Clone, Copy)]
637pub struct DefaultSleeper;
638
639#[cfg(any(test, feature = "tokio"))]
640impl Sleeper for DefaultSleeper {
641 type Sleep = tokio::time::Sleep;
642
643 fn sleep(dur: std::time::Duration) -> Self::Sleep {
644 tokio::time::sleep(dur)
645 }
646}