use async_trait::async_trait;
use thiserror::Error;
use super::{
record::Height,
rt::{MaybeSend, MaybeSync},
};
#[derive(Debug, Error)]
#[error("chain: {0}")]
pub struct ChainError(pub String);
#[derive(Clone, Debug)]
pub struct HistoryTx {
pub hex: String,
pub height: Option<Height>,
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait ChainClient: MaybeSend + MaybeSync {
async fn height(&self) -> Result<Height, ChainError>;
async fn fee_rate(&self, target: u32) -> Result<f64, ChainError>;
async fn address_history(&self, address: &str) -> Result<Vec<HistoryTx>, ChainError>;
async fn broadcast(&self, tx_hex: &str) -> Result<String, ChainError>;
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl<C: ChainClient + ?Sized> ChainClient for std::sync::Arc<C> {
async fn height(&self) -> Result<Height, ChainError> {
(**self).height().await
}
async fn fee_rate(&self, target: u32) -> Result<f64, ChainError> {
(**self).fee_rate(target).await
}
async fn address_history(&self, address: &str) -> Result<Vec<HistoryTx>, ChainError> {
(**self).address_history(address).await
}
async fn broadcast(&self, tx_hex: &str) -> Result<String, ChainError> {
(**self).broadcast(tx_hex).await
}
}
#[cfg(feature = "esplora")]
mod esplora {
use std::str::FromStr;
use anyswap_core::{
esplora::{BitcoinEsplora, EsploraError, LiquidEsplora},
transaction::bitcoin::{decode_tx_hex, encode_tx_hex},
};
use async_trait::async_trait;
use super::{ChainClient, ChainError, HistoryTx};
use crate::driver::record::Height;
impl From<EsploraError> for ChainError {
fn from(e: EsploraError) -> Self {
Self(e.to_string())
}
}
fn invalid(what: &str, e: impl std::fmt::Display) -> ChainError {
ChainError(format!("invalid {what}: {e}"))
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl ChainClient for BitcoinEsplora {
async fn height(&self) -> Result<Height, ChainError> {
Ok(BitcoinEsplora::get_height(self).await?)
}
async fn fee_rate(&self, target: u32) -> Result<f64, ChainError> {
Ok(BitcoinEsplora::get_fee_rate(self, target as usize).await?)
}
async fn address_history(&self, address: &str) -> Result<Vec<HistoryTx>, ChainError> {
let address = bitcoin::Address::from_str(address)
.map_err(|e| invalid("address", e))?
.assume_checked();
Ok(self
.address_txs(&address)
.await?
.into_iter()
.map(|(tx, height)| HistoryTx {
hex: encode_tx_hex(&tx),
height,
})
.collect())
}
async fn broadcast(&self, tx_hex: &str) -> Result<String, ChainError> {
let tx = decode_tx_hex(tx_hex).map_err(|e| invalid("transaction", e))?;
Ok(BitcoinEsplora::broadcast(self, &tx).await?.to_string())
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl ChainClient for LiquidEsplora {
async fn height(&self) -> Result<Height, ChainError> {
Ok(LiquidEsplora::get_height(self).await?)
}
async fn fee_rate(&self, target: u32) -> Result<f64, ChainError> {
Ok(LiquidEsplora::get_fee_rate(self, target as usize).await?)
}
async fn address_history(&self, address: &str) -> Result<Vec<HistoryTx>, ChainError> {
let address =
elements::Address::from_str(address).map_err(|e| invalid("address", e))?;
Ok(self
.address_txs(&address)
.await?
.into_iter()
.map(|(tx, height)| HistoryTx {
hex: hex::encode(elements::encode::serialize(&tx)),
height,
})
.collect())
}
async fn broadcast(&self, tx_hex: &str) -> Result<String, ChainError> {
let tx = anyswap_core::transaction::liquid::decode_tx_hex(tx_hex)
.map_err(|e| invalid("transaction", e))?;
Ok(LiquidEsplora::broadcast(self, &tx).await?.to_string())
}
}
}