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 http_client: BlockingReqwestClient,
15 base_url: String,
17}
18
19impl SyncClient {
20 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}