hyperliquid_rs/
client.rs

1// src/client.rs
2use futures_util::{SinkExt, StreamExt};
3use hex;
4use hmac::{Hmac, Mac};
5use reqwest::Client;
6use serde_json::json;
7use sha2::Sha256;
8use std::time::Duration;
9use tokio_tungstenite::connect_async;
10use tungstenite::{Message, client};
11
12use crate::types::account::AccountInfo;
13use crate::types::market::MarketOrderRequest;
14use crate::types::order::{OrderInfo, OrderRequest, OrderStatusResponse, OrderType};
15use crate::types::price::PriceData;
16use crate::types::response::ApiResponse;
17use crate::types::trade::{AssetPosition, Side, TradeResult};
18use crate::types::websocket::WebSocketMessage;
19
20type HmacSha256 = Hmac<Sha256>;
21
22const BASE_URL: &str = "https://api.hyperliquid.xyz";
23const WS_URL: &str = "wss://api.hyperliquid.xyz/ws";
24const TIME_OUT: u64 = 10;
25
26pub struct HyperliquidClient {
27    http_client: Client,
28    base_url: String,
29    ws_url: String,
30    api_key: Option<String>,
31    secret_key: Option<String>,
32}
33
34impl HyperliquidClient {
35    pub fn new() -> Result<Self, String> {
36        match Client::builder()
37            .timeout(Duration::from_secs(TIME_OUT))
38            .build()
39        {
40            Ok(client) => {
41                return Ok(Self {
42                    http_client: client,
43                    base_url: BASE_URL.to_string(),
44                    ws_url: WS_URL.to_string(),
45                    api_key: None,
46                    secret_key: None,
47                });
48            }
49            Err(_) => {
50                return Err("create http client fail".to_string());
51            }
52        }
53    }
54
55    pub fn auth(mut self, api_key: String, secret_key: String) -> Self {
56        self.api_key = Some(api_key);
57        self.secret_key = Some(secret_key);
58        self
59    }
60
61    /// create signature
62    fn create_signature(
63        &self,
64        timestamp: u64,
65        method: &str,
66        path: &str,
67        body: &str,
68    ) -> Result<String, ()> {
69        let secret_key = self.secret_key.as_ref().ok_or_else(|| {}).unwrap();
70        let message = format!("{}{}{}{}", timestamp, method, path, body);
71        let mut mac = HmacSha256::new_from_slice(secret_key.as_bytes())
72            .map_err(|e| {})
73            .unwrap();
74        mac.update(message.as_bytes());
75        let result = mac.finalize();
76        let signature = hex::encode(result.into_bytes());
77        Ok(signature)
78    }
79
80    /// send verify request
81    async fn send_verify_request<T: serde::de::DeserializeOwned>(
82        &self,
83        method: &str,
84        endpoint: &str,
85        body: serde_json::Value,
86    ) -> Result<T, ()> {
87        let api_key = self.api_key.as_ref().ok_or_else(|| {}).unwrap();
88        let timestamp = chrono::Utc::now().timestamp_millis() as u64;
89        let signature = self.create_signature(timestamp, method, endpoint, &body.to_string());
90        let url = format!("{}{}", self.base_url, endpoint);
91        let response = self
92            .http_client
93            .request(method.parse().unwrap(), &url)
94            .header("X-API-KEY", api_key)
95            .header("X-SIGNATURE", signature.unwrap())
96            .header("X-TIMESTAMP", timestamp.to_string())
97            .json(&body)
98            .send()
99            .await
100            .unwrap();
101        let api_response: ApiResponse<T> = response.json().await.unwrap();
102        Ok(api_response.response)
103    }
104
105    /// get price by symbol
106    pub async fn get_price_by_symbol(&self, symbol: &str) -> Result<f64, ()> {
107        let url = format!("{}/info", self.base_url);
108        let payload = json!({
109            "type": "ticker",
110            "coins": [symbol],
111        });
112        let response = self
113            .http_client
114            .post(&url)
115            .json(&payload)
116            .send()
117            .await
118            .unwrap();
119        if !response.status().is_success() {
120            // response statuc error
121        }
122        let price_data: Vec<PriceData> = response.json().await.unwrap();
123        price_data
124            .first()
125            .and_then(|data| data.mid.or(data.last))
126            .ok_or_else(|| {})
127    }
128
129    /// limit order
130    pub async fn place_limit_order(
131        &self,
132        symbol: &str,
133        side: Side,
134        size: f64,
135        price: f64,
136        reduce_only: bool,
137    ) -> Result<OrderStatusResponse, ()> {
138        let endpoint = "/exchange";
139        let order = OrderRequest {
140            coin: symbol.to_string(),
141            is_buy: matches!(side, Side::Buy),
142            sz: size.to_string(),
143            limit_px: price.to_string(),
144            order_type: OrderType::Limit,
145            reduce_only,
146            cloid: None,
147        };
148        let payload = json!({
149            "action": "order",
150            "order": order,
151        });
152        let response: TradeResult = self.send_verify_request("POST", endpoint, payload).await?;
153        response.data.statuses.into_iter().next().ok_or_else(|| {})
154    }
155
156    /// current market price order
157    pub async fn place_current_market_price_order(
158        &self,
159        symbol: &str,
160        side: Side,
161        size: f64,
162        reduce_only: bool,
163    ) -> Result<OrderStatusResponse, ()> {
164        let endpoint = "/exchange";
165        let order = MarketOrderRequest {
166            coin: symbol.to_string(),
167            is_buy: matches!(side, Side::Buy),
168            sz: size.to_string(),
169            order_type: OrderType::Market,
170            reduce_only,
171            cloid: None,
172        };
173        let payload = json!({
174            "action": "order",
175            "order": order,
176        });
177        let response: TradeResult = self.send_verify_request("POST", endpoint, payload).await?;
178        response.data.statuses.into_iter().next().ok_or_else(|| {})
179    }
180
181    /// open long position
182    pub async fn open_long_position(
183        &self,
184        symbol: &str,
185        size: f64,
186        price: f64,
187        order_type: OrderType,
188    ) -> Result<OrderStatusResponse, ()> {
189        self.place_limit_order(symbol, Side::Buy, size, price, false)
190            .await
191    }
192
193    /// open short position
194    pub async fn open_short_position(
195        &self,
196        symbol: &str,
197        size: f64,
198        price: f64,
199        order_type: OrderType,
200    ) -> Result<OrderStatusResponse, ()> {
201        self.place_limit_order(symbol, Side::Sell, size, price, false)
202            .await
203    }
204
205    /// get current account information
206    pub async fn get_account_info(&self) -> Result<AccountInfo, ()> {
207        let endpoint = "/info";
208        let payload = json!({
209            "type": "account",
210            "user": self.api_key.as_ref().ok_or_else(|| {
211                // the API key is empty.
212            }).unwrap(),
213        });
214        let response = self
215            .http_client
216            .post(&format!("{}{}", self.base_url, endpoint))
217            .json(&payload)
218            .send()
219            .await
220            .unwrap();
221        if !response.status().is_success() {
222            return Err(());
223        }
224        let account_info: AccountInfo = response.json().await.unwrap();
225        Ok(account_info)
226    }
227
228    /// get the current position
229    pub async fn get_positions(&self) -> Result<Vec<AssetPosition>, ()> {
230        let account_info = self.get_account_info().await.unwrap();
231        Ok(account_info.asset_positions)
232    }
233
234    /// get open order info
235    pub async fn get_open_orders(&self) -> Result<Vec<OrderInfo>, ()> {
236        let endpoint = "/info";
237        let payload = json!({
238            "type": "openOrders",
239            "user": self.api_key.as_ref().ok_or_else(||{}).unwrap(),
240        });
241        let response = self
242            .http_client
243            .post(&format!("{}{}", self.base_url, endpoint))
244            .json(&payload)
245            .send()
246            .await
247            .unwrap();
248        if !response.status().is_success() {
249            return Err(());
250        }
251        let orders: Vec<OrderInfo> = response.json().await.unwrap();
252        Ok(orders)
253    }
254
255    /// cancel order
256    pub async fn cancel_order(&self, order_id: u64) -> Result<OrderStatusResponse, ()> {
257        let endpoint = "/exchange";
258        let payload = json!({
259            "action": "cancel",
260            "oid": order_id,
261        });
262        let response: TradeResult = self
263            .send_verify_request("POST", endpoint, payload)
264            .await
265            .unwrap();
266        response.data.statuses.into_iter().next().ok_or_else(|| {})
267    }
268
269    /// modify order
270    pub async fn modify_order(
271        &self,
272        order_id: u64,
273        new_size: Option<f64>,
274        new_price: Option<f64>,
275    ) -> Result<OrderStatusResponse, ()> {
276        let endpoint = "/exchange";
277        let mut modifications = serde_json::Map::new();
278        modifications.insert("oid".to_string(), json!(order_id));
279        if let Some(size) = new_size {
280            modifications.insert("sz".to_string(), json!(size.to_string()));
281        }
282        if let Some(price) = new_price {
283            modifications.insert("limit_px".to_string(), json!(price.to_string()));
284        }
285        let payload = json!({
286            "action": "modify",
287            "modifications": modifications,
288        });
289        let response: TradeResult = self.send_verify_request("POST", endpoint, payload).await?;
290        response.data.statuses.into_iter().next().ok_or_else(|| {})
291    }
292
293    /// set leverage
294    pub async fn set_leverage(&self, symbol: &str, leverage: u32) -> Result<(), ()> {
295        let endpoint = "/exchange";
296        let payload = json!({
297            "action": "updateLeverage",
298            "coin": symbol,
299            "leverage": leverage,
300        });
301        let _: serde_json::Value = self.send_verify_request("POST", endpoint, payload).await?;
302        Ok(())
303    }
304
305    /// get transaction pair information
306    pub async fn get_transaction_pair_info(&self) -> Result<Vec<serde_json::Value>, ()> {
307        let endpoint = "/info";
308        let payload = json!({
309            "type": "meta",
310        });
311        let response = self
312            .http_client
313            .post(&format!("{}{}", self.base_url, endpoint))
314            .json(&payload)
315            .send()
316            .await
317            .unwrap();
318        if !response.status().is_success() {
319            return Err(());
320        }
321        let instruments: Vec<serde_json::Value> = response.json().await.unwrap();
322        Ok(instruments)
323    }
324
325    /// WebSocket subscription price
326    pub async fn ws_subscription_price(&self, symbols: Vec<String>) -> Result<(), ()> {
327        let (ws_stream, _) = connect_async(&self.ws_url).await.unwrap();
328        let (mut write, mut read) = ws_stream.split();
329
330        let subscribe_message = json!({
331            "method": "subscribe",
332            "subscription": {
333                "type": "ticker",
334                "coins": symbols,
335            }
336        });
337        write
338            .send(Message::Text(subscribe_message.to_string().into()))
339            .await
340            .unwrap();
341        tokio::spawn(async move {
342            while let Some(message) = read.next().await {
343                match message {
344                    Ok(Message::Text(text)) => {
345                        if let Ok(ws_message) = serde_json::from_str::<WebSocketMessage>(&text) {
346                            if ws_message.channel == "ticker" {
347                                // price update
348                            }
349                        }
350                    }
351                    Ok(Message::Ping(_)) => {}
352                    Ok(Message::Close(_)) => {
353                        // close
354                        break;
355                    }
356                    Err(e) => {
357                        // error
358                        break;
359                    }
360                    _ => {}
361                }
362            }
363        });
364        Ok(())
365    }
366}
367
368impl Default for HyperliquidClient {
369    fn default() -> Self {
370        HyperliquidClient::new().unwrap()
371    }
372}