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