1use crate::types::{ExchangeError, Signature};
2
3const MAINNET_API_URL: &str = "https://api.hyperliquid.xyz";
5const TESTNET_API_URL: &str = "https://api.hyperliquid-testnet.xyz";
7
8pub struct ExchangeClient {
13 http: reqwest::Client,
14 base_url: String,
15 is_mainnet: bool,
16}
17
18impl ExchangeClient {
19 pub fn new(is_mainnet: bool) -> Self {
21 let base_url = Self::base_url_for(is_mainnet).to_string();
22 Self {
23 http: reqwest::Client::new(),
24 base_url,
25 is_mainnet,
26 }
27 }
28
29 pub fn base_url_for(is_mainnet: bool) -> &'static str {
31 if is_mainnet {
32 MAINNET_API_URL
33 } else {
34 TESTNET_API_URL
35 }
36 }
37
38 pub fn is_mainnet(&self) -> bool {
40 self.is_mainnet
41 }
42
43 pub async fn post_action(
47 &self,
48 action: serde_json::Value,
49 signature: &Signature,
50 nonce: u64,
51 vault_address: Option<&str>,
52 ) -> Result<serde_json::Value, ExchangeError> {
53 let mut payload = serde_json::json!({
54 "action": action,
55 "nonce": nonce,
56 "signature": {
57 "r": signature.r,
58 "s": signature.s,
59 "v": signature.v,
60 },
61 });
62
63 if let Some(vault) = vault_address {
64 payload.as_object_mut().unwrap().insert(
65 "vaultAddress".to_string(),
66 serde_json::Value::String(vault.to_string()),
67 );
68 }
69
70 let url = format!("{}/exchange", self.base_url);
71 let response = self
72 .http
73 .post(&url)
74 .json(&payload)
75 .send()
76 .await
77 .map_err(|e| ExchangeError::HttpError(e.to_string()))?;
78
79 let status = response.status();
80 let body: serde_json::Value = response
81 .json()
82 .await
83 .map_err(|e| ExchangeError::HttpError(format!("Failed to parse response: {}", e)))?;
84
85 if !status.is_success() {
86 return Err(ExchangeError::ApiError(format!(
87 "HTTP {}: {}",
88 status, body
89 )));
90 }
91
92 Ok(body)
93 }
94
95 pub async fn post_info(
99 &self,
100 request: serde_json::Value,
101 ) -> Result<serde_json::Value, ExchangeError> {
102 let url = format!("{}/info", self.base_url);
103 let response = self
104 .http
105 .post(&url)
106 .json(&request)
107 .send()
108 .await
109 .map_err(|e| ExchangeError::HttpError(e.to_string()))?;
110
111 let status = response.status();
112 let body: serde_json::Value = response
113 .json()
114 .await
115 .map_err(|e| ExchangeError::HttpError(format!("Failed to parse response: {}", e)))?;
116
117 if !status.is_success() {
118 return Err(ExchangeError::ApiError(format!(
119 "HTTP {}: {}",
120 status, body
121 )));
122 }
123
124 Ok(body)
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn test_base_url_mainnet() {
134 assert_eq!(ExchangeClient::base_url_for(true), MAINNET_API_URL);
135 }
136
137 #[test]
138 fn test_base_url_testnet() {
139 assert_eq!(ExchangeClient::base_url_for(false), TESTNET_API_URL);
140 }
141
142 #[test]
143 fn test_new_client_mainnet() {
144 let client = ExchangeClient::new(true);
145 assert!(client.is_mainnet());
146 assert_eq!(client.base_url, MAINNET_API_URL);
147 }
148
149 #[test]
150 fn test_new_client_testnet() {
151 let client = ExchangeClient::new(false);
152 assert!(!client.is_mainnet());
153 assert_eq!(client.base_url, TESTNET_API_URL);
154 }
155}