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 L2 order book for a coin.
38    pub fn l2_book(coin: &str) -> Self {
39        Self {
40            request_type: "l2Book".to_string(),
41            params: serde_json::json!({ "coin": coin }),
42        }
43    }
44
45    /// Creates a request to get user fills.
46    pub fn user_fills(user: &str) -> Self {
47        Self {
48            request_type: "userFills".to_string(),
49            params: serde_json::json!({ "user": user }),
50        }
51    }
52
53    /// Creates a request to get order status for a user.
54    pub fn order_status(user: &str, oid: u64) -> Self {
55        Self {
56            request_type: "orderStatus".to_string(),
57            params: serde_json::json!({ "user": user, "oid": oid }),
58        }
59    }
60}
61
62/// Represents an exchange action wrapper for `POST /exchange`.
63#[derive(Debug, Clone, Serialize)]
64pub struct ExchangeAction {
65    #[serde(rename = "type")]
66    pub action_type: String,
67    #[serde(flatten)]
68    pub params: Value,
69}
70
71impl ExchangeAction {
72    /// Creates an action to place orders.
73    pub fn order(orders: Value) -> Self {
74        Self {
75            action_type: "order".to_string(),
76            params: serde_json::json!({ "orders": orders }),
77        }
78    }
79
80    /// Creates an action to cancel orders.
81    pub fn cancel(cancels: Value) -> Self {
82        Self {
83            action_type: "cancel".to_string(),
84            params: serde_json::json!({ "cancels": cancels }),
85        }
86    }
87
88    /// Creates an action to cancel orders by client order ID.
89    pub fn cancel_by_cloid(cancels: Value) -> Self {
90        Self {
91            action_type: "cancelByCloid".to_string(),
92            params: serde_json::json!({ "cancels": cancels }),
93        }
94    }
95
96    /// Creates an action to modify an order.
97    pub fn modify(oid: u64, order: Value) -> Self {
98        Self {
99            action_type: "modify".to_string(),
100            params: serde_json::json!({ "oid": oid, "order": order }),
101        }
102    }
103
104    /// Creates an action to update leverage for an asset.
105    pub fn update_leverage(asset: u32, is_cross: bool, leverage: u32) -> Self {
106        Self {
107            action_type: "updateLeverage".to_string(),
108            params: serde_json::json!({
109                "asset": asset,
110                "isCross": is_cross,
111                "leverage": leverage
112            }),
113        }
114    }
115
116    /// Creates an action to update isolated margin for an asset.
117    pub fn update_isolated_margin(asset: u32, is_buy: bool, ntli: i64) -> Self {
118        Self {
119            action_type: "updateIsolatedMargin".to_string(),
120            params: serde_json::json!({
121                "asset": asset,
122                "isBuy": is_buy,
123                "ntli": ntli
124            }),
125        }
126    }
127}
128
129////////////////////////////////////////////////////////////////////////////////
130// Tests
131////////////////////////////////////////////////////////////////////////////////
132
133#[cfg(test)]
134mod tests {
135    use rstest::rstest;
136
137    use super::*;
138
139    #[rstest]
140    fn test_info_request_meta() {
141        let req = InfoRequest::meta();
142
143        assert_eq!(req.request_type, "meta");
144        assert_eq!(req.params, Value::Null);
145    }
146
147    #[rstest]
148    fn test_info_request_l2_book() {
149        let req = InfoRequest::l2_book("BTC");
150
151        assert_eq!(req.request_type, "l2Book");
152        let json = serde_json::to_string(&req).unwrap();
153        assert!(json.contains("\"coin\":\"BTC\""));
154    }
155
156    #[rstest]
157    fn test_exchange_action_order() {
158        let orders =
159            serde_json::json!([{"asset": 0, "isBuy": true, "sz": "1.0", "limitPx": "50000"}]);
160
161        let action = ExchangeAction::order(orders);
162
163        assert_eq!(action.action_type, "order");
164        let json = serde_json::to_string(&action).unwrap();
165        assert!(json.contains("\"orders\""));
166    }
167
168    #[rstest]
169    fn test_exchange_action_cancel() {
170        let cancels = serde_json::json!([{"asset": 0, "oid": 123}]);
171
172        let action = ExchangeAction::cancel(cancels);
173
174        assert_eq!(action.action_type, "cancel");
175    }
176}