nautilus_hyperliquid/http/
query.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16use serde::Serialize;
17use serde_json::Value;
18
19/// Represents an info request wrapper for `POST /info`.
20#[derive(Debug, Clone, Serialize)]
21pub struct InfoRequest {
22    #[serde(rename = "type")]
23    pub request_type: String,
24    #[serde(flatten)]
25    pub params: Value,
26}
27
28impl InfoRequest {
29    /// Creates a request to get metadata about available markets.
30    pub fn meta() -> Self {
31        Self {
32            request_type: "meta".to_string(),
33            params: Value::Null,
34        }
35    }
36
37    /// Creates a request to get spot metadata (tokens and pairs).
38    pub fn spot_meta() -> Self {
39        Self {
40            request_type: "spotMeta".to_string(),
41            params: Value::Null,
42        }
43    }
44
45    /// Creates a request to get metadata with asset contexts (for price precision).
46    pub fn meta_and_asset_ctxs() -> Self {
47        Self {
48            request_type: "metaAndAssetCtxs".to_string(),
49            params: Value::Null,
50        }
51    }
52
53    /// Creates a request to get spot metadata with asset contexts.
54    pub fn spot_meta_and_asset_ctxs() -> Self {
55        Self {
56            request_type: "spotMetaAndAssetCtxs".to_string(),
57            params: Value::Null,
58        }
59    }
60
61    /// Creates a request to get L2 order book for a coin.
62    pub fn l2_book(coin: &str) -> Self {
63        Self {
64            request_type: "l2Book".to_string(),
65            params: serde_json::json!({ "coin": coin }),
66        }
67    }
68
69    /// Creates a request to get user fills.
70    pub fn user_fills(user: &str) -> Self {
71        Self {
72            request_type: "userFills".to_string(),
73            params: serde_json::json!({ "user": user }),
74        }
75    }
76
77    /// Creates a request to get order status for a user.
78    pub fn order_status(user: &str, oid: u64) -> Self {
79        Self {
80            request_type: "orderStatus".to_string(),
81            params: serde_json::json!({ "user": user, "oid": oid }),
82        }
83    }
84
85    /// Creates a request to get all open orders for a user.
86    pub fn open_orders(user: &str) -> Self {
87        Self {
88            request_type: "openOrders".to_string(),
89            params: serde_json::json!({ "user": user }),
90        }
91    }
92
93    /// Creates a request to get frontend open orders (includes more detail).
94    pub fn frontend_open_orders(user: &str) -> Self {
95        Self {
96            request_type: "frontendOpenOrders".to_string(),
97            params: serde_json::json!({ "user": user }),
98        }
99    }
100
101    /// Creates a request to get user state (balances, positions, margin).
102    pub fn clearinghouse_state(user: &str) -> Self {
103        Self {
104            request_type: "clearinghouseState".to_string(),
105            params: serde_json::json!({ "user": user }),
106        }
107    }
108
109    /// Creates a request to get candle/bar data.
110    ///
111    /// # Arguments
112    /// * `coin` - The coin symbol (e.g., "BTC")
113    /// * `interval` - The timeframe (e.g., "1m", "5m", "15m", "1h", "4h", "1d")
114    /// * `start_time` - Start timestamp in milliseconds
115    /// * `end_time` - End timestamp in milliseconds
116    pub fn candle_snapshot(coin: &str, interval: &str, start_time: u64, end_time: u64) -> Self {
117        Self {
118            request_type: "candleSnapshot".to_string(),
119            params: serde_json::json!({
120                "req": {
121                    "coin": coin,
122                    "interval": interval,
123                    "startTime": start_time,
124                    "endTime": end_time
125                }
126            }),
127        }
128    }
129}
130
131/// Represents an exchange action wrapper for `POST /exchange`.
132#[derive(Debug, Clone, Serialize)]
133pub struct ExchangeAction {
134    #[serde(rename = "type")]
135    pub action_type: String,
136    #[serde(flatten)]
137    pub params: Value,
138}
139
140impl ExchangeAction {
141    /// Creates an action to place orders.
142    pub fn order(orders: Value) -> Self {
143        Self {
144            action_type: "order".to_string(),
145            params: serde_json::json!({ "orders": orders }),
146        }
147    }
148
149    /// Creates an action to cancel orders.
150    pub fn cancel(cancels: Value) -> Self {
151        Self {
152            action_type: "cancel".to_string(),
153            params: serde_json::json!({ "cancels": cancels }),
154        }
155    }
156
157    /// Creates an action to cancel orders by client order ID.
158    pub fn cancel_by_cloid(cancels: Value) -> Self {
159        Self {
160            action_type: "cancelByCloid".to_string(),
161            params: serde_json::json!({ "cancels": cancels }),
162        }
163    }
164
165    /// Creates an action to modify an order.
166    pub fn modify(oid: u64, order: Value) -> Self {
167        Self {
168            action_type: "modify".to_string(),
169            params: serde_json::json!({ "oid": oid, "order": order }),
170        }
171    }
172
173    /// Creates an action to update leverage for an asset.
174    pub fn update_leverage(asset: u32, is_cross: bool, leverage: u32) -> Self {
175        Self {
176            action_type: "updateLeverage".to_string(),
177            params: serde_json::json!({
178                "asset": asset,
179                "isCross": is_cross,
180                "leverage": leverage
181            }),
182        }
183    }
184
185    /// Creates an action to update isolated margin for an asset.
186    pub fn update_isolated_margin(asset: u32, is_buy: bool, ntli: i64) -> Self {
187        Self {
188            action_type: "updateIsolatedMargin".to_string(),
189            params: serde_json::json!({
190                "asset": asset,
191                "isBuy": is_buy,
192                "ntli": ntli
193            }),
194        }
195    }
196}
197
198////////////////////////////////////////////////////////////////////////////////
199// Tests
200////////////////////////////////////////////////////////////////////////////////
201
202#[cfg(test)]
203mod tests {
204    use rstest::rstest;
205
206    use super::*;
207
208    #[rstest]
209    fn test_info_request_meta() {
210        let req = InfoRequest::meta();
211
212        assert_eq!(req.request_type, "meta");
213        assert_eq!(req.params, Value::Null);
214    }
215
216    #[rstest]
217    fn test_info_request_l2_book() {
218        let req = InfoRequest::l2_book("BTC");
219
220        assert_eq!(req.request_type, "l2Book");
221        let json = serde_json::to_string(&req).unwrap();
222        assert!(json.contains("\"coin\":\"BTC\""));
223    }
224
225    #[rstest]
226    fn test_exchange_action_order() {
227        let orders =
228            serde_json::json!([{"asset": 0, "isBuy": true, "sz": "1.0", "limitPx": "50000"}]);
229
230        let action = ExchangeAction::order(orders);
231
232        assert_eq!(action.action_type, "order");
233        let json = serde_json::to_string(&action).unwrap();
234        assert!(json.contains("\"orders\""));
235    }
236
237    #[rstest]
238    fn test_exchange_action_cancel() {
239        let cancels = serde_json::json!([{"asset": 0, "oid": 123}]);
240
241        let action = ExchangeAction::cancel(cancels);
242
243        assert_eq!(action.action_type, "cancel");
244    }
245}