Skip to main content

anyswap_sdk/driver/
chain.rs

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/// One transaction of an address's history, mempool included.
14#[derive(Clone, Debug)]
15pub struct HistoryTx {
16    pub hex: String,
17    /// The confirming block, `None` while in the mempool.
18    pub height: Option<Height>,
19}
20
21/// Query and broadcast on one chain. The driver reads a script's whole history
22/// and interprets it itself, so an implementation fetches bytes by a fixed
23/// recipe and never decides anything. It also bounds each of its own requests:
24/// the driver waits on a call for as long as it takes, since a history read
25/// may need many requests.
26#[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    /// Estimate for confirmation within `target` blocks, in sat/vB, or an
32    /// error when the backend has none.
33    async fn fee_rate(&self, target: u32) -> Result<f64, ChainError>;
34
35    /// Every transaction paying or spending `address`.
36    async fn address_history(&self, address: &str) -> Result<Vec<HistoryTx>, ChainError>;
37
38    /// The transaction spending output `vout` of `txid`, mempool included, or
39    /// `None` while it is unspent: Esplora's outspend with the spending
40    /// transaction fetched. Read from the output itself, so it is found
41    /// however many other transactions crowd the address's history.
42    async fn outspend(&self, txid: &str, vout: u32) -> Result<Option<HistoryTx>, ChainError>;
43
44    /// Broadcasts a signed transaction and returns its txid.
45    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}