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, HyperliquidOrderStatus as HyperliquidOrderStatusEnum,
26 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, 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}
167
168#[derive(Debug, Clone, Serialize, Deserialize)]
170#[serde(rename_all = "camelCase")]
171pub struct MarginTable {
172 pub description: String,
174 #[serde(default)]
176 pub margin_tiers: Vec<MarginTier>,
177}
178
179#[derive(Debug, Clone, Serialize, Deserialize)]
181#[serde(rename_all = "camelCase")]
182pub struct MarginTier {
183 pub lower_bound: String,
185 pub max_leverage: u32,
187}
188
189#[derive(Debug, Clone, Serialize, Deserialize)]
191#[serde(rename_all = "camelCase")]
192pub struct SpotMeta {
193 pub tokens: Vec<SpotToken>,
195 pub universe: Vec<SpotPair>,
197}
198
199#[derive(Debug, Clone, Serialize, Deserialize)]
201#[serde(rename_all = "snake_case")]
202pub struct EvmContract {
203 pub address: Address,
205 pub evm_extra_wei_decimals: i32,
207}
208
209#[derive(Debug, Clone, Serialize, Deserialize)]
211#[serde(rename_all = "camelCase")]
212pub struct SpotToken {
213 pub name: String,
215 pub sz_decimals: u32,
217 pub wei_decimals: u32,
219 pub index: u32,
221 pub token_id: String,
223 pub is_canonical: bool,
225 #[serde(default)]
227 pub evm_contract: Option<EvmContract>,
228 #[serde(default)]
230 pub full_name: Option<String>,
231 #[serde(default)]
233 pub deployer_trading_fee_share: Option<String>,
234}
235
236#[derive(Debug, Clone, Serialize, Deserialize)]
238#[serde(rename_all = "camelCase")]
239pub struct SpotPair {
240 pub name: String,
242 pub tokens: [u32; 2],
244 pub index: u32,
246 pub is_canonical: bool,
248}
249
250#[derive(Debug, Clone, Serialize, Deserialize)]
253#[serde(untagged)]
254pub enum PerpMetaAndCtxs {
255 Payload(Box<(PerpMeta, Vec<PerpAssetCtx>)>),
257}
258
259#[derive(Debug, Clone, Serialize, Deserialize)]
261#[serde(rename_all = "camelCase")]
262pub struct PerpAssetCtx {
263 #[serde(default)]
265 pub mark_px: Option<String>,
266 #[serde(default)]
268 pub mid_px: Option<String>,
269 #[serde(default)]
271 pub funding: Option<String>,
272 #[serde(default)]
274 pub open_interest: Option<String>,
275}
276
277#[derive(Debug, Clone, Serialize, Deserialize)]
280#[serde(untagged)]
281pub enum SpotMetaAndCtxs {
282 Payload(Box<(SpotMeta, Vec<SpotAssetCtx>)>),
284}
285
286#[derive(Debug, Clone, Serialize, Deserialize)]
288#[serde(rename_all = "camelCase")]
289pub struct SpotAssetCtx {
290 #[serde(default)]
292 pub mark_px: Option<String>,
293 #[serde(default)]
295 pub mid_px: Option<String>,
296 #[serde(default)]
298 pub day_volume: Option<String>,
299}
300
301#[derive(Debug, Clone, Serialize, Deserialize)]
303pub struct HyperliquidL2Book {
304 pub coin: Ustr,
306 pub levels: Vec<Vec<HyperliquidLevel>>,
308 pub time: u64,
310}
311
312#[derive(Debug, Clone, Serialize, Deserialize)]
314pub struct HyperliquidLevel {
315 pub px: String,
317 pub sz: String,
319}
320
321pub type HyperliquidFills = Vec<HyperliquidFill>;
325
326#[derive(Debug, Clone, Serialize, Deserialize)]
328pub struct HyperliquidMeta {
329 #[serde(default)]
330 pub universe: Vec<HyperliquidAssetInfo>,
331}
332
333#[derive(Debug, Clone, Serialize, Deserialize)]
335#[serde(rename_all = "camelCase")]
336pub struct HyperliquidCandle {
337 #[serde(rename = "t")]
339 pub timestamp: u64,
340 #[serde(rename = "T")]
342 pub end_timestamp: u64,
343 #[serde(rename = "o")]
345 pub open: String,
346 #[serde(rename = "h")]
348 pub high: String,
349 #[serde(rename = "l")]
351 pub low: String,
352 #[serde(rename = "c")]
354 pub close: String,
355 #[serde(rename = "v")]
357 pub volume: String,
358 #[serde(rename = "n", default)]
360 pub num_trades: Option<u64>,
361}
362
363#[derive(Debug, Clone, Serialize, Deserialize)]
365pub struct HyperliquidFill {
366 pub coin: Ustr,
368 pub px: String,
370 pub sz: String,
372 pub side: HyperliquidSide,
374 pub time: u64,
376 #[serde(rename = "startPosition")]
378 pub start_position: String,
379 pub dir: HyperliquidFillDirection,
381 #[serde(rename = "closedPnl")]
383 pub closed_pnl: String,
384 pub hash: String,
386 pub oid: u64,
388 pub crossed: bool,
390 pub fee: String,
392}
393
394#[derive(Debug, Clone, Serialize, Deserialize)]
396pub struct HyperliquidOrderStatus {
397 #[serde(default)]
398 pub statuses: Vec<HyperliquidOrderStatusEntry>,
399}
400
401#[derive(Debug, Clone, Serialize, Deserialize)]
403pub struct HyperliquidOrderStatusEntry {
404 pub order: HyperliquidOrderInfo,
406 pub status: HyperliquidOrderStatusEnum,
408 #[serde(rename = "statusTimestamp")]
410 pub status_timestamp: u64,
411}
412
413#[derive(Debug, Clone, Serialize, Deserialize)]
415pub struct HyperliquidOrderInfo {
416 pub coin: Ustr,
418 pub side: HyperliquidSide,
420 #[serde(rename = "limitPx")]
422 pub limit_px: String,
423 pub sz: String,
425 pub oid: u64,
427 pub timestamp: u64,
429 #[serde(rename = "origSz")]
431 pub orig_sz: String,
432}
433
434#[derive(Debug, Clone, Serialize)]
436pub struct HyperliquidSignature {
437 pub r: String,
439 pub s: String,
441 pub v: u64,
443}
444
445impl HyperliquidSignature {
446 pub fn from_hex(sig_hex: &str) -> Result<Self, String> {
448 let sig_hex = sig_hex.strip_prefix("0x").unwrap_or(sig_hex);
449
450 if sig_hex.len() != 130 {
451 return Err(format!(
452 "Invalid signature length: expected 130 hex chars, was {}",
453 sig_hex.len()
454 ));
455 }
456
457 let r = format!("0x{}", &sig_hex[0..64]);
458 let s = format!("0x{}", &sig_hex[64..128]);
459 let v = u64::from_str_radix(&sig_hex[128..130], 16)
460 .map_err(|e| format!("Failed to parse v component: {e}"))?;
461
462 Ok(Self { r, s, v })
463 }
464}
465
466#[derive(Debug, Clone, Serialize)]
468pub struct HyperliquidExchangeRequest<T> {
469 #[serde(rename = "action")]
471 pub action: T,
472 #[serde(rename = "nonce")]
474 pub nonce: u64,
475 #[serde(rename = "signature")]
477 pub signature: HyperliquidSignature,
478 #[serde(rename = "vaultAddress", skip_serializing_if = "Option::is_none")]
480 pub vault_address: Option<String>,
481 #[serde(rename = "expiresAfter", skip_serializing_if = "Option::is_none")]
483 pub expires_after: Option<u64>,
484}
485
486impl<T> HyperliquidExchangeRequest<T>
487where
488 T: Serialize,
489{
490 pub fn new(action: T, nonce: u64, signature: String) -> Result<Self, String> {
492 Ok(Self {
493 action,
494 nonce,
495 signature: HyperliquidSignature::from_hex(&signature)?,
496 vault_address: None,
497 expires_after: None,
498 })
499 }
500
501 pub fn with_vault(
503 action: T,
504 nonce: u64,
505 signature: String,
506 vault_address: String,
507 ) -> Result<Self, String> {
508 Ok(Self {
509 action,
510 nonce,
511 signature: HyperliquidSignature::from_hex(&signature)?,
512 vault_address: Some(vault_address),
513 expires_after: None,
514 })
515 }
516
517 pub fn to_sign_value(&self) -> serde_json::Result<serde_json::Value> {
519 serde_json::to_value(self)
520 }
521}
522
523#[derive(Debug, Clone, Serialize, Deserialize)]
525#[serde(untagged)]
526pub enum HyperliquidExchangeResponse {
527 Status {
529 status: String,
531 response: serde_json::Value,
533 },
534 Error {
536 error: String,
538 },
539}
540
541#[cfg(test)]
542mod tests {
543 use rstest::rstest;
544
545 use super::*;
546
547 #[rstest]
548 fn test_meta_deserialization() {
549 let json = r#"{"universe": [{"name": "BTC", "szDecimals": 5}]}"#;
550
551 let meta: HyperliquidMeta = serde_json::from_str(json).unwrap();
552
553 assert_eq!(meta.universe.len(), 1);
554 assert_eq!(meta.universe[0].name, "BTC");
555 assert_eq!(meta.universe[0].sz_decimals, 5);
556 }
557
558 #[rstest]
559 fn test_l2_book_deserialization() {
560 let json = r#"{"coin": "BTC", "levels": [[{"px": "50000", "sz": "1.5"}], [{"px": "50100", "sz": "2.0"}]], "time": 1234567890}"#;
561
562 let book: HyperliquidL2Book = serde_json::from_str(json).unwrap();
563
564 assert_eq!(book.coin, "BTC");
565 assert_eq!(book.levels.len(), 2);
566 assert_eq!(book.time, 1234567890);
567 }
568
569 #[rstest]
570 fn test_exchange_response_deserialization() {
571 let json = r#"{"status": "ok", "response": {"type": "order"}}"#;
572
573 let response: HyperliquidExchangeResponse = serde_json::from_str(json).unwrap();
574
575 match response {
576 HyperliquidExchangeResponse::Status { status, .. } => assert_eq!(status, "ok"),
577 _ => panic!("Expected status response"),
578 }
579 }
580
581 #[rstest]
582 fn test_msgpack_serialization_matches_python() {
583 let action = HyperliquidExecAction::Order {
588 orders: vec![],
589 grouping: HyperliquidExecGrouping::Na,
590 builder: None,
591 };
592
593 let json = serde_json::to_string(&action).unwrap();
595 assert!(
596 json.contains(r#""type":"order""#),
597 "JSON should have type tag: {json}"
598 );
599
600 let msgpack_bytes = rmp_serde::to_vec_named(&action).unwrap();
602
603 let decoded: serde_json::Value = rmp_serde::from_slice(&msgpack_bytes).unwrap();
605
606 assert!(
608 decoded.get("type").is_some(),
609 "MsgPack should have type tag. Decoded: {decoded:?}"
610 );
611 assert_eq!(
612 decoded.get("type").unwrap().as_str().unwrap(),
613 "order",
614 "Type should be 'order'"
615 );
616 assert!(decoded.get("orders").is_some(), "Should have orders field");
617 assert!(
618 decoded.get("grouping").is_some(),
619 "Should have grouping field"
620 );
621 }
622}
623
624#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
628pub enum HyperliquidExecTif {
629 #[serde(rename = "Alo")]
631 Alo,
632 #[serde(rename = "Ioc")]
634 Ioc,
635 #[serde(rename = "Gtc")]
637 Gtc,
638}
639
640#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
642pub enum HyperliquidExecTpSl {
643 #[serde(rename = "tp")]
645 Tp,
646 #[serde(rename = "sl")]
648 Sl,
649}
650
651#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
653pub enum HyperliquidExecGrouping {
654 #[serde(rename = "na")]
656 #[default]
657 Na,
658 #[serde(rename = "normalTpsl")]
660 NormalTpsl,
661 #[serde(rename = "positionTpsl")]
663 PositionTpsl,
664}
665
666#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
668#[serde(untagged)]
669pub enum HyperliquidExecOrderKind {
670 Limit {
672 limit: HyperliquidExecLimitParams,
674 },
675 Trigger {
677 trigger: HyperliquidExecTriggerParams,
679 },
680}
681
682#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
684pub struct HyperliquidExecLimitParams {
685 pub tif: HyperliquidExecTif,
687}
688
689#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
691#[serde(rename_all = "camelCase")]
692pub struct HyperliquidExecTriggerParams {
693 pub is_market: bool,
695 #[serde(
697 serialize_with = "crate::common::parse::serialize_decimal_as_str",
698 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
699 )]
700 pub trigger_px: Decimal,
701 pub tpsl: HyperliquidExecTpSl,
703}
704
705#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
710pub struct HyperliquidExecBuilderFee {
711 #[serde(rename = "b")]
713 pub address: String,
714 #[serde(rename = "f")]
716 pub fee_tenths_bp: u32,
717}
718
719#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
724pub struct HyperliquidExecPlaceOrderRequest {
725 #[serde(rename = "a")]
727 pub asset: AssetId,
728 #[serde(rename = "b")]
730 pub is_buy: bool,
731 #[serde(
733 rename = "p",
734 serialize_with = "crate::common::parse::serialize_decimal_as_str",
735 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
736 )]
737 pub price: Decimal,
738 #[serde(
740 rename = "s",
741 serialize_with = "crate::common::parse::serialize_decimal_as_str",
742 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
743 )]
744 pub size: Decimal,
745 #[serde(rename = "r")]
747 pub reduce_only: bool,
748 #[serde(rename = "t")]
750 pub kind: HyperliquidExecOrderKind,
751 #[serde(rename = "c", skip_serializing_if = "Option::is_none")]
753 pub cloid: Option<Cloid>,
754}
755
756#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
758pub struct HyperliquidExecCancelOrderRequest {
759 #[serde(rename = "a")]
761 pub asset: AssetId,
762 #[serde(rename = "o")]
764 pub oid: OrderId,
765}
766
767#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
772pub struct HyperliquidExecCancelByCloidRequest {
773 pub asset: AssetId,
775 pub cloid: Cloid,
777}
778
779#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
781pub struct HyperliquidExecModifyOrderRequest {
782 #[serde(rename = "a")]
784 pub asset: AssetId,
785 #[serde(rename = "o")]
787 pub oid: OrderId,
788 #[serde(
790 rename = "p",
791 skip_serializing_if = "Option::is_none",
792 serialize_with = "crate::common::parse::serialize_optional_decimal_as_str",
793 deserialize_with = "crate::common::parse::deserialize_optional_decimal_from_str"
794 )]
795 pub price: Option<Decimal>,
796 #[serde(
798 rename = "s",
799 skip_serializing_if = "Option::is_none",
800 serialize_with = "crate::common::parse::serialize_optional_decimal_as_str",
801 deserialize_with = "crate::common::parse::deserialize_optional_decimal_from_str"
802 )]
803 pub size: Option<Decimal>,
804 #[serde(rename = "r", skip_serializing_if = "Option::is_none")]
806 pub reduce_only: Option<bool>,
807 #[serde(rename = "t", skip_serializing_if = "Option::is_none")]
809 pub kind: Option<HyperliquidExecOrderKind>,
810}
811
812#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
814pub struct HyperliquidExecTwapRequest {
815 #[serde(rename = "a")]
817 pub asset: AssetId,
818 #[serde(rename = "b")]
820 pub is_buy: bool,
821 #[serde(
823 rename = "s",
824 serialize_with = "crate::common::parse::serialize_decimal_as_str",
825 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
826 )]
827 pub size: Decimal,
828 #[serde(rename = "m")]
830 pub duration_ms: u64,
831}
832
833#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
839#[serde(tag = "type")]
840pub enum HyperliquidExecAction {
841 #[serde(rename = "order")]
843 Order {
844 orders: Vec<HyperliquidExecPlaceOrderRequest>,
846 #[serde(default)]
848 grouping: HyperliquidExecGrouping,
849 #[serde(skip_serializing_if = "Option::is_none")]
851 builder: Option<HyperliquidExecBuilderFee>,
852 },
853
854 #[serde(rename = "cancel")]
856 Cancel {
857 cancels: Vec<HyperliquidExecCancelOrderRequest>,
859 },
860
861 #[serde(rename = "cancelByCloid")]
863 CancelByCloid {
864 cancels: Vec<HyperliquidExecCancelByCloidRequest>,
866 },
867
868 #[serde(rename = "modify")]
870 Modify {
871 #[serde(flatten)]
873 modify: HyperliquidExecModifyOrderRequest,
874 },
875
876 #[serde(rename = "batchModify")]
878 BatchModify {
879 modifies: Vec<HyperliquidExecModifyOrderRequest>,
881 },
882
883 #[serde(rename = "scheduleCancel")]
885 ScheduleCancel {
886 #[serde(skip_serializing_if = "Option::is_none")]
889 time: Option<u64>,
890 },
891
892 #[serde(rename = "updateLeverage")]
894 UpdateLeverage {
895 #[serde(rename = "a")]
897 asset: AssetId,
898 #[serde(rename = "isCross")]
900 is_cross: bool,
901 #[serde(rename = "leverage")]
903 leverage: u32,
904 },
905
906 #[serde(rename = "updateIsolatedMargin")]
908 UpdateIsolatedMargin {
909 #[serde(rename = "a")]
911 asset: AssetId,
912 #[serde(
914 rename = "delta",
915 serialize_with = "crate::common::parse::serialize_decimal_as_str",
916 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
917 )]
918 delta: Decimal,
919 },
920
921 #[serde(rename = "usdClassTransfer")]
923 UsdClassTransfer {
924 from: String,
926 to: String,
928 #[serde(
930 serialize_with = "crate::common::parse::serialize_decimal_as_str",
931 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
932 )]
933 amount: Decimal,
934 },
935
936 #[serde(rename = "twapPlace")]
938 TwapPlace {
939 #[serde(flatten)]
941 twap: HyperliquidExecTwapRequest,
942 },
943
944 #[serde(rename = "twapCancel")]
946 TwapCancel {
947 #[serde(rename = "a")]
949 asset: AssetId,
950 #[serde(rename = "t")]
952 twap_id: u64,
953 },
954
955 #[serde(rename = "noop")]
957 Noop,
958}
959
960#[derive(Debug, Clone, Serialize)]
965#[serde(rename_all = "camelCase")]
966pub struct HyperliquidExecRequest {
967 pub action: HyperliquidExecAction,
969 pub nonce: u64,
971 pub signature: String,
973 #[serde(skip_serializing_if = "Option::is_none")]
975 pub vault_address: Option<String>,
976 #[serde(skip_serializing_if = "Option::is_none")]
979 pub expires_after: Option<u64>,
980}
981
982#[derive(Debug, Clone, Serialize, Deserialize)]
984pub struct HyperliquidExecResponse {
985 pub status: String,
987 pub response: HyperliquidExecResponseData,
989}
990
991#[derive(Debug, Clone, Serialize, Deserialize)]
993#[serde(tag = "type")]
994pub enum HyperliquidExecResponseData {
995 #[serde(rename = "order")]
997 Order {
998 data: HyperliquidExecOrderResponseData,
1000 },
1001 #[serde(rename = "cancel")]
1003 Cancel {
1004 data: HyperliquidExecCancelResponseData,
1006 },
1007 #[serde(rename = "modify")]
1009 Modify {
1010 data: HyperliquidExecModifyResponseData,
1012 },
1013 #[serde(rename = "default")]
1015 Default,
1016 #[serde(other)]
1018 Unknown,
1019}
1020
1021#[derive(Debug, Clone, Serialize, Deserialize)]
1023pub struct HyperliquidExecOrderResponseData {
1024 pub statuses: Vec<HyperliquidExecOrderStatus>,
1026}
1027
1028#[derive(Debug, Clone, Serialize, Deserialize)]
1030pub struct HyperliquidExecCancelResponseData {
1031 pub statuses: Vec<HyperliquidExecCancelStatus>,
1033}
1034
1035#[derive(Debug, Clone, Serialize, Deserialize)]
1037pub struct HyperliquidExecModifyResponseData {
1038 pub statuses: Vec<HyperliquidExecModifyStatus>,
1040}
1041
1042#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1044#[serde(untagged)]
1045pub enum HyperliquidExecOrderStatus {
1046 Resting {
1048 resting: HyperliquidExecRestingInfo,
1050 },
1051 Filled {
1053 filled: HyperliquidExecFilledInfo,
1055 },
1056 Error {
1058 error: String,
1060 },
1061}
1062
1063#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1065pub struct HyperliquidExecRestingInfo {
1066 pub oid: OrderId,
1068}
1069
1070#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1072pub struct HyperliquidExecFilledInfo {
1073 #[serde(
1075 rename = "totalSz",
1076 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1077 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1078 )]
1079 pub total_sz: Decimal,
1080 #[serde(
1082 rename = "avgPx",
1083 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1084 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1085 )]
1086 pub avg_px: Decimal,
1087 pub oid: OrderId,
1089}
1090
1091#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1093#[serde(untagged)]
1094pub enum HyperliquidExecCancelStatus {
1095 Success(String), Error {
1099 error: String,
1101 },
1102}
1103
1104#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1106#[serde(untagged)]
1107pub enum HyperliquidExecModifyStatus {
1108 Success(String), Error {
1112 error: String,
1114 },
1115}
1116
1117#[derive(Debug, Clone, Serialize, Deserialize)]
1120#[serde(rename_all = "camelCase")]
1121pub struct ClearinghouseState {
1122 #[serde(default)]
1124 pub asset_positions: Vec<AssetPosition>,
1125 #[serde(default)]
1127 pub cross_margin_summary: Option<CrossMarginSummary>,
1128 #[serde(
1130 default,
1131 serialize_with = "crate::common::parse::serialize_optional_decimal_as_str",
1132 deserialize_with = "crate::common::parse::deserialize_optional_decimal_from_str"
1133 )]
1134 pub withdrawable: Option<Decimal>,
1135 #[serde(default)]
1137 pub time: Option<u64>,
1138}
1139
1140#[derive(Debug, Clone, Serialize, Deserialize)]
1142#[serde(rename_all = "camelCase")]
1143pub struct AssetPosition {
1144 pub position: PositionData,
1146 #[serde(rename = "type")]
1148 pub position_type: HyperliquidPositionType,
1149}
1150
1151#[derive(Debug, Clone, Serialize, Deserialize)]
1153#[serde(rename_all = "camelCase")]
1154pub struct LeverageInfo {
1155 #[serde(rename = "type")]
1157 pub leverage_type: String,
1158 pub value: u32,
1160}
1161
1162#[derive(Debug, Clone, Serialize, Deserialize)]
1164#[serde(rename_all = "camelCase")]
1165pub struct CumFundingInfo {
1166 #[serde(
1168 rename = "allTime",
1169 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1170 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1171 )]
1172 pub all_time: Decimal,
1173 #[serde(
1175 rename = "sinceOpen",
1176 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1177 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1178 )]
1179 pub since_open: Decimal,
1180 #[serde(
1182 rename = "sinceChange",
1183 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1184 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1185 )]
1186 pub since_change: Decimal,
1187}
1188
1189#[derive(Debug, Clone, Serialize, Deserialize)]
1191#[serde(rename_all = "camelCase")]
1192pub struct PositionData {
1193 pub coin: String,
1195 #[serde(rename = "cumFunding")]
1197 pub cum_funding: CumFundingInfo,
1198 #[serde(
1200 rename = "entryPx",
1201 serialize_with = "crate::common::parse::serialize_optional_decimal_as_str",
1202 deserialize_with = "crate::common::parse::deserialize_optional_decimal_from_str",
1203 default
1204 )]
1205 pub entry_px: Option<Decimal>,
1206 pub leverage: LeverageInfo,
1208 #[serde(
1210 rename = "liquidationPx",
1211 serialize_with = "crate::common::parse::serialize_optional_decimal_as_str",
1212 deserialize_with = "crate::common::parse::deserialize_optional_decimal_from_str",
1213 default
1214 )]
1215 pub liquidation_px: Option<Decimal>,
1216 #[serde(
1218 rename = "marginUsed",
1219 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1220 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1221 )]
1222 pub margin_used: Decimal,
1223 #[serde(rename = "maxLeverage", default)]
1225 pub max_leverage: Option<u32>,
1226 #[serde(
1228 rename = "positionValue",
1229 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1230 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1231 )]
1232 pub position_value: Decimal,
1233 #[serde(
1235 rename = "returnOnEquity",
1236 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1237 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1238 )]
1239 pub return_on_equity: Decimal,
1240 #[serde(
1242 rename = "szi",
1243 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1244 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1245 )]
1246 pub szi: Decimal,
1247 #[serde(
1249 rename = "unrealizedPnl",
1250 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1251 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1252 )]
1253 pub unrealized_pnl: Decimal,
1254}
1255
1256#[derive(Debug, Clone, Serialize, Deserialize)]
1258#[serde(rename_all = "camelCase")]
1259pub struct CrossMarginSummary {
1260 #[serde(
1262 rename = "accountValue",
1263 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1264 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1265 )]
1266 pub account_value: Decimal,
1267 #[serde(
1269 rename = "totalNtlPos",
1270 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1271 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1272 )]
1273 pub total_ntl_pos: Decimal,
1274 #[serde(
1276 rename = "totalRawUsd",
1277 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1278 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1279 )]
1280 pub total_raw_usd: Decimal,
1281 #[serde(
1283 rename = "totalMarginUsed",
1284 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1285 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1286 )]
1287 pub total_margin_used: Decimal,
1288 #[serde(
1290 rename = "withdrawable",
1291 default,
1292 serialize_with = "crate::common::parse::serialize_optional_decimal_as_str",
1293 deserialize_with = "crate::common::parse::deserialize_optional_decimal_from_str"
1294 )]
1295 pub withdrawable: Option<Decimal>,
1296}