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
380 use super::*;
381
382 #[rstest]
383 fn test_info_request_meta() {
384 let req = InfoRequest::meta();
385
386 assert_eq!(req.request_type, HyperliquidInfoRequestType::Meta);
387 assert!(matches!(req.params, InfoRequestParams::None));
388 }
389
390 #[rstest]
391 fn test_info_request_l2_book() {
392 let req = InfoRequest::l2_book("BTC");
393
394 assert_eq!(req.request_type, HyperliquidInfoRequestType::L2Book);
395 let json = serde_json::to_string(&req).unwrap();
396 assert!(json.contains("\"coin\":\"BTC\""));
397 }
398
399 #[rstest]
400 fn test_exchange_action_order() {
401 use rust_decimal::Decimal;
402
403 use crate::http::models::{
404 HyperliquidExecLimitParams, HyperliquidExecOrderKind, HyperliquidExecPlaceOrderRequest,
405 HyperliquidExecTif,
406 };
407
408 let order = HyperliquidExecPlaceOrderRequest {
409 asset: 0,
410 is_buy: true,
411 price: Decimal::new(50000, 0),
412 size: Decimal::new(1, 0),
413 reduce_only: false,
414 kind: HyperliquidExecOrderKind::Limit {
415 limit: HyperliquidExecLimitParams {
416 tif: HyperliquidExecTif::Gtc,
417 },
418 },
419 cloid: None,
420 };
421
422 let action = ExchangeAction::order(vec![order], None);
423
424 assert_eq!(action.action_type, ExchangeActionType::Order);
425 let json = serde_json::to_string(&action).unwrap();
426 assert!(json.contains("\"orders\""));
427 }
428
429 #[rstest]
430 fn test_exchange_action_cancel() {
431 use crate::http::models::{Cloid, HyperliquidExecCancelByCloidRequest};
432
433 let cancel = HyperliquidExecCancelByCloidRequest {
434 asset: 0,
435 cloid: Cloid::from_hex("0x00000000000000000000000000000000").unwrap(),
436 };
437
438 let action = ExchangeAction::cancel(vec![cancel]);
439
440 assert_eq!(action.action_type, ExchangeActionType::Cancel);
441 }
442
443 #[rstest]
444 fn test_exchange_action_serialization() {
445 use rust_decimal::Decimal;
446
447 use crate::http::models::{
448 HyperliquidExecLimitParams, HyperliquidExecOrderKind, HyperliquidExecPlaceOrderRequest,
449 HyperliquidExecTif,
450 };
451
452 let order = HyperliquidExecPlaceOrderRequest {
453 asset: 0,
454 is_buy: true,
455 price: Decimal::new(50000, 0),
456 size: Decimal::new(1, 0),
457 reduce_only: false,
458 kind: HyperliquidExecOrderKind::Limit {
459 limit: HyperliquidExecLimitParams {
460 tif: HyperliquidExecTif::Gtc,
461 },
462 },
463 cloid: None,
464 };
465
466 let action = ExchangeAction::order(vec![order], None);
467
468 let json = serde_json::to_string(&action).unwrap();
469 assert!(json.contains(r#""type":"order""#));
471 assert!(json.contains(r#""orders""#));
472 assert!(json.contains(r#""grouping":"na""#));
473 }
474
475 #[rstest]
476 fn test_exchange_action_type_as_ref() {
477 assert_eq!(ExchangeActionType::Order.as_ref(), "order");
478 assert_eq!(ExchangeActionType::Cancel.as_ref(), "cancel");
479 assert_eq!(ExchangeActionType::CancelByCloid.as_ref(), "cancelByCloid");
480 assert_eq!(ExchangeActionType::Modify.as_ref(), "modify");
481 assert_eq!(
482 ExchangeActionType::UpdateLeverage.as_ref(),
483 "updateLeverage"
484 );
485 assert_eq!(
486 ExchangeActionType::UpdateIsolatedMargin.as_ref(),
487 "updateIsolatedMargin"
488 );
489 }
490
491 #[rstest]
492 fn test_update_leverage_serialization() {
493 let action = ExchangeAction::update_leverage(1, true, 10);
494 let json = serde_json::to_string(&action).unwrap();
495
496 assert!(json.contains(r#""type":"updateLeverage""#));
497 assert!(json.contains(r#""asset":1"#));
498 assert!(json.contains(r#""isCross":true"#));
499 assert!(json.contains(r#""leverage":10"#));
500 }
501
502 #[rstest]
503 fn test_update_isolated_margin_serialization() {
504 let action = ExchangeAction::update_isolated_margin(2, false, 1000);
505 let json = serde_json::to_string(&action).unwrap();
506
507 assert!(json.contains(r#""type":"updateIsolatedMargin""#));
508 assert!(json.contains(r#""asset":2"#));
509 assert!(json.contains(r#""isBuy":false"#));
510 assert!(json.contains(r#""ntli":1000"#));
511 }
512
513 #[rstest]
514 fn test_cancel_by_cloid_serialization() {
515 use crate::http::models::{Cloid, HyperliquidExecCancelByCloidRequest};
516
517 let cancel_request = HyperliquidExecCancelByCloidRequest {
518 asset: 0,
519 cloid: Cloid::from_hex("0x00000000000000000000000000000000").unwrap(),
520 };
521 let action = ExchangeAction::cancel_by_cloid(vec![cancel_request]);
522 let json = serde_json::to_string(&action).unwrap();
523
524 assert!(json.contains(r#""type":"cancelByCloid""#));
525 assert!(json.contains(r#""cancels""#));
526 }
527
528 #[rstest]
529 fn test_modify_serialization() {
530 use rust_decimal::Decimal;
531
532 use crate::http::models::HyperliquidExecModifyOrderRequest;
533
534 let modify_request = HyperliquidExecModifyOrderRequest {
535 asset: 0,
536 oid: 12345,
537 price: Some(Decimal::new(51000, 0)),
538 size: Some(Decimal::new(2, 0)),
539 reduce_only: None,
540 kind: None,
541 };
542 let action = ExchangeAction::modify(12345, modify_request);
543 let json = serde_json::to_string(&action).unwrap();
544
545 assert!(json.contains(r#""type":"modify""#));
546 assert!(json.contains(r#""oid":12345"#));
547 }
548}