1use async_trait::async_trait;
2use thiserror::Error;
3
4use super::{
5 record::Height,
6 rt::{MaybeSend, MaybeSync},
7};
8
9#[derive(Clone, Debug, Error)]
10#[error("chain: {0}")]
11pub struct ChainError(pub String);
12
13#[derive(Clone, Debug)]
15pub struct HistoryTx {
16 pub hex: String,
17 pub height: Option<Height>,
19}
20
21#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
27#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
28pub trait ChainClient: MaybeSend + MaybeSync {
29 async fn height(&self) -> Result<Height, ChainError>;
30
31 async fn fee_rate(&self, target: u32) -> Result<f64, ChainError>;
34
35 async fn address_history(&self, address: &str) -> Result<Vec<HistoryTx>, ChainError>;
37
38 async fn outspend(&self, txid: &str, vout: u32) -> Result<Option<HistoryTx>, ChainError>;
43
44 async fn broadcast(&self, tx_hex: &str) -> Result<String, ChainError>;
46}
47
48#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
49#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
50impl<C: ChainClient + ?Sized> ChainClient for std::sync::Arc<C> {
51 async fn height(&self) -> Result<Height, ChainError> {
52 (**self).height().await
53 }
54
55 async fn fee_rate(&self, target: u32) -> Result<f64, ChainError> {
56 (**self).fee_rate(target).await
57 }
58
59 async fn address_history(&self, address: &str) -> Result<Vec<HistoryTx>, ChainError> {
60 (**self).address_history(address).await
61 }
62
63 async fn outspend(&self, txid: &str, vout: u32) -> Result<Option<HistoryTx>, ChainError> {
64 (**self).outspend(txid, vout).await
65 }
66
67 async fn broadcast(&self, tx_hex: &str) -> Result<String, ChainError> {
68 (**self).broadcast(tx_hex).await
69 }
70}
71
72#[cfg(feature = "esplora")]
73mod esplora {
74 use std::str::FromStr;
75
76 use anyswap_core::{
77 esplora::{BitcoinEsplora, EsploraError, LiquidEsplora},
78 transaction::bitcoin::{decode_tx_hex, encode_tx_hex},
79 };
80 use async_trait::async_trait;
81
82 use super::{ChainClient, ChainError, HistoryTx};
83 use crate::driver::record::Height;
84
85 impl From<EsploraError> for ChainError {
86 fn from(e: EsploraError) -> Self {
87 Self(e.to_string())
88 }
89 }
90
91 fn invalid(what: &str, e: impl std::fmt::Display) -> ChainError {
92 ChainError(format!("invalid {what}: {e}"))
93 }
94
95 #[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
96 #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
97 impl ChainClient for BitcoinEsplora {
98 async fn height(&self) -> Result<Height, ChainError> {
99 Ok(BitcoinEsplora::get_height(self).await?)
100 }
101
102 async fn fee_rate(&self, target: u32) -> Result<f64, ChainError> {
103 Ok(BitcoinEsplora::get_fee_rate(self, target as usize).await?)
104 }
105
106 async fn address_history(&self, address: &str) -> Result<Vec<HistoryTx>, ChainError> {
107 let address = bitcoin::Address::from_str(address)
108 .map_err(|e| invalid("address", e))?
109 .assume_checked();
110 Ok(self
111 .address_txs(&address)
112 .await?
113 .into_iter()
114 .map(|(tx, height)| HistoryTx {
115 hex: encode_tx_hex(&tx),
116 height,
117 })
118 .collect())
119 }
120
121 async fn outspend(&self, txid: &str, vout: u32) -> Result<Option<HistoryTx>, ChainError> {
122 let txid = bitcoin::Txid::from_str(txid).map_err(|e| invalid("txid", e))?;
123 let Some(spend) = self.get_outspend(&txid, vout).await? else {
124 return Ok(None);
125 };
126 let Some(tx) = self.get_tx(&spend.txid).await? else {
127 return Ok(None);
128 };
129 Ok(Some(HistoryTx {
130 hex: encode_tx_hex(&tx),
131 height: self.get_confirmation_height(&spend.txid).await?,
132 }))
133 }
134
135 async fn broadcast(&self, tx_hex: &str) -> Result<String, ChainError> {
136 let tx = decode_tx_hex(tx_hex).map_err(|e| invalid("transaction", e))?;
137 Ok(BitcoinEsplora::broadcast(self, &tx).await?.to_string())
138 }
139 }
140
141 #[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
142 #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
143 impl ChainClient for LiquidEsplora {
144 async fn height(&self) -> Result<Height, ChainError> {
145 Ok(LiquidEsplora::get_height(self).await?)
146 }
147
148 async fn fee_rate(&self, target: u32) -> Result<f64, ChainError> {
149 Ok(LiquidEsplora::get_fee_rate(self, target as usize).await?)
150 }
151
152 async fn address_history(&self, address: &str) -> Result<Vec<HistoryTx>, ChainError> {
153 let address =
154 elements::Address::from_str(address).map_err(|e| invalid("address", e))?;
155 Ok(self
156 .address_txs(&address)
157 .await?
158 .into_iter()
159 .map(|(tx, height)| HistoryTx {
160 hex: hex::encode(elements::encode::serialize(&tx)),
161 height,
162 })
163 .collect())
164 }
165
166 async fn outspend(&self, txid: &str, vout: u32) -> Result<Option<HistoryTx>, ChainError> {
167 let txid = elements::Txid::from_str(txid).map_err(|e| invalid("txid", e))?;
168 let Some(spend) = self.get_outspend(&txid, vout).await? else {
169 return Ok(None);
170 };
171 let Some(tx) = self.get_tx(&spend.txid).await? else {
172 return Ok(None);
173 };
174 Ok(Some(HistoryTx {
175 hex: hex::encode(elements::encode::serialize(&tx)),
176 height: self.get_confirmation_height(&spend.txid).await?,
177 }))
178 }
179
180 async fn broadcast(&self, tx_hex: &str) -> Result<String, ChainError> {
181 let tx = anyswap_core::transaction::liquid::decode_tx_hex(tx_hex)
182 .map_err(|e| invalid("transaction", e))?;
183 Ok(LiquidEsplora::broadcast(self, &tx).await?.to_string())
184 }
185 }
186}