1use 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 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 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 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 }
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 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 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 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 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 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 }).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 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 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 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 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 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 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 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 }
349 }
350 }
351 Ok(Message::Ping(_)) => {}
352 Ok(Message::Close(_)) => {
353 break;
355 }
356 Err(e) => {
357 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}