use std::collections::HashMap;
use std::str::FromStr;
use bitcoin::consensus::{deserialize, serialize};
use bitcoin::hashes::{sha256, Hash};
use bitcoin::hex::{DisplayHex, FromHex};
use bitcoin::{
block::Header as BlockHeader, Block, BlockHash, MerkleBlock, Script, Transaction, Txid,
};
#[allow(unused_imports)]
use log::{debug, error, info, trace};
use reqwest::{header, Client, StatusCode};
use crate::{BlockStatus, BlockSummary, Builder, Error, MerkleProof, OutputStatus, Tx, TxStatus};
#[derive(Debug, Clone)]
pub struct AsyncClient {
url: String,
client: Client,
}
impl AsyncClient {
pub fn from_builder(builder: Builder) -> Result<Self, Error> {
let mut client_builder = Client::builder();
#[cfg(not(target_arch = "wasm32"))]
if let Some(proxy) = &builder.proxy {
client_builder = client_builder.proxy(reqwest::Proxy::all(proxy)?);
}
#[cfg(not(target_arch = "wasm32"))]
if let Some(timeout) = builder.timeout {
client_builder = client_builder.timeout(core::time::Duration::from_secs(timeout));
}
if !builder.headers.is_empty() {
let mut headers = header::HeaderMap::new();
for (k, v) in builder.headers {
let header_name = header::HeaderName::from_lowercase(k.to_lowercase().as_bytes())
.map_err(|_| Error::InvalidHttpHeaderName(k))?;
let header_value = header::HeaderValue::from_str(&v)
.map_err(|_| Error::InvalidHttpHeaderValue(v))?;
headers.insert(header_name, header_value);
}
client_builder = client_builder.default_headers(headers);
}
Ok(Self::from_client(builder.base_url, client_builder.build()?))
}
pub fn from_client(url: String, client: Client) -> Self {
AsyncClient { url, client }
}
pub async fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
let resp = self
.client
.get(&format!("{}/tx/{}/raw", self.url, txid))
.send()
.await?;
if let StatusCode::NOT_FOUND = resp.status() {
return Ok(None);
}
if resp.status().is_server_error() || resp.status().is_client_error() {
Err(Error::HttpResponse {
status: resp.status().as_u16(),
message: resp.text().await?,
})
} else {
Ok(Some(deserialize(&resp.bytes().await?)?))
}
}
pub async fn get_tx_no_opt(&self, txid: &Txid) -> Result<Transaction, Error> {
match self.get_tx(txid).await {
Ok(Some(tx)) => Ok(tx),
Ok(None) => Err(Error::TransactionNotFound(*txid)),
Err(e) => Err(e),
}
}
pub async fn get_txid_at_block_index(
&self,
block_hash: &BlockHash,
index: usize,
) -> Result<Option<Txid>, Error> {
let resp = self
.client
.get(&format!("{}/block/{}/txid/{}", self.url, block_hash, index))
.send()
.await?;
if let StatusCode::NOT_FOUND = resp.status() {
return Ok(None);
}
if resp.status().is_server_error() || resp.status().is_client_error() {
Err(Error::HttpResponse {
status: resp.status().as_u16(),
message: resp.text().await?,
})
} else {
Ok(Some(Txid::from_str(&resp.text().await?)?))
}
}
pub async fn get_tx_status(&self, txid: &Txid) -> Result<TxStatus, Error> {
let resp = self
.client
.get(&format!("{}/tx/{}/status", self.url, txid))
.send()
.await?;
if resp.status().is_server_error() || resp.status().is_client_error() {
Err(Error::HttpResponse {
status: resp.status().as_u16(),
message: resp.text().await?,
})
} else {
Ok(resp.json().await?)
}
}
pub async fn get_tx_info(&self, txid: &Txid) -> Result<Option<Tx>, Error> {
let resp = self
.client
.get(&format!("{}/tx/{}", self.url, txid))
.send()
.await?;
if resp.status() == StatusCode::NOT_FOUND {
return Ok(None);
}
if resp.status().is_server_error() || resp.status().is_client_error() {
Err(Error::HttpResponse {
status: resp.status().as_u16(),
message: resp.text().await?,
})
} else {
Ok(Some(resp.json().await?))
}
}
pub async fn get_header_by_hash(&self, block_hash: &BlockHash) -> Result<BlockHeader, Error> {
let resp = self
.client
.get(&format!("{}/block/{}/header", self.url, block_hash))
.send()
.await?;
if resp.status().is_server_error() || resp.status().is_client_error() {
Err(Error::HttpResponse {
status: resp.status().as_u16(),
message: resp.text().await?,
})
} else {
let header = deserialize(&Vec::from_hex(&resp.text().await?)?)?;
Ok(header)
}
}
pub async fn get_block_status(&self, block_hash: &BlockHash) -> Result<BlockStatus, Error> {
let resp = self
.client
.get(&format!("{}/block/{}/status", self.url, block_hash))
.send()
.await?;
if resp.status().is_server_error() || resp.status().is_client_error() {
Err(Error::HttpResponse {
status: resp.status().as_u16(),
message: resp.text().await?,
})
} else {
Ok(resp.json().await?)
}
}
pub async fn get_block_by_hash(&self, block_hash: &BlockHash) -> Result<Option<Block>, Error> {
let resp = self
.client
.get(&format!("{}/block/{}/raw", self.url, block_hash))
.send()
.await?;
if let StatusCode::NOT_FOUND = resp.status() {
return Ok(None);
}
if resp.status().is_server_error() || resp.status().is_client_error() {
Err(Error::HttpResponse {
status: resp.status().as_u16(),
message: resp.text().await?,
})
} else {
Ok(Some(deserialize(&resp.bytes().await?)?))
}
}
pub async fn get_merkle_proof(&self, tx_hash: &Txid) -> Result<Option<MerkleProof>, Error> {
let resp = self
.client
.get(&format!("{}/tx/{}/merkle-proof", self.url, tx_hash))
.send()
.await?;
if let StatusCode::NOT_FOUND = resp.status() {
return Ok(None);
}
if resp.status().is_server_error() || resp.status().is_client_error() {
Err(Error::HttpResponse {
status: resp.status().as_u16(),
message: resp.text().await?,
})
} else {
Ok(Some(resp.json().await?))
}
}
pub async fn get_merkle_block(&self, tx_hash: &Txid) -> Result<Option<MerkleBlock>, Error> {
let resp = self
.client
.get(&format!("{}/tx/{}/merkleblock-proof", self.url, tx_hash))
.send()
.await?;
if let StatusCode::NOT_FOUND = resp.status() {
return Ok(None);
}
if resp.status().is_server_error() || resp.status().is_client_error() {
Err(Error::HttpResponse {
status: resp.status().as_u16(),
message: resp.text().await?,
})
} else {
let merkle_block = deserialize(&Vec::from_hex(&resp.text().await?)?)?;
Ok(Some(merkle_block))
}
}
pub async fn get_output_status(
&self,
txid: &Txid,
index: u64,
) -> Result<Option<OutputStatus>, Error> {
let resp = self
.client
.get(&format!("{}/tx/{}/outspend/{}", self.url, txid, index))
.send()
.await?;
if let StatusCode::NOT_FOUND = resp.status() {
return Ok(None);
}
if resp.status().is_server_error() || resp.status().is_client_error() {
Err(Error::HttpResponse {
status: resp.status().as_u16(),
message: resp.text().await?,
})
} else {
Ok(Some(resp.json().await?))
}
}
pub async fn broadcast(&self, transaction: &Transaction) -> Result<(), Error> {
let resp = self
.client
.post(&format!("{}/tx", self.url))
.body(serialize(transaction).to_lower_hex_string())
.send()
.await?;
if resp.status().is_server_error() || resp.status().is_client_error() {
Err(Error::HttpResponse {
status: resp.status().as_u16(),
message: resp.text().await?,
})
} else {
Ok(())
}
}
pub async fn get_height(&self) -> Result<u32, Error> {
let resp = self
.client
.get(&format!("{}/blocks/tip/height", self.url))
.send()
.await?;
if resp.status().is_server_error() || resp.status().is_client_error() {
Err(Error::HttpResponse {
status: resp.status().as_u16(),
message: resp.text().await?,
})
} else {
Ok(resp.text().await?.parse()?)
}
}
pub async fn get_tip_hash(&self) -> Result<BlockHash, Error> {
let resp = self
.client
.get(&format!("{}/blocks/tip/hash", self.url))
.send()
.await?;
if resp.status().is_server_error() || resp.status().is_client_error() {
Err(Error::HttpResponse {
status: resp.status().as_u16(),
message: resp.text().await?,
})
} else {
Ok(BlockHash::from_str(&resp.text().await?)?)
}
}
pub async fn get_block_hash(&self, block_height: u32) -> Result<BlockHash, Error> {
let resp = self
.client
.get(&format!("{}/block-height/{}", self.url, block_height))
.send()
.await?;
if let StatusCode::NOT_FOUND = resp.status() {
return Err(Error::HeaderHeightNotFound(block_height));
}
if resp.status().is_server_error() || resp.status().is_client_error() {
Err(Error::HttpResponse {
status: resp.status().as_u16(),
message: resp.text().await?,
})
} else {
Ok(BlockHash::from_str(&resp.text().await?)?)
}
}
pub async fn scripthash_txs(
&self,
script: &Script,
last_seen: Option<Txid>,
) -> Result<Vec<Tx>, Error> {
let script_hash = sha256::Hash::hash(script.as_bytes());
let url = match last_seen {
Some(last_seen) => format!(
"{}/scripthash/{:x}/txs/chain/{}",
self.url, script_hash, last_seen
),
None => format!("{}/scripthash/{:x}/txs", self.url, script_hash),
};
let resp = self.client.get(url).send().await?;
if resp.status().is_server_error() || resp.status().is_client_error() {
Err(Error::HttpResponse {
status: resp.status().as_u16(),
message: resp.text().await?,
})
} else {
Ok(resp.json::<Vec<Tx>>().await?)
}
}
pub async fn get_fee_estimates(&self) -> Result<HashMap<u16, f64>, Error> {
let resp = self
.client
.get(&format!("{}/fee-estimates", self.url,))
.send()
.await?;
if resp.status().is_server_error() || resp.status().is_client_error() {
Err(Error::HttpResponse {
status: resp.status().as_u16(),
message: resp.text().await?,
})
} else {
Ok(resp.json::<HashMap<u16, f64>>().await?)
}
}
pub async fn get_blocks(&self, height: Option<u32>) -> Result<Vec<BlockSummary>, Error> {
let url = match height {
Some(height) => format!("{}/blocks/{}", self.url, height),
None => format!("{}/blocks", self.url),
};
let resp = self.client.get(&url).send().await?;
if resp.status().is_server_error() || resp.status().is_client_error() {
Err(Error::HttpResponse {
status: resp.status().as_u16(),
message: resp.text().await?,
})
} else {
Ok(resp.json::<Vec<BlockSummary>>().await?)
}
}
pub fn url(&self) -> &str {
&self.url
}
pub fn client(&self) -> &Client {
&self.client
}
}