1use std::fmt::Display;
17
18use alloy_primitives::{Address, keccak256};
19use nautilus_model::identifiers::ClientOrderId;
20use rust_decimal::Decimal;
21use serde::{Deserialize, Deserializer, Serialize, Serializer};
22use ustr::Ustr;
23
24use crate::common::enums::{
25 HyperliquidFillDirection, HyperliquidLeverageType,
26 HyperliquidOrderStatus as HyperliquidOrderStatusEnum, HyperliquidPositionType, HyperliquidSide,
27};
28
29pub type HyperliquidCandleSnapshot = Vec<HyperliquidCandle>;
31
32#[derive(Clone, PartialEq, Eq, Hash, Debug)]
34pub struct Cloid(pub [u8; 16]);
35
36impl Cloid {
37 pub fn from_hex<S: AsRef<str>>(s: S) -> Result<Self, String> {
43 let hex_str = s.as_ref();
44 let without_prefix = hex_str
45 .strip_prefix("0x")
46 .ok_or("CLOID must start with '0x'")?;
47
48 if without_prefix.len() != 32 {
49 return Err("CLOID must be exactly 32 hex characters (128 bits)".to_string());
50 }
51
52 let mut bytes = [0u8; 16];
53 for i in 0..16 {
54 let byte_str = &without_prefix[i * 2..i * 2 + 2];
55 bytes[i] = u8::from_str_radix(byte_str, 16)
56 .map_err(|_| "Invalid hex character in CLOID".to_string())?;
57 }
58
59 Ok(Self(bytes))
60 }
61
62 #[must_use]
67 pub fn from_client_order_id(client_order_id: ClientOrderId) -> Self {
68 let hash = keccak256(client_order_id.as_str().as_bytes());
69 let mut bytes = [0u8; 16];
70 bytes.copy_from_slice(&hash[..16]);
71 Self(bytes)
72 }
73
74 pub fn to_hex(&self) -> String {
76 let mut result = String::with_capacity(34);
77 result.push_str("0x");
78 for byte in &self.0 {
79 result.push_str(&format!("{byte:02x}"));
80 }
81 result
82 }
83}
84
85impl Display for Cloid {
86 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87 write!(f, "{}", self.to_hex())
88 }
89}
90
91impl Serialize for Cloid {
92 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
93 where
94 S: Serializer,
95 {
96 serializer.serialize_str(&self.to_hex())
97 }
98}
99
100impl<'de> Deserialize<'de> for Cloid {
101 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
102 where
103 D: Deserializer<'de>,
104 {
105 let s = String::deserialize(deserializer)?;
106 Self::from_hex(&s).map_err(serde::de::Error::custom)
107 }
108}
109
110pub type AssetId = u32;
115
116pub type OrderId = u64;
118
119#[derive(Debug, Clone, Serialize, Deserialize)]
121#[serde(rename_all = "camelCase")]
122pub struct HyperliquidAssetInfo {
123 pub name: Ustr,
125 pub sz_decimals: u32,
127 #[serde(default)]
129 pub max_leverage: Option<u32>,
130 #[serde(default)]
132 pub only_isolated: Option<bool>,
133 #[serde(default)]
135 pub is_delisted: Option<bool>,
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
140#[serde(rename_all = "camelCase")]
141pub struct PerpMeta {
142 pub universe: Vec<PerpAsset>,
144 #[serde(default)]
146 pub margin_tables: Vec<(u32, MarginTable)>,
147}
148
149#[derive(Debug, Clone, Default, Serialize, Deserialize)]
151#[serde(rename_all = "camelCase")]
152pub struct PerpAsset {
153 pub name: String,
155 pub sz_decimals: u32,
157 #[serde(default)]
159 pub max_leverage: Option<u32>,
160 #[serde(default)]
162 pub only_isolated: Option<bool>,
163 #[serde(default)]
165 pub is_delisted: Option<bool>,
166 #[serde(default)]
168 pub growth_mode: Option<String>,
169 #[serde(default)]
171 pub margin_mode: Option<String>,
172}
173
174#[derive(Debug, Clone, Serialize, Deserialize)]
176#[serde(rename_all = "camelCase")]
177pub struct MarginTable {
178 pub description: String,
180 #[serde(default)]
182 pub margin_tiers: Vec<MarginTier>,
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize)]
187#[serde(rename_all = "camelCase")]
188pub struct MarginTier {
189 pub lower_bound: String,
191 pub max_leverage: u32,
193}
194
195#[derive(Debug, Clone, Serialize, Deserialize)]
197#[serde(rename_all = "camelCase")]
198pub struct SpotMeta {
199 pub tokens: Vec<SpotToken>,
201 pub universe: Vec<SpotPair>,
203}
204
205#[derive(Debug, Clone, Serialize, Deserialize)]
207#[serde(rename_all = "snake_case")]
208pub struct EvmContract {
209 pub address: Address,
211 pub evm_extra_wei_decimals: i32,
213}
214
215#[derive(Debug, Clone, Serialize, Deserialize)]
217#[serde(rename_all = "camelCase")]
218pub struct SpotToken {
219 pub name: String,
221 pub sz_decimals: u32,
223 pub wei_decimals: u32,
225 pub index: u32,
227 pub token_id: String,
229 pub is_canonical: bool,
231 #[serde(default)]
233 pub evm_contract: Option<EvmContract>,
234 #[serde(default)]
236 pub full_name: Option<String>,
237 #[serde(default)]
239 pub deployer_trading_fee_share: Option<String>,
240}
241
242#[derive(Debug, Clone, Serialize, Deserialize)]
244#[serde(rename_all = "camelCase")]
245pub struct SpotPair {
246 pub name: String,
248 pub tokens: [u32; 2],
250 pub index: u32,
252 pub is_canonical: bool,
254}
255
256#[derive(Debug, Clone, Serialize, Deserialize)]
259#[serde(untagged)]
260pub enum PerpMetaAndCtxs {
261 Payload(Box<(PerpMeta, Vec<PerpAssetCtx>)>),
263}
264
265#[derive(Debug, Clone, Serialize, Deserialize)]
267#[serde(rename_all = "camelCase")]
268pub struct PerpAssetCtx {
269 #[serde(default)]
271 pub mark_px: Option<String>,
272 #[serde(default)]
274 pub mid_px: Option<String>,
275 #[serde(default)]
277 pub funding: Option<String>,
278 #[serde(default)]
280 pub open_interest: Option<String>,
281}
282
283#[derive(Debug, Clone, Serialize, Deserialize)]
286#[serde(untagged)]
287pub enum SpotMetaAndCtxs {
288 Payload(Box<(SpotMeta, Vec<SpotAssetCtx>)>),
290}
291
292#[derive(Debug, Clone, Serialize, Deserialize)]
294#[serde(rename_all = "camelCase")]
295pub struct SpotAssetCtx {
296 #[serde(default)]
298 pub mark_px: Option<String>,
299 #[serde(default)]
301 pub mid_px: Option<String>,
302 #[serde(default)]
304 pub day_volume: Option<String>,
305}
306
307#[derive(Debug, Clone, Serialize, Deserialize)]
309pub struct HyperliquidL2Book {
310 pub coin: Ustr,
312 pub levels: Vec<Vec<HyperliquidLevel>>,
314 pub time: u64,
316}
317
318#[derive(Debug, Clone, Serialize, Deserialize)]
320pub struct HyperliquidLevel {
321 pub px: String,
323 pub sz: String,
325}
326
327pub type HyperliquidFills = Vec<HyperliquidFill>;
331
332#[derive(Debug, Clone, Serialize, Deserialize)]
334pub struct HyperliquidMeta {
335 #[serde(default)]
336 pub universe: Vec<HyperliquidAssetInfo>,
337}
338
339#[derive(Debug, Clone, Serialize, Deserialize)]
341#[serde(rename_all = "camelCase")]
342pub struct HyperliquidCandle {
343 #[serde(rename = "t")]
345 pub timestamp: u64,
346 #[serde(rename = "T")]
348 pub end_timestamp: u64,
349 #[serde(rename = "o")]
351 pub open: String,
352 #[serde(rename = "h")]
354 pub high: String,
355 #[serde(rename = "l")]
357 pub low: String,
358 #[serde(rename = "c")]
360 pub close: String,
361 #[serde(rename = "v")]
363 pub volume: String,
364 #[serde(rename = "n", default)]
366 pub num_trades: Option<u64>,
367}
368
369#[derive(Debug, Clone, Serialize, Deserialize)]
371pub struct HyperliquidFill {
372 pub coin: Ustr,
374 pub px: String,
376 pub sz: String,
378 pub side: HyperliquidSide,
380 pub time: u64,
382 #[serde(rename = "startPosition")]
384 pub start_position: String,
385 pub dir: HyperliquidFillDirection,
387 #[serde(rename = "closedPnl")]
389 pub closed_pnl: String,
390 pub hash: String,
392 pub oid: u64,
394 pub crossed: bool,
396 pub fee: String,
398 #[serde(rename = "feeToken")]
400 pub fee_token: Ustr,
401}
402
403#[derive(Debug, Clone, Serialize, Deserialize)]
405pub struct HyperliquidOrderStatus {
406 #[serde(default)]
407 pub statuses: Vec<HyperliquidOrderStatusEntry>,
408}
409
410#[derive(Debug, Clone, Serialize, Deserialize)]
412pub struct HyperliquidOrderStatusEntry {
413 pub order: HyperliquidOrderInfo,
415 pub status: HyperliquidOrderStatusEnum,
417 #[serde(rename = "statusTimestamp")]
419 pub status_timestamp: u64,
420}
421
422#[derive(Debug, Clone, Serialize, Deserialize)]
424pub struct HyperliquidOrderInfo {
425 pub coin: Ustr,
427 pub side: HyperliquidSide,
429 #[serde(rename = "limitPx")]
431 pub limit_px: String,
432 pub sz: String,
434 pub oid: u64,
436 pub timestamp: u64,
438 #[serde(rename = "origSz")]
440 pub orig_sz: String,
441}
442
443#[derive(Debug, Clone, Serialize)]
445pub struct HyperliquidSignature {
446 pub r: String,
448 pub s: String,
450 pub v: u64,
452}
453
454impl HyperliquidSignature {
455 pub fn from_hex(sig_hex: &str) -> Result<Self, String> {
457 let sig_hex = sig_hex.strip_prefix("0x").unwrap_or(sig_hex);
458
459 if sig_hex.len() != 130 {
460 return Err(format!(
461 "Invalid signature length: expected 130 hex chars, was {}",
462 sig_hex.len()
463 ));
464 }
465
466 let r = format!("0x{}", &sig_hex[0..64]);
467 let s = format!("0x{}", &sig_hex[64..128]);
468 let v = u64::from_str_radix(&sig_hex[128..130], 16)
469 .map_err(|e| format!("Failed to parse v component: {e}"))?;
470
471 Ok(Self { r, s, v })
472 }
473}
474
475#[derive(Debug, Clone, Serialize)]
477pub struct HyperliquidExchangeRequest<T> {
478 #[serde(rename = "action")]
480 pub action: T,
481 #[serde(rename = "nonce")]
483 pub nonce: u64,
484 #[serde(rename = "signature")]
486 pub signature: HyperliquidSignature,
487 #[serde(rename = "vaultAddress", skip_serializing_if = "Option::is_none")]
489 pub vault_address: Option<String>,
490 #[serde(rename = "expiresAfter", skip_serializing_if = "Option::is_none")]
492 pub expires_after: Option<u64>,
493}
494
495impl<T> HyperliquidExchangeRequest<T>
496where
497 T: Serialize,
498{
499 pub fn new(action: T, nonce: u64, signature: &str) -> Result<Self, String> {
501 Ok(Self {
502 action,
503 nonce,
504 signature: HyperliquidSignature::from_hex(signature)?,
505 vault_address: None,
506 expires_after: None,
507 })
508 }
509
510 pub fn with_vault(
512 action: T,
513 nonce: u64,
514 signature: &str,
515 vault_address: String,
516 ) -> Result<Self, String> {
517 Ok(Self {
518 action,
519 nonce,
520 signature: HyperliquidSignature::from_hex(signature)?,
521 vault_address: Some(vault_address),
522 expires_after: None,
523 })
524 }
525
526 pub fn to_sign_value(&self) -> serde_json::Result<serde_json::Value> {
528 serde_json::to_value(self)
529 }
530}
531
532#[derive(Debug, Clone, Serialize, Deserialize)]
534#[serde(untagged)]
535pub enum HyperliquidExchangeResponse {
536 Status {
538 status: String,
540 response: serde_json::Value,
542 },
543 Error {
545 error: String,
547 },
548}
549
550impl HyperliquidExchangeResponse {
551 pub fn is_ok(&self) -> bool {
552 matches!(self, Self::Status { status, .. } if status == RESPONSE_STATUS_OK)
553 }
554}
555
556pub const RESPONSE_STATUS_OK: &str = "ok";
558
559#[cfg(test)]
560mod tests {
561 use rstest::rstest;
562
563 use super::*;
564
565 #[rstest]
566 fn test_meta_deserialization() {
567 let json = r#"{"universe": [{"name": "BTC", "szDecimals": 5}]}"#;
568
569 let meta: HyperliquidMeta = serde_json::from_str(json).unwrap();
570
571 assert_eq!(meta.universe.len(), 1);
572 assert_eq!(meta.universe[0].name, "BTC");
573 assert_eq!(meta.universe[0].sz_decimals, 5);
574 }
575
576 #[rstest]
577 fn test_perp_asset_hip3_fields() {
578 let json = r#"{
579 "name": "xyz:TSLA",
580 "szDecimals": 3,
581 "maxLeverage": 10,
582 "onlyIsolated": true,
583 "growthMode": "enabled",
584 "marginMode": "strictIsolated"
585 }"#;
586
587 let asset: PerpAsset = serde_json::from_str(json).unwrap();
588
589 assert_eq!(asset.name, "xyz:TSLA");
590 assert_eq!(asset.sz_decimals, 3);
591 assert_eq!(asset.max_leverage, Some(10));
592 assert_eq!(asset.only_isolated, Some(true));
593 assert_eq!(asset.growth_mode.as_deref(), Some("enabled"));
594 assert_eq!(asset.margin_mode.as_deref(), Some("strictIsolated"));
595 }
596
597 #[rstest]
598 fn test_perp_asset_hip3_fields_absent() {
599 let json = r#"{"name": "BTC", "szDecimals": 5}"#;
600
601 let asset: PerpAsset = serde_json::from_str(json).unwrap();
602
603 assert_eq!(asset.growth_mode, None);
604 assert_eq!(asset.margin_mode, None);
605 }
606
607 #[rstest]
608 fn test_l2_book_deserialization() {
609 let json = r#"{"coin": "BTC", "levels": [[{"px": "50000", "sz": "1.5"}], [{"px": "50100", "sz": "2.0"}]], "time": 1234567890}"#;
610
611 let book: HyperliquidL2Book = serde_json::from_str(json).unwrap();
612
613 assert_eq!(book.coin, "BTC");
614 assert_eq!(book.levels.len(), 2);
615 assert_eq!(book.time, 1234567890);
616 }
617
618 #[rstest]
619 fn test_exchange_response_deserialization() {
620 let json = r#"{"status": "ok", "response": {"type": "order"}}"#;
621
622 let response: HyperliquidExchangeResponse = serde_json::from_str(json).unwrap();
623 assert!(response.is_ok());
624 }
625
626 #[rstest]
627 fn test_msgpack_serialization_matches_python() {
628 let action = HyperliquidExecAction::Order {
633 orders: vec![],
634 grouping: HyperliquidExecGrouping::Na,
635 builder: None,
636 };
637
638 let json = serde_json::to_string(&action).unwrap();
640 assert!(
641 json.contains(r#""type":"order""#),
642 "JSON should have type tag: {json}"
643 );
644
645 let msgpack_bytes = rmp_serde::to_vec_named(&action).unwrap();
647
648 let decoded: serde_json::Value = rmp_serde::from_slice(&msgpack_bytes).unwrap();
650
651 assert!(
653 decoded.get("type").is_some(),
654 "MsgPack should have type tag. Decoded: {decoded:?}"
655 );
656 assert_eq!(
657 decoded.get("type").unwrap().as_str().unwrap(),
658 "order",
659 "Type should be 'order'"
660 );
661 assert!(decoded.get("orders").is_some(), "Should have orders field");
662 assert!(
663 decoded.get("grouping").is_some(),
664 "Should have grouping field"
665 );
666 }
667}
668
669#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
673pub enum HyperliquidExecTif {
674 #[serde(rename = "Alo")]
676 Alo,
677 #[serde(rename = "Ioc")]
679 Ioc,
680 #[serde(rename = "Gtc")]
682 Gtc,
683}
684
685#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
687pub enum HyperliquidExecTpSl {
688 #[serde(rename = "tp")]
690 Tp,
691 #[serde(rename = "sl")]
693 Sl,
694}
695
696#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
698pub enum HyperliquidExecGrouping {
699 #[serde(rename = "na")]
701 #[default]
702 Na,
703 #[serde(rename = "normalTpsl")]
705 NormalTpsl,
706 #[serde(rename = "positionTpsl")]
708 PositionTpsl,
709}
710
711#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
713#[serde(untagged)]
714pub enum HyperliquidExecOrderKind {
715 Limit {
717 limit: HyperliquidExecLimitParams,
719 },
720 Trigger {
722 trigger: HyperliquidExecTriggerParams,
724 },
725}
726
727#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
729pub struct HyperliquidExecLimitParams {
730 pub tif: HyperliquidExecTif,
732}
733
734#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
736#[serde(rename_all = "camelCase")]
737pub struct HyperliquidExecTriggerParams {
738 pub is_market: bool,
740 #[serde(
742 serialize_with = "crate::common::parse::serialize_decimal_as_str",
743 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
744 )]
745 pub trigger_px: Decimal,
746 pub tpsl: HyperliquidExecTpSl,
748}
749
750#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
755pub struct HyperliquidExecBuilderFee {
756 #[serde(rename = "b")]
758 pub address: String,
759 #[serde(rename = "f")]
761 pub fee_tenths_bp: u32,
762}
763
764#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
769pub struct HyperliquidExecPlaceOrderRequest {
770 #[serde(rename = "a")]
772 pub asset: AssetId,
773 #[serde(rename = "b")]
775 pub is_buy: bool,
776 #[serde(
778 rename = "p",
779 serialize_with = "crate::common::parse::serialize_decimal_as_str",
780 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
781 )]
782 pub price: Decimal,
783 #[serde(
785 rename = "s",
786 serialize_with = "crate::common::parse::serialize_decimal_as_str",
787 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
788 )]
789 pub size: Decimal,
790 #[serde(rename = "r")]
792 pub reduce_only: bool,
793 #[serde(rename = "t")]
795 pub kind: HyperliquidExecOrderKind,
796 #[serde(rename = "c", skip_serializing_if = "Option::is_none")]
798 pub cloid: Option<Cloid>,
799}
800
801#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
803pub struct HyperliquidExecCancelOrderRequest {
804 #[serde(rename = "a")]
806 pub asset: AssetId,
807 #[serde(rename = "o")]
809 pub oid: OrderId,
810}
811
812#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
817pub struct HyperliquidExecCancelByCloidRequest {
818 pub asset: AssetId,
820 pub cloid: Cloid,
822}
823
824#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
829pub struct HyperliquidExecModifyOrderRequest {
830 pub oid: OrderId,
832 pub order: HyperliquidExecPlaceOrderRequest,
834}
835
836#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
838pub struct HyperliquidExecTwapRequest {
839 #[serde(rename = "a")]
841 pub asset: AssetId,
842 #[serde(rename = "b")]
844 pub is_buy: bool,
845 #[serde(
847 rename = "s",
848 serialize_with = "crate::common::parse::serialize_decimal_as_str",
849 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
850 )]
851 pub size: Decimal,
852 #[serde(rename = "m")]
854 pub duration_ms: u64,
855}
856
857#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
863#[serde(tag = "type")]
864pub enum HyperliquidExecAction {
865 #[serde(rename = "order")]
867 Order {
868 orders: Vec<HyperliquidExecPlaceOrderRequest>,
870 #[serde(default)]
872 grouping: HyperliquidExecGrouping,
873 #[serde(skip_serializing_if = "Option::is_none")]
875 builder: Option<HyperliquidExecBuilderFee>,
876 },
877
878 #[serde(rename = "cancel")]
880 Cancel {
881 cancels: Vec<HyperliquidExecCancelOrderRequest>,
883 },
884
885 #[serde(rename = "cancelByCloid")]
887 CancelByCloid {
888 cancels: Vec<HyperliquidExecCancelByCloidRequest>,
890 },
891
892 #[serde(rename = "modify")]
894 Modify {
895 #[serde(flatten)]
897 modify: HyperliquidExecModifyOrderRequest,
898 },
899
900 #[serde(rename = "batchModify")]
902 BatchModify {
903 modifies: Vec<HyperliquidExecModifyOrderRequest>,
905 },
906
907 #[serde(rename = "scheduleCancel")]
909 ScheduleCancel {
910 #[serde(skip_serializing_if = "Option::is_none")]
913 time: Option<u64>,
914 },
915
916 #[serde(rename = "updateLeverage")]
918 UpdateLeverage {
919 #[serde(rename = "a")]
921 asset: AssetId,
922 #[serde(rename = "isCross")]
924 is_cross: bool,
925 #[serde(rename = "leverage")]
927 leverage: u32,
928 },
929
930 #[serde(rename = "updateIsolatedMargin")]
932 UpdateIsolatedMargin {
933 #[serde(rename = "a")]
935 asset: AssetId,
936 #[serde(
938 rename = "delta",
939 serialize_with = "crate::common::parse::serialize_decimal_as_str",
940 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
941 )]
942 delta: Decimal,
943 },
944
945 #[serde(rename = "usdClassTransfer")]
947 UsdClassTransfer {
948 from: String,
950 to: String,
952 #[serde(
954 serialize_with = "crate::common::parse::serialize_decimal_as_str",
955 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
956 )]
957 amount: Decimal,
958 },
959
960 #[serde(rename = "twapPlace")]
962 TwapPlace {
963 #[serde(flatten)]
965 twap: HyperliquidExecTwapRequest,
966 },
967
968 #[serde(rename = "twapCancel")]
970 TwapCancel {
971 #[serde(rename = "a")]
973 asset: AssetId,
974 #[serde(rename = "t")]
976 twap_id: u64,
977 },
978
979 #[serde(rename = "noop")]
981 Noop,
982}
983
984#[derive(Debug, Clone, Serialize)]
989#[serde(rename_all = "camelCase")]
990pub struct HyperliquidExecRequest {
991 pub action: HyperliquidExecAction,
993 pub nonce: u64,
995 pub signature: String,
997 #[serde(skip_serializing_if = "Option::is_none")]
999 pub vault_address: Option<String>,
1000 #[serde(skip_serializing_if = "Option::is_none")]
1003 pub expires_after: Option<u64>,
1004}
1005
1006#[derive(Debug, Clone, Serialize, Deserialize)]
1008pub struct HyperliquidExecResponse {
1009 pub status: String,
1011 pub response: HyperliquidExecResponseData,
1013}
1014
1015#[derive(Debug, Clone, Serialize, Deserialize)]
1017#[serde(tag = "type")]
1018pub enum HyperliquidExecResponseData {
1019 #[serde(rename = "order")]
1021 Order {
1022 data: HyperliquidExecOrderResponseData,
1024 },
1025 #[serde(rename = "cancel")]
1027 Cancel {
1028 data: HyperliquidExecCancelResponseData,
1030 },
1031 #[serde(rename = "modify")]
1033 Modify {
1034 data: HyperliquidExecModifyResponseData,
1036 },
1037 #[serde(rename = "default")]
1039 Default,
1040 #[serde(other)]
1042 Unknown,
1043}
1044
1045#[derive(Debug, Clone, Serialize, Deserialize)]
1047pub struct HyperliquidExecOrderResponseData {
1048 pub statuses: Vec<HyperliquidExecOrderStatus>,
1050}
1051
1052#[derive(Debug, Clone, Serialize, Deserialize)]
1054pub struct HyperliquidExecCancelResponseData {
1055 pub statuses: Vec<HyperliquidExecCancelStatus>,
1057}
1058
1059#[derive(Debug, Clone, Serialize, Deserialize)]
1061pub struct HyperliquidExecModifyResponseData {
1062 pub statuses: Vec<HyperliquidExecModifyStatus>,
1064}
1065
1066#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1068#[serde(untagged)]
1069pub enum HyperliquidExecOrderStatus {
1070 Resting {
1072 resting: HyperliquidExecRestingInfo,
1074 },
1075 Filled {
1077 filled: HyperliquidExecFilledInfo,
1079 },
1080 Error {
1082 error: String,
1084 },
1085}
1086
1087#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1089pub struct HyperliquidExecRestingInfo {
1090 pub oid: OrderId,
1092}
1093
1094#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1096pub struct HyperliquidExecFilledInfo {
1097 #[serde(
1099 rename = "totalSz",
1100 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1101 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1102 )]
1103 pub total_sz: Decimal,
1104 #[serde(
1106 rename = "avgPx",
1107 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1108 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1109 )]
1110 pub avg_px: Decimal,
1111 pub oid: OrderId,
1113}
1114
1115#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1117#[serde(untagged)]
1118pub enum HyperliquidExecCancelStatus {
1119 Success(String), Error {
1123 error: String,
1125 },
1126}
1127
1128#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1130#[serde(untagged)]
1131pub enum HyperliquidExecModifyStatus {
1132 Success(String), Error {
1136 error: String,
1138 },
1139}
1140
1141#[derive(Debug, Clone, Serialize, Deserialize)]
1144#[serde(rename_all = "camelCase")]
1145pub struct ClearinghouseState {
1146 #[serde(default)]
1148 pub asset_positions: Vec<AssetPosition>,
1149 #[serde(default)]
1151 pub cross_margin_summary: Option<CrossMarginSummary>,
1152 #[serde(
1154 default,
1155 serialize_with = "crate::common::parse::serialize_optional_decimal_as_str",
1156 deserialize_with = "crate::common::parse::deserialize_optional_decimal_from_str"
1157 )]
1158 pub withdrawable: Option<Decimal>,
1159 #[serde(default)]
1161 pub time: Option<u64>,
1162}
1163
1164#[derive(Debug, Clone, Serialize, Deserialize)]
1166#[serde(rename_all = "camelCase")]
1167pub struct AssetPosition {
1168 pub position: PositionData,
1170 #[serde(rename = "type")]
1172 pub position_type: HyperliquidPositionType,
1173}
1174
1175#[derive(Debug, Clone, Serialize, Deserialize)]
1177#[serde(rename_all = "camelCase")]
1178pub struct LeverageInfo {
1179 #[serde(rename = "type")]
1180 pub leverage_type: HyperliquidLeverageType,
1181 pub value: u32,
1183}
1184
1185#[derive(Debug, Clone, Serialize, Deserialize)]
1187#[serde(rename_all = "camelCase")]
1188pub struct CumFundingInfo {
1189 #[serde(
1191 rename = "allTime",
1192 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1193 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1194 )]
1195 pub all_time: Decimal,
1196 #[serde(
1198 rename = "sinceOpen",
1199 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1200 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1201 )]
1202 pub since_open: Decimal,
1203 #[serde(
1205 rename = "sinceChange",
1206 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1207 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1208 )]
1209 pub since_change: Decimal,
1210}
1211
1212#[derive(Debug, Clone, Serialize, Deserialize)]
1214#[serde(rename_all = "camelCase")]
1215pub struct PositionData {
1216 pub coin: Ustr,
1218 #[serde(rename = "cumFunding")]
1220 pub cum_funding: CumFundingInfo,
1221 #[serde(
1223 rename = "entryPx",
1224 serialize_with = "crate::common::parse::serialize_optional_decimal_as_str",
1225 deserialize_with = "crate::common::parse::deserialize_optional_decimal_from_str",
1226 default
1227 )]
1228 pub entry_px: Option<Decimal>,
1229 pub leverage: LeverageInfo,
1231 #[serde(
1233 rename = "liquidationPx",
1234 serialize_with = "crate::common::parse::serialize_optional_decimal_as_str",
1235 deserialize_with = "crate::common::parse::deserialize_optional_decimal_from_str",
1236 default
1237 )]
1238 pub liquidation_px: Option<Decimal>,
1239 #[serde(
1241 rename = "marginUsed",
1242 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1243 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1244 )]
1245 pub margin_used: Decimal,
1246 #[serde(rename = "maxLeverage", default)]
1248 pub max_leverage: Option<u32>,
1249 #[serde(
1251 rename = "positionValue",
1252 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1253 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1254 )]
1255 pub position_value: Decimal,
1256 #[serde(
1258 rename = "returnOnEquity",
1259 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1260 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1261 )]
1262 pub return_on_equity: Decimal,
1263 #[serde(
1265 rename = "szi",
1266 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1267 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1268 )]
1269 pub szi: Decimal,
1270 #[serde(
1272 rename = "unrealizedPnl",
1273 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1274 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1275 )]
1276 pub unrealized_pnl: Decimal,
1277}
1278
1279#[derive(Debug, Clone, Serialize, Deserialize)]
1281#[serde(rename_all = "camelCase")]
1282pub struct CrossMarginSummary {
1283 #[serde(
1285 rename = "accountValue",
1286 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1287 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1288 )]
1289 pub account_value: Decimal,
1290 #[serde(
1292 rename = "totalNtlPos",
1293 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1294 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1295 )]
1296 pub total_ntl_pos: Decimal,
1297 #[serde(
1299 rename = "totalRawUsd",
1300 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1301 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1302 )]
1303 pub total_raw_usd: Decimal,
1304 #[serde(
1306 rename = "totalMarginUsed",
1307 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1308 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1309 )]
1310 pub total_margin_used: Decimal,
1311 #[serde(
1313 rename = "withdrawable",
1314 default,
1315 serialize_with = "crate::common::parse::serialize_optional_decimal_as_str",
1316 deserialize_with = "crate::common::parse::deserialize_optional_decimal_from_str"
1317 )]
1318 pub withdrawable: Option<Decimal>,
1319}