titan_client/http/
client_sync_impl.rs

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