Skip to main content

tensora/api/
explorer.rs

1//! Explorer API client for transaction and block data
2
3use crate::errors::{Result, TensoraError};
4use ethers::types::{H256, Address};
5use reqwest::Client;
6use serde_json::Value;
7use url::Url;
8
9/// Client for the Tensora Explorer API
10pub struct ExplorerClient {
11    base_url: Url,
12    client: Client,
13}
14
15impl ExplorerClient {
16    /// Create a new Explorer client
17    ///
18    /// # Arguments
19    /// * `base_url` - Base URL of the explorer API (e.g., https://explorer.tensora.sh)
20    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    /// Get transaction by hash
33    ///
34    /// # Arguments
35    /// * `tx_hash` - Transaction hash
36    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    /// Get block by number
58    ///
59    /// # Arguments
60    /// * `block_number` - Block number
61    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    /// Get address information
83    ///
84    /// # Arguments
85    /// * `address` - Address to query
86    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    /// Search transactions, blocks, or addresses
108    ///
109    /// # Arguments
110    /// * `query` - Search query (tx hash, block number, or address)
111    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