1use crate::errors::{Result, TensoraError};
4use ethers::types::{H256, Address};
5use reqwest::Client;
6use serde_json::Value;
7use url::Url;
8
9pub struct ExplorerClient {
11 base_url: Url,
12 client: Client,
13}
14
15impl ExplorerClient {
16 pub fn new(base_url: &str) -> Result<Self> {
21 let base_url = Url::parse(base_url)
22 .map_err(|e| TensoraError::Api(format!("Invalid explorer URL: {}", e)))?;
23
24 let client = Client::builder()
25 .timeout(std::time::Duration::from_secs(30))
26 .build()
27 .map_err(|e| TensoraError::Api(format!("Failed to create HTTP client: {}", e)))?;
28
29 Ok(Self { base_url, client })
30 }
31
32 pub async fn get_transaction(&self, tx_hash: H256) -> Result<Option<Value>> {
37 let url = self.base_url.join(&format!("/api/txs/0x{:x}", tx_hash))
38 .map_err(|e| TensoraError::Api(format!("Invalid URL: {}", e)))?;
39
40 let response = self.client
41 .get(url)
42 .send()
43 .await?;
44
45 if response.status().as_u16() == 404 {
46 return Ok(None);
47 }
48
49 if !response.status().is_success() {
50 return Err(TensoraError::Api(format!("HTTP {}", response.status())));
51 }
52
53 let tx: Value = response.json().await?;
54 Ok(Some(tx))
55 }
56
57 pub async fn get_block(&self, block_number: u64) -> Result<Option<Value>> {
62 let url = self.base_url.join(&format!("/api/blocks/{}", block_number))
63 .map_err(|e| TensoraError::Api(format!("Invalid URL: {}", e)))?;
64
65 let response = self.client
66 .get(url)
67 .send()
68 .await?;
69
70 if response.status().as_u16() == 404 {
71 return Ok(None);
72 }
73
74 if !response.status().is_success() {
75 return Err(TensoraError::Api(format!("HTTP {}", response.status())));
76 }
77
78 let block: Value = response.json().await?;
79 Ok(Some(block))
80 }
81
82 pub async fn get_address(&self, address: Address) -> Result<Option<Value>> {
87 let url = self.base_url.join(&format!("/api/addresses/0x{:x}", address))
88 .map_err(|e| TensoraError::Api(format!("Invalid URL: {}", e)))?;
89
90 let response = self.client
91 .get(url)
92 .send()
93 .await?;
94
95 if response.status().as_u16() == 404 {
96 return Ok(None);
97 }
98
99 if !response.status().is_success() {
100 return Err(TensoraError::Api(format!("HTTP {}", response.status())));
101 }
102
103 let addr_info: Value = response.json().await?;
104 Ok(Some(addr_info))
105 }
106
107 pub async fn search(&self, query: &str) -> Result<Vec<Value>> {
112 let url = self.base_url.join(&format!("/api/search?q={}", query))
113 .map_err(|e| TensoraError::Api(format!("Invalid URL: {}", e)))?;
114
115 let response = self.client
116 .get(url)
117 .send()
118 .await?;
119
120 if !response.status().is_success() {
121 return Err(TensoraError::Api(format!("HTTP {}", response.status())));
122 }
123
124 let results: Vec<Value> = response.json().await?;
125 Ok(results)
126 }
127}
128
129impl Default for ExplorerClient {
130 fn default() -> Self {
131 Self::new("https://explorer.tensora.sh")
132 .expect("Failed to create default ExplorerClient")
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[test]
141 fn test_explorer_client_creation() {
142 let client = ExplorerClient::new("https://explorer.tensora.sh");
143 assert!(client.is_ok());
144 }
145
146 #[test]
147 fn test_invalid_url() {
148 let client = ExplorerClient::new("not a url");
149 assert!(client.is_err());
150 }
151}
152