1use serde::Serialize;
17
18use crate::{
19 common::enums::{HyperliquidBarInterval, HyperliquidInfoRequestType},
20 http::models::{
21 HyperliquidExecCancelByCloidRequest, HyperliquidExecGrouping,
22 HyperliquidExecModifyOrderRequest, HyperliquidExecPlaceOrderRequest,
23 },
24};
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
28#[serde(rename_all = "camelCase")]
29pub enum ExchangeActionType {
30 Order,
32 Cancel,
34 CancelByCloid,
36 Modify,
38 UpdateLeverage,
40 UpdateIsolatedMargin,
42}
43
44impl AsRef<str> for ExchangeActionType {
45 fn as_ref(&self) -> &str {
46 match self {
47 Self::Order => "order",
48 Self::Cancel => "cancel",
49 Self::CancelByCloid => "cancelByCloid",
50 Self::Modify => "modify",
51 Self::UpdateLeverage => "updateLeverage",
52 Self::UpdateIsolatedMargin => "updateIsolatedMargin",
53 }
54 }
55}
56
57#[derive(Debug, Clone, Serialize)]
59pub struct OrderParams {
60 pub orders: Vec<HyperliquidExecPlaceOrderRequest>,
61 pub grouping: HyperliquidExecGrouping,
62}
63
64#[derive(Debug, Clone, Serialize)]
66pub struct CancelParams {
67 pub cancels: Vec<HyperliquidExecCancelByCloidRequest>,
68}
69
70#[derive(Debug, Clone, Serialize)]
72pub struct ModifyParams {
73 pub oid: u64,
74 pub order: HyperliquidExecModifyOrderRequest,
75}
76
77#[derive(Debug, Clone, Serialize)]
79#[serde(rename_all = "camelCase")]
80pub struct UpdateLeverageParams {
81 pub asset: u32,
82 pub is_cross: bool,
83 pub leverage: u32,
84}
85
86#[derive(Debug, Clone, Serialize)]
88#[serde(rename_all = "camelCase")]
89pub struct UpdateIsolatedMarginParams {
90 pub asset: u32,
91 pub is_buy: bool,
92 pub ntli: i64,
93}
94
95#[derive(Debug, Clone, Serialize)]
97pub struct L2BookParams {
98 pub coin: String,
99}
100
101#[derive(Debug, Clone, Serialize)]
103pub struct UserFillsParams {
104 pub user: String,
105}
106
107#[derive(Debug, Clone, Serialize)]
109pub struct OrderStatusParams {
110 pub user: String,
111 pub oid: u64,
112}
113
114#[derive(Debug, Clone, Serialize)]
116pub struct OpenOrdersParams {
117 pub user: String,
118}
119
120#[derive(Debug, Clone, Serialize)]
122pub struct ClearinghouseStateParams {
123 pub user: String,
124}
125
126#[derive(Debug, Clone, Serialize)]
128#[serde(rename_all = "camelCase")]
129pub struct CandleSnapshotReq {
130 pub coin: String,
131 pub interval: HyperliquidBarInterval,
132 pub start_time: u64,
133 pub end_time: u64,
134}
135
136#[derive(Debug, Clone, Serialize)]
138pub struct CandleSnapshotParams {
139 pub req: CandleSnapshotReq,
140}
141
142#[derive(Debug, Clone, Serialize)]
144#[serde(untagged)]
145pub enum InfoRequestParams {
146 L2Book(L2BookParams),
147 UserFills(UserFillsParams),
148 OrderStatus(OrderStatusParams),
149 OpenOrders(OpenOrdersParams),
150 ClearinghouseState(ClearinghouseStateParams),
151 CandleSnapshot(CandleSnapshotParams),
152 None,
153}
154
155#[derive(Debug, Clone, Serialize)]
157pub struct InfoRequest {
158 #[serde(rename = "type")]
159 pub request_type: HyperliquidInfoRequestType,
160 #[serde(flatten)]
161 pub params: InfoRequestParams,
162}
163
164impl InfoRequest {
165 pub fn meta() -> Self {
167 Self {
168 request_type: HyperliquidInfoRequestType::Meta,
169 params: InfoRequestParams::None,
170 }
171 }
172
173 pub fn spot_meta() -> Self {
175 Self {
176 request_type: HyperliquidInfoRequestType::SpotMeta,
177 params: InfoRequestParams::None,
178 }
179 }
180
181 pub fn meta_and_asset_ctxs() -> Self {
183 Self {
184 request_type: HyperliquidInfoRequestType::MetaAndAssetCtxs,
185 params: InfoRequestParams::None,
186 }
187 }
188
189 pub fn spot_meta_and_asset_ctxs() -> Self {
191 Self {
192 request_type: HyperliquidInfoRequestType::SpotMetaAndAssetCtxs,
193 params: InfoRequestParams::None,
194 }
195 }
196
197 pub fn l2_book(coin: &str) -> Self {
199 Self {
200 request_type: HyperliquidInfoRequestType::L2Book,
201 params: InfoRequestParams::L2Book(L2BookParams {
202 coin: coin.to_string(),
203 }),
204 }
205 }
206
207 pub fn user_fills(user: &str) -> Self {
209 Self {
210 request_type: HyperliquidInfoRequestType::UserFills,
211 params: InfoRequestParams::UserFills(UserFillsParams {
212 user: user.to_string(),
213 }),
214 }
215 }
216
217 pub fn order_status(user: &str, oid: u64) -> Self {
219 Self {
220 request_type: HyperliquidInfoRequestType::OrderStatus,
221 params: InfoRequestParams::OrderStatus(OrderStatusParams {
222 user: user.to_string(),
223 oid,
224 }),
225 }
226 }
227
228 pub fn open_orders(user: &str) -> Self {
230 Self {
231 request_type: HyperliquidInfoRequestType::OpenOrders,
232 params: InfoRequestParams::OpenOrders(OpenOrdersParams {
233 user: user.to_string(),
234 }),
235 }
236 }
237
238 pub fn frontend_open_orders(user: &str) -> Self {
240 Self {
241 request_type: HyperliquidInfoRequestType::FrontendOpenOrders,
242 params: InfoRequestParams::OpenOrders(OpenOrdersParams {
243 user: user.to_string(),
244 }),
245 }
246 }
247
248 pub fn clearinghouse_state(user: &str) -> Self {
250 Self {
251 request_type: HyperliquidInfoRequestType::ClearinghouseState,
252 params: InfoRequestParams::ClearinghouseState(ClearinghouseStateParams {
253 user: user.to_string(),
254 }),
255 }
256 }
257
258 pub fn candle_snapshot(
260 coin: &str,
261 interval: HyperliquidBarInterval,
262 start_time: u64,
263 end_time: u64,
264 ) -> Self {
265 Self {
266 request_type: HyperliquidInfoRequestType::CandleSnapshot,
267 params: InfoRequestParams::CandleSnapshot(CandleSnapshotParams {
268 req: CandleSnapshotReq {
269 coin: coin.to_string(),
270 interval,
271 start_time,
272 end_time,
273 },
274 }),
275 }
276 }
277}
278
279#[derive(Debug, Clone, Serialize)]
281#[serde(untagged)]
282pub enum ExchangeActionParams {
283 Order(OrderParams),
284 Cancel(CancelParams),
285 Modify(ModifyParams),
286 UpdateLeverage(UpdateLeverageParams),
287 UpdateIsolatedMargin(UpdateIsolatedMarginParams),
288}
289
290#[derive(Debug, Clone, Serialize)]
292pub struct ExchangeAction {
293 #[serde(rename = "type", serialize_with = "serialize_action_type")]
294 pub action_type: ExchangeActionType,
295 #[serde(flatten)]
296 pub params: ExchangeActionParams,
297}
298
299fn serialize_action_type<S>(
300 action_type: &ExchangeActionType,
301 serializer: S,
302) -> Result<S::Ok, S::Error>
303where
304 S: serde::Serializer,
305{
306 serializer.serialize_str(action_type.as_ref())
307}
308
309impl ExchangeAction {
310 pub fn order(orders: Vec<HyperliquidExecPlaceOrderRequest>) -> Self {
312 Self {
313 action_type: ExchangeActionType::Order,
314 params: ExchangeActionParams::Order(OrderParams {
315 orders,
316 grouping: HyperliquidExecGrouping::Na,
317 }),
318 }
319 }
320
321 pub fn cancel(cancels: Vec<HyperliquidExecCancelByCloidRequest>) -> Self {
323 Self {
324 action_type: ExchangeActionType::Cancel,
325 params: ExchangeActionParams::Cancel(CancelParams { cancels }),
326 }
327 }
328
329 pub fn cancel_by_cloid(cancels: Vec<HyperliquidExecCancelByCloidRequest>) -> Self {
331 Self {
332 action_type: ExchangeActionType::CancelByCloid,
333 params: ExchangeActionParams::Cancel(CancelParams { cancels }),
334 }
335 }
336
337 pub fn modify(oid: u64, order: HyperliquidExecModifyOrderRequest) -> Self {
339 Self {
340 action_type: ExchangeActionType::Modify,
341 params: ExchangeActionParams::Modify(ModifyParams { oid, order }),
342 }
343 }
344
345 pub fn update_leverage(asset: u32, is_cross: bool, leverage: u32) -> Self {
347 Self {
348 action_type: ExchangeActionType::UpdateLeverage,
349 params: ExchangeActionParams::UpdateLeverage(UpdateLeverageParams {
350 asset,
351 is_cross,
352 leverage,
353 }),
354 }
355 }
356
357 pub fn update_isolated_margin(asset: u32, is_buy: bool, ntli: i64) -> Self {
359 Self {
360 action_type: ExchangeActionType::UpdateIsolatedMargin,
361 params: ExchangeActionParams::UpdateIsolatedMargin(UpdateIsolatedMarginParams {
362 asset,
363 is_buy,
364 ntli,
365 }),
366 }
367 }
368}
369
370#[cfg(test)]
375mod tests {
376 use rstest::rstest;
377
378 use super::*;
379
380 #[rstest]
381 fn test_info_request_meta() {
382 let req = InfoRequest::meta();
383
384 assert_eq!(req.request_type, HyperliquidInfoRequestType::Meta);
385 assert!(matches!(req.params, InfoRequestParams::None));
386 }
387
388 #[rstest]
389 fn test_info_request_l2_book() {
390 let req = InfoRequest::l2_book("BTC");
391
392 assert_eq!(req.request_type, HyperliquidInfoRequestType::L2Book);
393 let json = serde_json::to_string(&req).unwrap();
394 assert!(json.contains("\"coin\":\"BTC\""));
395 }
396
397 #[rstest]
398 fn test_exchange_action_order() {
399 use rust_decimal::Decimal;
400
401 use crate::http::models::{
402 HyperliquidExecLimitParams, HyperliquidExecOrderKind, HyperliquidExecPlaceOrderRequest,
403 HyperliquidExecTif,
404 };
405
406 let order = HyperliquidExecPlaceOrderRequest {
407 asset: 0,
408 is_buy: true,
409 price: Decimal::new(50000, 0),
410 size: Decimal::new(1, 0),
411 reduce_only: false,
412 kind: HyperliquidExecOrderKind::Limit {
413 limit: HyperliquidExecLimitParams {
414 tif: HyperliquidExecTif::Gtc,
415 },
416 },
417 cloid: None,
418 };
419
420 let action = ExchangeAction::order(vec![order]);
421
422 assert_eq!(action.action_type, ExchangeActionType::Order);
423 let json = serde_json::to_string(&action).unwrap();
424 assert!(json.contains("\"orders\""));
425 }
426
427 #[rstest]
428 fn test_exchange_action_cancel() {
429 use crate::http::models::{Cloid, HyperliquidExecCancelByCloidRequest};
430
431 let cancel = HyperliquidExecCancelByCloidRequest {
432 asset: 0,
433 cloid: Cloid::from_hex("0x00000000000000000000000000000000").unwrap(),
434 };
435
436 let action = ExchangeAction::cancel(vec![cancel]);
437
438 assert_eq!(action.action_type, ExchangeActionType::Cancel);
439 }
440
441 #[rstest]
442 fn test_exchange_action_serialization() {
443 use rust_decimal::Decimal;
444
445 use crate::http::models::{
446 HyperliquidExecLimitParams, HyperliquidExecOrderKind, HyperliquidExecPlaceOrderRequest,
447 HyperliquidExecTif,
448 };
449
450 let order = HyperliquidExecPlaceOrderRequest {
451 asset: 0,
452 is_buy: true,
453 price: Decimal::new(50000, 0),
454 size: Decimal::new(1, 0),
455 reduce_only: false,
456 kind: HyperliquidExecOrderKind::Limit {
457 limit: HyperliquidExecLimitParams {
458 tif: HyperliquidExecTif::Gtc,
459 },
460 },
461 cloid: None,
462 };
463
464 let action = ExchangeAction::order(vec![order]);
465
466 let json = serde_json::to_string(&action).unwrap();
467 assert!(json.contains(r#""type":"order""#));
469 assert!(json.contains(r#""orders""#));
470 assert!(json.contains(r#""grouping":"na""#));
471 }
472
473 #[rstest]
474 fn test_exchange_action_type_as_ref() {
475 assert_eq!(ExchangeActionType::Order.as_ref(), "order");
476 assert_eq!(ExchangeActionType::Cancel.as_ref(), "cancel");
477 assert_eq!(ExchangeActionType::CancelByCloid.as_ref(), "cancelByCloid");
478 assert_eq!(ExchangeActionType::Modify.as_ref(), "modify");
479 assert_eq!(
480 ExchangeActionType::UpdateLeverage.as_ref(),
481 "updateLeverage"
482 );
483 assert_eq!(
484 ExchangeActionType::UpdateIsolatedMargin.as_ref(),
485 "updateIsolatedMargin"
486 );
487 }
488
489 #[rstest]
490 fn test_update_leverage_serialization() {
491 let action = ExchangeAction::update_leverage(1, true, 10);
492 let json = serde_json::to_string(&action).unwrap();
493
494 assert!(json.contains(r#""type":"updateLeverage""#));
495 assert!(json.contains(r#""asset":1"#));
496 assert!(json.contains(r#""isCross":true"#));
497 assert!(json.contains(r#""leverage":10"#));
498 }
499
500 #[rstest]
501 fn test_update_isolated_margin_serialization() {
502 let action = ExchangeAction::update_isolated_margin(2, false, 1000);
503 let json = serde_json::to_string(&action).unwrap();
504
505 assert!(json.contains(r#""type":"updateIsolatedMargin""#));
506 assert!(json.contains(r#""asset":2"#));
507 assert!(json.contains(r#""isBuy":false"#));
508 assert!(json.contains(r#""ntli":1000"#));
509 }
510
511 #[rstest]
512 fn test_cancel_by_cloid_serialization() {
513 use crate::http::models::{Cloid, HyperliquidExecCancelByCloidRequest};
514
515 let cancel_request = HyperliquidExecCancelByCloidRequest {
516 asset: 0,
517 cloid: Cloid::from_hex("0x00000000000000000000000000000000").unwrap(),
518 };
519 let action = ExchangeAction::cancel_by_cloid(vec![cancel_request]);
520 let json = serde_json::to_string(&action).unwrap();
521
522 assert!(json.contains(r#""type":"cancelByCloid""#));
523 assert!(json.contains(r#""cancels""#));
524 }
525
526 #[rstest]
527 fn test_modify_serialization() {
528 use rust_decimal::Decimal;
529
530 use crate::http::models::HyperliquidExecModifyOrderRequest;
531
532 let modify_request = HyperliquidExecModifyOrderRequest {
533 asset: 0,
534 oid: 12345,
535 price: Some(Decimal::new(51000, 0)),
536 size: Some(Decimal::new(2, 0)),
537 reduce_only: None,
538 kind: None,
539 };
540 let action = ExchangeAction::modify(12345, modify_request);
541 let json = serde_json::to_string(&action).unwrap();
542
543 assert!(json.contains(r#""type":"modify""#));
544 assert!(json.contains(r#""oid":12345"#));
545 }
546}