tensora/api/
coordinator.rs1use crate::errors::{Result, TensoraError};
4use crate::types::EpochInfo;
5use ethers::types::{Address, H256};
6use reqwest::Client;
7use serde::{Deserialize, Serialize};
8use serde_json::Value;
9use url::Url;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct Assignment {
14 pub subnet_id: u32,
16 pub address: Address,
18 pub role: String,
20 pub vrf_proof: Option<Vec<u8>>,
22}
23
24pub struct CoordinatorClient {
26 base_url: Url,
27 client: Client,
28}
29
30impl CoordinatorClient {
31 pub fn new(base_url: &str) -> Result<Self> {
36 let base_url = Url::parse(base_url)
37 .map_err(|e| TensoraError::Api(format!("Invalid coordinator URL: {}", e)))?;
38
39 let client = Client::builder()
40 .timeout(std::time::Duration::from_secs(30))
41 .build()
42 .map_err(|e| TensoraError::Api(format!("Failed to create HTTP client: {}", e)))?;
43
44 Ok(Self { base_url, client })
45 }
46
47 pub async fn get_current_epoch(&self) -> Result<EpochInfo> {
61 let url = self.base_url.join("/vrf/epoch")
62 .map_err(|e| TensoraError::Api(format!("Invalid URL: {}", e)))?;
63
64 let response = self.client
65 .get(url)
66 .send()
67 .await?;
68
69 if !response.status().is_success() {
70 return Err(TensoraError::Api(format!("HTTP {}", response.status())));
71 }
72
73 let epoch: EpochInfo = response.json().await?;
74 Ok(epoch)
75 }
76
77 pub async fn get_assignments(&self, subnet_id: Option<u32>) -> Result<Vec<Assignment>> {
82 let path = if let Some(id) = subnet_id {
83 format!("/assignments?subnet_id={}", id)
84 } else {
85 "/assignments".to_string()
86 };
87
88 let url = self.base_url.join(&path)
89 .map_err(|e| TensoraError::Api(format!("Invalid URL: {}", e)))?;
90
91 let response = self.client
92 .get(url)
93 .send()
94 .await?;
95
96 if !response.status().is_success() {
97 return Err(TensoraError::Api(format!("HTTP {}", response.status())));
98 }
99
100 let assignments: Vec<Assignment> = response.json().await?;
101 Ok(assignments)
102 }
103
104 pub async fn get_vrf_seed(&self, epoch: u64) -> Result<H256> {
109 let url = self.base_url.join(&format!("/vrf/epoch/{}/seed", epoch))
110 .map_err(|e| TensoraError::Api(format!("Invalid URL: {}", e)))?;
111
112 let response = self.client
113 .get(url)
114 .send()
115 .await?;
116
117 if !response.status().is_success() {
118 return Err(TensoraError::Api(format!("HTTP {}", response.status())));
119 }
120
121 let data: Value = response.json().await?;
122 let seed_str = data["seed"]
123 .as_str()
124 .ok_or_else(|| TensoraError::Api("Invalid VRF seed response".to_string()))?;
125
126 let seed: H256 = seed_str
127 .parse()
128 .map_err(|e| TensoraError::Api(format!("Invalid VRF seed format: {}", e)))?;
129
130 Ok(seed)
131 }
132}
133
134impl Default for CoordinatorClient {
135 fn default() -> Self {
136 Self::new("https://coordinator.tensora.sh")
137 .expect("Failed to create default CoordinatorClient")
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn test_coordinator_client_creation() {
147 let client = CoordinatorClient::new("https://coordinator.tensora.sh");
148 assert!(client.is_ok());
149 }
150
151 #[test]
152 fn test_invalid_url() {
153 let client = CoordinatorClient::new("not a url");
154 assert!(client.is_err());
155 }
156}
157