Skip to main content

interveil_sdk/client/
client.rs

1use crate::error::VeilError;
2use crate::intent::sign::SignedIntent;
3
4/// Response from the node after submitting a signed intent.
5#[derive(Debug, Clone)]
6pub struct SubmitResponse {
7    pub tx_hash: String,
8    pub status: String,
9}
10
11/// HTTP client for the Interveil execution node.
12///
13/// Usage:
14///   let client = Client::new("http://localhost:3030");
15///   let response = client.submit(&signed_intent)?;
16///   println!("{}", response.tx_hash);
17#[derive(Debug, Clone)]
18pub struct Client {
19    base_url: String,
20}
21
22impl Client {
23    /// Create a new client pointing to an Interveil node.
24    ///
25    /// `base_url` should NOT have a trailing slash.
26    /// Example: "http://localhost:3030"
27    pub fn new(base_url: &str) -> Self {
28        let url = base_url.trim_end_matches('/');
29        Self {
30            base_url: url.to_string(),
31        }
32    }
33
34    /// Submit a signed intent to the node for execution on Solana.
35    ///
36    /// POST {base_url}/intents
37    /// Content-Type: application/json
38    /// Body: output of signed_intent.to_json()
39    ///
40    /// Returns SubmitResponse with tx_hash on success.
41    /// Returns VeilError::Http on failure.
42    pub fn submit(&self, signed_intent: &SignedIntent) -> Result<SubmitResponse, VeilError> {
43        let url = format!("{}/intents", self.base_url);
44        let body = signed_intent.to_json()?;
45
46        let response = reqwest::blocking::Client::new()
47            .post(&url)
48            .header("Content-Type", "application/json")
49            .body(body)
50            .send()
51            .map_err(|e| VeilError::Http(format!("request failed: {}", e)))?;
52
53        let status = response.status();
54        let response_text = response
55            .text()
56            .map_err(|e| VeilError::Http(format!("failed to read response: {}", e)))?;
57
58        if !status.is_success() {
59            return Err(VeilError::Http(format!(
60                "node returned status {}: {}",
61                status, response_text
62            )));
63        }
64
65        // Parse response JSON
66        // Expected: {"tx_hash":"...","status":"..."}
67        let parsed: serde_json::Value = serde_json::from_str(&response_text)
68            .map_err(|e| VeilError::Http(format!("invalid JSON response: {}", e)))?;
69
70        let tx_hash = parsed["tx_hash"]
71            .as_str()
72            .ok_or_else(|| VeilError::Http("missing tx_hash in response".to_string()))?
73            .to_string();
74
75        let status_str = parsed["status"]
76            .as_str()
77            .ok_or_else(|| VeilError::Http("missing status in response".to_string()))?
78            .to_string();
79
80        Ok(SubmitResponse {
81            tx_hash,
82            status: status_str,
83        })
84    }
85
86    /// Return the base URL this client is configured with.
87    pub fn base_url(&self) -> &str {
88        &self.base_url
89    }
90}