titan_client/http/
client_async_impl.rs

1use bitcoin::{OutPoint, Txid};
2use reqwest::{header::HeaderMap, Client as AsyncReqwestClient};
3use std::{collections::HashMap, str::FromStr};
4use titan_types_api::*;
5use titan_types_core::*;
6
7use crate::Error;
8
9use super::TitanApiAsync;
10
11#[derive(Clone)]
12pub struct AsyncClient {
13    /// The async HTTP client from `reqwest`.
14    http_client: AsyncReqwestClient,
15    /// The base URL for all endpoints (e.g. http://localhost:3030).
16    base_url: String,
17}
18
19impl AsyncClient {
20    /// Creates a new `AsyncClient` for the given `base_url`.
21    pub fn new(base_url: &str) -> Self {
22        Self {
23            http_client: AsyncReqwestClient::new(),
24            base_url: base_url.trim_end_matches('/').to_string(),
25        }
26    }
27
28    async fn call_text(&self, path: &str) -> Result<String, Error> {
29        let url = format!("{}{}", self.base_url, path);
30        let response = self.http_client.get(&url).send().await?;
31        if response.status().is_success() {
32            Ok(response.text().await?)
33        } else {
34            Err(Error::TitanError(response.status(), response.text().await?))
35        }
36    }
37
38    async fn call_bytes(&self, path: &str) -> Result<Vec<u8>, Error> {
39        let url = format!("{}{}", self.base_url, path);
40        let response = self.http_client.get(&url).send().await?;
41        if response.status().is_success() {
42            Ok(response.bytes().await?.to_vec())
43        } else {
44            Err(Error::TitanError(response.status(), response.text().await?))
45        }
46    }
47
48    async fn post_text(&self, path: &str, body: String) -> Result<String, Error> {
49        let url = format!("{}{}", self.base_url, path);
50        let response = self.http_client.post(&url).body(body).send().await?;
51        if response.status().is_success() {
52            Ok(response.text().await?)
53        } else {
54            Err(Error::TitanError(response.status(), response.text().await?))
55        }
56    }
57
58    async fn delete(&self, path: &str) -> Result<(), Error> {
59        let url = format!("{}{}", self.base_url, path);
60        let response = self.http_client.delete(&url).send().await?;
61        if response.status().is_success() {
62            Ok(())
63        } else {
64            Err(Error::TitanError(response.status(), response.text().await?))
65        }
66    }
67}
68
69#[async_trait::async_trait]
70impl TitanApiAsync for AsyncClient {
71    async fn get_status(&self) -> Result<Status, Error> {
72        let text = self.call_text("/status").await?;
73        serde_json::from_str(&text).map_err(Error::from)
74    }
75
76    async fn get_tip(&self) -> Result<BlockTip, Error> {
77        let text = self.call_text("/tip").await?;
78        serde_json::from_str(&text).map_err(Error::from)
79    }
80
81    async fn get_block(&self, query: &query::Block) -> Result<Block, Error> {
82        let text = self.call_text(&format!("/block/{}", query)).await?;
83        serde_json::from_str(&text).map_err(Error::from)
84    }
85
86    async fn get_block_hash_by_height(&self, height: u64) -> Result<String, Error> {
87        self.call_text(&format!("/block/{}/hash", height)).await
88    }
89
90    async fn get_block_txids(&self, query: &query::Block) -> Result<Vec<String>, Error> {
91        let text = self.call_text(&format!("/block/{}/txids", query)).await?;
92        serde_json::from_str(&text).map_err(Error::from)
93    }
94
95    async fn get_address(&self, address: &str) -> Result<AddressData, Error> {
96        let text = self.call_text(&format!("/address/{}", address)).await?;
97        serde_json::from_str(&text).map_err(Error::from)
98    }
99
100    async fn get_transaction(&self, txid: &Txid) -> Result<Transaction, Error> {
101        let text = self.call_text(&format!("/tx/{}", txid)).await?;
102        serde_json::from_str(&text).map_err(Error::from)
103    }
104
105    async fn get_transaction_raw(&self, txid: &Txid) -> Result<Vec<u8>, Error> {
106        self.call_bytes(&format!("/tx/{}/raw", txid)).await
107    }
108
109    async fn get_transaction_hex(&self, txid: &Txid) -> Result<String, Error> {
110        self.call_text(&format!("/tx/{}/hex", txid)).await
111    }
112
113    async fn get_transaction_status(&self, txid: &Txid) -> Result<TransactionStatus, Error> {
114        let text = self.call_text(&format!("/tx/{}/status", txid)).await?;
115        serde_json::from_str(&text).map_err(Error::from)
116    }
117
118    async fn send_transaction(&self, tx_hex: String) -> Result<Txid, Error> {
119        let text = self.post_text("/tx/broadcast", tx_hex).await?;
120        Txid::from_str(&text).map_err(Error::from)
121    }
122
123    async fn get_output(&self, outpoint: &OutPoint) -> Result<TxOut, Error> {
124        let text = self.call_text(&format!("/output/{}", outpoint)).await?;
125        serde_json::from_str(&text).map_err(Error::from)
126    }
127
128    async fn get_inscription(
129        &self,
130        inscription_id: &InscriptionId,
131    ) -> Result<(HeaderMap, Vec<u8>), Error> {
132        let url = format!("{}/inscription/{}", self.base_url, inscription_id);
133        let resp = self.http_client.get(&url).send().await?;
134        let status = resp.status();
135        if !status.is_success() {
136            let body = resp.text().await.unwrap_or_default();
137            return Err(Error::TitanError(status, body));
138        }
139        let headers = resp.headers().clone();
140        let bytes = resp.bytes().await?.to_vec();
141        Ok((headers, bytes))
142    }
143
144    async fn get_runes(
145        &self,
146        pagination: Option<Pagination>,
147    ) -> Result<PaginationResponse<RuneResponse>, Error> {
148        let mut path = "/runes".to_string();
149        if let Some(p) = pagination {
150            path = format!("{}?skip={}&limit={}", path, p.skip, p.limit);
151        }
152        let text = self.call_text(&path).await?;
153        serde_json::from_str(&text).map_err(Error::from)
154    }
155
156    async fn get_rune(&self, rune: &query::Rune) -> Result<RuneResponse, Error> {
157        let text = self.call_text(&format!("/rune/{}", rune)).await?;
158        serde_json::from_str(&text).map_err(Error::from)
159    }
160
161    async fn get_rune_transactions(
162        &self,
163        rune: &query::Rune,
164        pagination: Option<Pagination>,
165    ) -> Result<PaginationResponse<Txid>, Error> {
166        let mut path = format!("/rune/{}/transactions", rune);
167        if let Some(p) = pagination {
168            path = format!("{}?skip={}&limit={}", path, p.skip, p.limit);
169        }
170        let text = self.call_text(&path).await?;
171        serde_json::from_str(&text).map_err(Error::from)
172    }
173
174    async fn get_mempool_txids(&self) -> Result<Vec<Txid>, Error> {
175        let text = self.call_text("/mempool/txids").await?;
176        serde_json::from_str(&text).map_err(Error::from)
177    }
178
179    async fn get_mempool_entry(&self, txid: &Txid) -> Result<MempoolEntry, Error> {
180        let text = self.call_text(&format!("/mempool/entry/{}", txid)).await?;
181        serde_json::from_str(&text).map_err(Error::from)
182    }
183
184    async fn get_mempool_entries(
185        &self,
186        txids: &[Txid],
187    ) -> Result<HashMap<Txid, Option<MempoolEntry>>, Error> {
188        let text = self
189            .post_text("/mempool/entries", serde_json::to_string(txids)?)
190            .await?;
191        serde_json::from_str(&text).map_err(Error::from)
192    }
193
194    async fn get_all_mempool_entries(&self) -> Result<HashMap<Txid, MempoolEntry>, Error> {
195        let text = self.call_text("/mempool/entries/all").await?;
196        serde_json::from_str(&text).map_err(Error::from)
197    }
198
199    async fn get_mempool_entries_with_ancestors(
200        &self,
201        txids: &[Txid],
202    ) -> Result<HashMap<Txid, MempoolEntry>, Error> {
203        let url = format!("{}/mempool/entries/ancestors", self.base_url);
204        let response = self.http_client.post(&url).json(txids).send().await?;
205
206        if response.status().is_success() {
207            let text = response.text().await?;
208            serde_json::from_str(&text).map_err(Error::from)
209        } else {
210            Err(Error::TitanError(response.status(), response.text().await?))
211        }
212    }
213
214    async fn get_subscription(&self, id: &str) -> Result<Subscription, Error> {
215        let text = self.call_text(&format!("/subscription/{}", id)).await?;
216        serde_json::from_str(&text).map_err(Error::from)
217    }
218
219    async fn list_subscriptions(&self) -> Result<Vec<Subscription>, Error> {
220        let text = self.call_text("/subscriptions").await?;
221        serde_json::from_str(&text).map_err(Error::from)
222    }
223
224    async fn add_subscription(&self, subscription: &Subscription) -> Result<Subscription, Error> {
225        let text = self
226            .post_text("/subscription", serde_json::to_string(subscription)?)
227            .await?;
228        serde_json::from_str(&text).map_err(Error::from)
229    }
230
231    async fn delete_subscription(&self, id: &str) -> Result<(), Error> {
232        self.delete(&format!("/subscription/{}", id)).await
233    }
234}