1use std::fmt::Display;
17
18use alloy_primitives::Address;
19use rust_decimal::Decimal;
20use serde::{Deserialize, Deserializer, Serialize, Serializer};
21use ustr::Ustr;
22
23use crate::common::enums::{
24 HyperliquidFillDirection, HyperliquidOrderStatus as HyperliquidOrderStatusEnum,
25 HyperliquidPositionType, HyperliquidSide, HyperliquidTpSl, HyperliquidTrailingOffsetType,
26 HyperliquidTriggerPriceType,
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 pub fn to_hex(&self) -> String {
64 let mut result = String::with_capacity(34);
65 result.push_str("0x");
66 for byte in &self.0 {
67 result.push_str(&format!("{byte:02x}"));
68 }
69 result
70 }
71}
72
73impl Display for Cloid {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 write!(f, "{}", self.to_hex())
76 }
77}
78
79impl Serialize for Cloid {
80 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
81 where
82 S: Serializer,
83 {
84 serializer.serialize_str(&self.to_hex())
85 }
86}
87
88impl<'de> Deserialize<'de> for Cloid {
89 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
90 where
91 D: Deserializer<'de>,
92 {
93 let s = String::deserialize(deserializer)?;
94 Self::from_hex(&s).map_err(serde::de::Error::custom)
95 }
96}
97
98pub type AssetId = u32;
103
104pub type OrderId = u64;
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
109#[serde(rename_all = "camelCase")]
110pub struct HyperliquidAssetInfo {
111 pub name: Ustr,
113 pub sz_decimals: u32,
115 #[serde(default)]
117 pub max_leverage: Option<u32>,
118 #[serde(default)]
120 pub only_isolated: Option<bool>,
121 #[serde(default)]
123 pub is_delisted: Option<bool>,
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize)]
132#[serde(rename_all = "camelCase")]
133pub struct PerpMeta {
134 pub universe: Vec<PerpAsset>,
136 #[serde(default)]
138 pub margin_tables: Vec<(u32, MarginTable)>,
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize)]
143#[serde(rename_all = "camelCase")]
144pub struct PerpAsset {
145 pub name: String,
147 pub sz_decimals: u32,
149 #[serde(default)]
151 pub max_leverage: Option<u32>,
152 #[serde(default)]
154 pub only_isolated: Option<bool>,
155 #[serde(default)]
157 pub is_delisted: Option<bool>,
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
162#[serde(rename_all = "camelCase")]
163pub struct MarginTable {
164 pub description: String,
166 #[serde(default)]
168 pub margin_tiers: Vec<MarginTier>,
169}
170
171#[derive(Debug, Clone, Serialize, Deserialize)]
173#[serde(rename_all = "camelCase")]
174pub struct MarginTier {
175 pub lower_bound: String,
177 pub max_leverage: u32,
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize)]
183#[serde(rename_all = "camelCase")]
184pub struct SpotMeta {
185 pub tokens: Vec<SpotToken>,
187 pub universe: Vec<SpotPair>,
189}
190
191#[derive(Debug, Clone, Serialize, Deserialize)]
193#[serde(rename_all = "snake_case")]
194pub struct EvmContract {
195 pub address: Address,
197 pub evm_extra_wei_decimals: i32,
199}
200
201#[derive(Debug, Clone, Serialize, Deserialize)]
203#[serde(rename_all = "camelCase")]
204pub struct SpotToken {
205 pub name: String,
207 pub sz_decimals: u32,
209 pub wei_decimals: u32,
211 pub index: u32,
213 pub token_id: String,
215 pub is_canonical: bool,
217 #[serde(default)]
219 pub evm_contract: Option<EvmContract>,
220 #[serde(default)]
222 pub full_name: Option<String>,
223 #[serde(default)]
225 pub deployer_trading_fee_share: Option<String>,
226}
227
228#[derive(Debug, Clone, Serialize, Deserialize)]
230#[serde(rename_all = "camelCase")]
231pub struct SpotPair {
232 pub name: String,
234 pub tokens: [u32; 2],
236 pub index: u32,
238 pub is_canonical: bool,
240}
241
242#[derive(Debug, Clone, Serialize, Deserialize)]
249#[serde(untagged)]
250pub enum PerpMetaAndCtxs {
251 Payload(Box<(PerpMeta, Vec<PerpAssetCtx>)>),
253}
254
255#[derive(Debug, Clone, Serialize, Deserialize)]
257#[serde(rename_all = "camelCase")]
258pub struct PerpAssetCtx {
259 #[serde(default)]
261 pub mark_px: Option<String>,
262 #[serde(default)]
264 pub mid_px: Option<String>,
265 #[serde(default)]
267 pub funding: Option<String>,
268 #[serde(default)]
270 pub open_interest: Option<String>,
271}
272
273#[derive(Debug, Clone, Serialize, Deserialize)]
276#[serde(untagged)]
277pub enum SpotMetaAndCtxs {
278 Payload(Box<(SpotMeta, Vec<SpotAssetCtx>)>),
280}
281
282#[derive(Debug, Clone, Serialize, Deserialize)]
284#[serde(rename_all = "camelCase")]
285pub struct SpotAssetCtx {
286 #[serde(default)]
288 pub mark_px: Option<String>,
289 #[serde(default)]
291 pub mid_px: Option<String>,
292 #[serde(default)]
294 pub day_volume: Option<String>,
295}
296
297#[derive(Debug, Clone, Serialize, Deserialize)]
299pub struct HyperliquidL2Book {
300 pub coin: Ustr,
302 pub levels: Vec<Vec<HyperliquidLevel>>,
304 pub time: u64,
306}
307
308#[derive(Debug, Clone, Serialize, Deserialize)]
310pub struct HyperliquidLevel {
311 pub px: String,
313 pub sz: String,
315}
316
317pub type HyperliquidFills = Vec<HyperliquidFill>;
321
322#[derive(Debug, Clone, Serialize, Deserialize)]
324pub struct HyperliquidMeta {
325 #[serde(default)]
326 pub universe: Vec<HyperliquidAssetInfo>,
327}
328
329#[derive(Debug, Clone, Serialize, Deserialize)]
331#[serde(rename_all = "camelCase")]
332pub struct HyperliquidCandle {
333 #[serde(rename = "t")]
335 pub timestamp: u64,
336 #[serde(rename = "T")]
338 pub end_timestamp: u64,
339 #[serde(rename = "o")]
341 pub open: String,
342 #[serde(rename = "h")]
344 pub high: String,
345 #[serde(rename = "l")]
347 pub low: String,
348 #[serde(rename = "c")]
350 pub close: String,
351 #[serde(rename = "v")]
353 pub volume: String,
354 #[serde(rename = "n", default)]
356 pub num_trades: Option<u64>,
357}
358
359#[derive(Debug, Clone, Serialize, Deserialize)]
361pub struct HyperliquidFill {
362 pub coin: Ustr,
364 pub px: String,
366 pub sz: String,
368 pub side: HyperliquidSide,
370 pub time: u64,
372 #[serde(rename = "startPosition")]
374 pub start_position: String,
375 pub dir: HyperliquidFillDirection,
377 #[serde(rename = "closedPnl")]
379 pub closed_pnl: String,
380 pub hash: String,
382 pub oid: u64,
384 pub crossed: bool,
386 pub fee: String,
388}
389
390#[derive(Debug, Clone, Serialize, Deserialize)]
392pub struct HyperliquidOrderStatus {
393 #[serde(default)]
394 pub statuses: Vec<HyperliquidOrderStatusEntry>,
395}
396
397#[derive(Debug, Clone, Serialize, Deserialize)]
399pub struct HyperliquidOrderStatusEntry {
400 pub order: HyperliquidOrderInfo,
402 pub status: HyperliquidOrderStatusEnum,
404 #[serde(rename = "statusTimestamp")]
406 pub status_timestamp: u64,
407}
408
409#[derive(Debug, Clone, Serialize, Deserialize)]
411pub struct HyperliquidOrderInfo {
412 pub coin: Ustr,
414 pub side: HyperliquidSide,
416 #[serde(rename = "limitPx")]
418 pub limit_px: String,
419 pub sz: String,
421 pub oid: u64,
423 pub timestamp: u64,
425 #[serde(rename = "origSz")]
427 pub orig_sz: String,
428}
429
430#[derive(Debug, Clone, Serialize)]
432pub struct HyperliquidSignature {
433 pub r: String,
435 pub s: String,
437 pub v: u64,
439}
440
441impl HyperliquidSignature {
442 pub fn from_hex(sig_hex: &str) -> Result<Self, String> {
444 let sig_hex = sig_hex.strip_prefix("0x").unwrap_or(sig_hex);
445
446 if sig_hex.len() != 130 {
447 return Err(format!(
448 "Invalid signature length: expected 130 hex chars, was {}",
449 sig_hex.len()
450 ));
451 }
452
453 let r = format!("0x{}", &sig_hex[0..64]);
454 let s = format!("0x{}", &sig_hex[64..128]);
455 let v = u64::from_str_radix(&sig_hex[128..130], 16)
456 .map_err(|e| format!("Failed to parse v component: {e}"))?;
457
458 Ok(Self { r, s, v })
459 }
460}
461
462#[derive(Debug, Clone, Serialize)]
464pub struct HyperliquidExchangeRequest<T> {
465 #[serde(rename = "action")]
467 pub action: T,
468 #[serde(rename = "nonce")]
470 pub nonce: u64,
471 #[serde(rename = "signature")]
473 pub signature: HyperliquidSignature,
474 #[serde(rename = "vaultAddress", skip_serializing_if = "Option::is_none")]
476 pub vault_address: Option<String>,
477 #[serde(rename = "expiresAfter", skip_serializing_if = "Option::is_none")]
479 pub expires_after: Option<u64>,
480}
481
482impl<T> HyperliquidExchangeRequest<T>
483where
484 T: Serialize,
485{
486 pub fn new(action: T, nonce: u64, signature: String) -> Result<Self, String> {
488 Ok(Self {
489 action,
490 nonce,
491 signature: HyperliquidSignature::from_hex(&signature)?,
492 vault_address: None,
493 expires_after: None,
494 })
495 }
496
497 pub fn with_vault(
499 action: T,
500 nonce: u64,
501 signature: String,
502 vault_address: String,
503 ) -> Result<Self, String> {
504 Ok(Self {
505 action,
506 nonce,
507 signature: HyperliquidSignature::from_hex(&signature)?,
508 vault_address: Some(vault_address),
509 expires_after: None,
510 })
511 }
512
513 pub fn to_sign_value(&self) -> serde_json::Result<serde_json::Value> {
515 serde_json::to_value(self)
516 }
517}
518
519#[derive(Debug, Clone, Serialize, Deserialize)]
521#[serde(untagged)]
522pub enum HyperliquidExchangeResponse {
523 Status {
525 status: String,
527 response: serde_json::Value,
529 },
530 Error {
532 error: String,
534 },
535}
536
537#[derive(Debug, Clone, Serialize, Deserialize)]
543#[serde(rename_all = "camelCase")]
544pub struct HyperliquidTriggerOrderParams {
545 #[serde(rename = "isMarket")]
547 pub is_market: bool,
548 #[serde(rename = "triggerPx")]
550 pub trigger_px: String,
551 pub tpsl: HyperliquidTpSl,
553 #[serde(rename = "triggerPxType", skip_serializing_if = "Option::is_none")]
555 pub trigger_px_type: Option<HyperliquidTriggerPriceType>,
556}
557
558#[derive(Debug, Clone, Serialize, Deserialize)]
560#[serde(rename_all = "camelCase")]
561pub struct HyperliquidTrailingStopParams {
562 #[serde(
564 rename = "trailingOffset",
565 serialize_with = "crate::common::parse::serialize_decimal_as_str",
566 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
567 )]
568 pub trailing_offset: Decimal,
569 #[serde(rename = "trailingOffsetType")]
571 pub trailing_offset_type: HyperliquidTrailingOffsetType,
572 #[serde(rename = "activationPx", skip_serializing_if = "Option::is_none")]
574 pub activation_px: Option<String>,
575 pub tpsl: HyperliquidTpSl,
577}
578
579#[derive(Debug, Clone, Serialize, Deserialize)]
581#[serde(rename_all = "camelCase")]
582pub struct HyperliquidPlaceTriggerOrderRequest {
583 #[serde(rename = "a")]
585 pub asset: AssetId,
586 #[serde(rename = "b")]
588 pub is_buy: bool,
589 #[serde(
591 rename = "s",
592 serialize_with = "crate::common::parse::serialize_decimal_as_str",
593 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
594 )]
595 pub sz: Decimal,
596 #[serde(rename = "limitPx", skip_serializing_if = "Option::is_none")]
598 pub limit_px: Option<String>,
599 #[serde(flatten)]
601 pub trigger_params: HyperliquidTriggerOrderParams,
602 #[serde(rename = "reduceOnly", skip_serializing_if = "Option::is_none")]
604 pub reduce_only: Option<bool>,
605 #[serde(skip_serializing_if = "Option::is_none")]
607 pub cloid: Option<Cloid>,
608}
609
610#[derive(Debug, Clone, Serialize, Deserialize)]
612#[serde(rename_all = "camelCase")]
613pub struct HyperliquidModifyTriggerOrderRequest {
614 pub oid: OrderId,
616 #[serde(rename = "a")]
618 pub asset: AssetId,
619 #[serde(rename = "triggerPx")]
621 pub trigger_px: String,
622 #[serde(rename = "limitPx", skip_serializing_if = "Option::is_none")]
624 pub limit_px: Option<String>,
625 #[serde(
627 skip_serializing_if = "Option::is_none",
628 serialize_with = "crate::common::parse::serialize_optional_decimal_as_str",
629 deserialize_with = "crate::common::parse::deserialize_optional_decimal_from_str"
630 )]
631 pub sz: Option<Decimal>,
632}
633
634#[derive(Debug, Clone, Serialize, Deserialize)]
636#[serde(rename_all = "camelCase")]
637pub struct HyperliquidCancelTriggerOrderRequest {
638 #[serde(rename = "a")]
640 pub asset: AssetId,
641 pub oid: OrderId,
643}
644
645#[derive(Debug, Clone, Serialize, Deserialize)]
647#[serde(rename_all = "camelCase")]
648pub struct HyperliquidTriggerOrderStatus {
649 pub oid: OrderId,
651 pub status: HyperliquidOrderStatusEnum,
653 #[serde(rename = "statusTimestamp")]
655 pub status_timestamp: u64,
656 pub order: HyperliquidTriggerOrderInfo,
658}
659
660#[derive(Debug, Clone, Serialize, Deserialize)]
662#[serde(rename_all = "camelCase")]
663pub struct HyperliquidTriggerOrderInfo {
664 pub coin: Ustr,
666 pub side: HyperliquidSide,
668 #[serde(rename = "limitPx", skip_serializing_if = "Option::is_none")]
670 pub limit_px: Option<String>,
671 #[serde(rename = "triggerPx")]
673 pub trigger_px: String,
674 pub sz: String,
676 #[serde(rename = "isMarket")]
678 pub is_market: bool,
679 pub tpsl: HyperliquidTpSl,
681 pub oid: OrderId,
683 pub timestamp: u64,
685 #[serde(default)]
687 pub triggered: bool,
688 #[serde(rename = "triggerTime", skip_serializing_if = "Option::is_none")]
690 pub trigger_time: Option<u64>,
691}
692
693#[derive(Debug, Clone, Serialize, Deserialize)]
695#[serde(rename_all = "camelCase")]
696pub struct HyperliquidBracketOrderRequest {
697 #[serde(rename = "a")]
699 pub asset: AssetId,
700 #[serde(rename = "b")]
702 pub is_buy: bool,
703 #[serde(
705 rename = "s",
706 serialize_with = "crate::common::parse::serialize_decimal_as_str",
707 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
708 )]
709 pub sz: Decimal,
710 #[serde(rename = "limitPx")]
712 pub limit_px: String,
713 #[serde(rename = "tpTriggerPx")]
715 pub tp_trigger_px: String,
716 #[serde(rename = "tpLimitPx", skip_serializing_if = "Option::is_none")]
718 pub tp_limit_px: Option<String>,
719 #[serde(rename = "tpIsMarket", default)]
721 pub tp_is_market: bool,
722 #[serde(rename = "slTriggerPx")]
724 pub sl_trigger_px: String,
725 #[serde(rename = "slLimitPx", skip_serializing_if = "Option::is_none")]
727 pub sl_limit_px: Option<String>,
728 #[serde(rename = "slIsMarket", default)]
730 pub sl_is_market: bool,
731 #[serde(skip_serializing_if = "Option::is_none")]
733 pub cloid: Option<Cloid>,
734}
735
736#[derive(Debug, Clone, Serialize, Deserialize)]
738#[serde(rename_all = "camelCase")]
739pub struct HyperliquidOcoOrderRequest {
740 #[serde(rename = "a")]
742 pub asset: AssetId,
743 #[serde(rename = "b")]
745 pub is_buy: bool,
746 #[serde(
748 rename = "s",
749 serialize_with = "crate::common::parse::serialize_decimal_as_str",
750 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
751 )]
752 pub sz: Decimal,
753 #[serde(rename = "triggerPx1")]
755 pub trigger_px_1: String,
756 #[serde(rename = "limitPx1", skip_serializing_if = "Option::is_none")]
758 pub limit_px_1: Option<String>,
759 #[serde(rename = "isMarket1", default)]
761 pub is_market_1: bool,
762 #[serde(rename = "tpsl1")]
764 pub tpsl_1: HyperliquidTpSl,
765 #[serde(rename = "triggerPx2")]
767 pub trigger_px_2: String,
768 #[serde(rename = "limitPx2", skip_serializing_if = "Option::is_none")]
770 pub limit_px_2: Option<String>,
771 #[serde(rename = "isMarket2", default)]
773 pub is_market_2: bool,
774 #[serde(rename = "tpsl2")]
776 pub tpsl_2: HyperliquidTpSl,
777 #[serde(rename = "reduceOnly", skip_serializing_if = "Option::is_none")]
779 pub reduce_only: Option<bool>,
780}
781
782#[cfg(test)]
783mod tests {
784 use rstest::rstest;
785
786 use super::*;
787
788 #[rstest]
789 fn test_meta_deserialization() {
790 let json = r#"{"universe": [{"name": "BTC", "szDecimals": 5}]}"#;
791
792 let meta: HyperliquidMeta = serde_json::from_str(json).unwrap();
793
794 assert_eq!(meta.universe.len(), 1);
795 assert_eq!(meta.universe[0].name, "BTC");
796 assert_eq!(meta.universe[0].sz_decimals, 5);
797 }
798
799 #[rstest]
800 fn test_l2_book_deserialization() {
801 let json = r#"{"coin": "BTC", "levels": [[{"px": "50000", "sz": "1.5"}], [{"px": "50100", "sz": "2.0"}]], "time": 1234567890}"#;
802
803 let book: HyperliquidL2Book = serde_json::from_str(json).unwrap();
804
805 assert_eq!(book.coin, "BTC");
806 assert_eq!(book.levels.len(), 2);
807 assert_eq!(book.time, 1234567890);
808 }
809
810 #[rstest]
811 fn test_exchange_response_deserialization() {
812 let json = r#"{"status": "ok", "response": {"type": "order"}}"#;
813
814 let response: HyperliquidExchangeResponse = serde_json::from_str(json).unwrap();
815
816 match response {
817 HyperliquidExchangeResponse::Status { status, .. } => assert_eq!(status, "ok"),
818 _ => panic!("Expected status response"),
819 }
820 }
821}
822
823#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
827pub enum HyperliquidExecTif {
828 #[serde(rename = "Alo")]
830 Alo,
831 #[serde(rename = "Ioc")]
833 Ioc,
834 #[serde(rename = "Gtc")]
836 Gtc,
837}
838
839#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
841pub enum HyperliquidExecTpSl {
842 #[serde(rename = "tp")]
844 Tp,
845 #[serde(rename = "sl")]
847 Sl,
848}
849
850#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
852pub enum HyperliquidExecGrouping {
853 #[serde(rename = "na")]
855 #[default]
856 Na,
857 #[serde(rename = "normalTpsl")]
859 NormalTpsl,
860 #[serde(rename = "positionTpsl")]
862 PositionTpsl,
863}
864
865#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
867#[serde(untagged)]
868pub enum HyperliquidExecOrderKind {
869 Limit {
871 limit: HyperliquidExecLimitParams,
873 },
874 Trigger {
876 trigger: HyperliquidExecTriggerParams,
878 },
879}
880
881#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
883pub struct HyperliquidExecLimitParams {
884 pub tif: HyperliquidExecTif,
886}
887
888#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
890#[serde(rename_all = "camelCase")]
891pub struct HyperliquidExecTriggerParams {
892 pub is_market: bool,
894 #[serde(
896 serialize_with = "crate::common::parse::serialize_decimal_as_str",
897 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
898 )]
899 pub trigger_px: Decimal,
900 pub tpsl: HyperliquidExecTpSl,
902}
903
904#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
909pub struct HyperliquidExecBuilderFee {
910 #[serde(rename = "b")]
912 pub address: String,
913 #[serde(rename = "f")]
915 pub fee_tenths_bp: u32,
916}
917
918#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
923pub struct HyperliquidExecPlaceOrderRequest {
924 #[serde(rename = "a")]
926 pub asset: AssetId,
927 #[serde(rename = "b")]
929 pub is_buy: bool,
930 #[serde(
932 rename = "p",
933 serialize_with = "crate::common::parse::serialize_decimal_as_str",
934 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
935 )]
936 pub price: Decimal,
937 #[serde(
939 rename = "s",
940 serialize_with = "crate::common::parse::serialize_decimal_as_str",
941 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
942 )]
943 pub size: Decimal,
944 #[serde(rename = "r")]
946 pub reduce_only: bool,
947 #[serde(rename = "t")]
949 pub kind: HyperliquidExecOrderKind,
950 #[serde(rename = "c", skip_serializing_if = "Option::is_none")]
952 pub cloid: Option<Cloid>,
953}
954
955#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
957pub struct HyperliquidExecCancelOrderRequest {
958 #[serde(rename = "a")]
960 pub asset: AssetId,
961 #[serde(rename = "o")]
963 pub oid: OrderId,
964}
965
966#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
968pub struct HyperliquidExecCancelByCloidRequest {
969 #[serde(rename = "a")]
971 pub asset: AssetId,
972 #[serde(rename = "c")]
974 pub cloid: Cloid,
975}
976
977#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
979pub struct HyperliquidExecModifyOrderRequest {
980 #[serde(rename = "a")]
982 pub asset: AssetId,
983 #[serde(rename = "o")]
985 pub oid: OrderId,
986 #[serde(
988 rename = "p",
989 skip_serializing_if = "Option::is_none",
990 serialize_with = "crate::common::parse::serialize_optional_decimal_as_str",
991 deserialize_with = "crate::common::parse::deserialize_optional_decimal_from_str"
992 )]
993 pub price: Option<Decimal>,
994 #[serde(
996 rename = "s",
997 skip_serializing_if = "Option::is_none",
998 serialize_with = "crate::common::parse::serialize_optional_decimal_as_str",
999 deserialize_with = "crate::common::parse::deserialize_optional_decimal_from_str"
1000 )]
1001 pub size: Option<Decimal>,
1002 #[serde(rename = "r", skip_serializing_if = "Option::is_none")]
1004 pub reduce_only: Option<bool>,
1005 #[serde(rename = "t", skip_serializing_if = "Option::is_none")]
1007 pub kind: Option<HyperliquidExecOrderKind>,
1008}
1009
1010#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1012pub struct HyperliquidExecTwapRequest {
1013 #[serde(rename = "a")]
1015 pub asset: AssetId,
1016 #[serde(rename = "b")]
1018 pub is_buy: bool,
1019 #[serde(
1021 rename = "s",
1022 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1023 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1024 )]
1025 pub size: Decimal,
1026 #[serde(rename = "m")]
1028 pub duration_ms: u64,
1029}
1030
1031#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1037#[serde(tag = "type")]
1038pub enum HyperliquidExecAction {
1039 #[serde(rename = "order")]
1041 Order {
1042 orders: Vec<HyperliquidExecPlaceOrderRequest>,
1044 #[serde(default)]
1046 grouping: HyperliquidExecGrouping,
1047 #[serde(skip_serializing_if = "Option::is_none")]
1049 builder: Option<HyperliquidExecBuilderFee>,
1050 },
1051
1052 #[serde(rename = "cancel")]
1054 Cancel {
1055 cancels: Vec<HyperliquidExecCancelOrderRequest>,
1057 },
1058
1059 #[serde(rename = "cancelByCloid")]
1061 CancelByCloid {
1062 cancels: Vec<HyperliquidExecCancelByCloidRequest>,
1064 },
1065
1066 #[serde(rename = "modify")]
1068 Modify {
1069 #[serde(flatten)]
1071 modify: HyperliquidExecModifyOrderRequest,
1072 },
1073
1074 #[serde(rename = "batchModify")]
1076 BatchModify {
1077 modifies: Vec<HyperliquidExecModifyOrderRequest>,
1079 },
1080
1081 #[serde(rename = "scheduleCancel")]
1083 ScheduleCancel {
1084 #[serde(skip_serializing_if = "Option::is_none")]
1087 time: Option<u64>,
1088 },
1089
1090 #[serde(rename = "updateLeverage")]
1092 UpdateLeverage {
1093 #[serde(rename = "a")]
1095 asset: AssetId,
1096 #[serde(rename = "isCross")]
1098 is_cross: bool,
1099 #[serde(rename = "leverage")]
1101 leverage: u32,
1102 },
1103
1104 #[serde(rename = "updateIsolatedMargin")]
1106 UpdateIsolatedMargin {
1107 #[serde(rename = "a")]
1109 asset: AssetId,
1110 #[serde(
1112 rename = "delta",
1113 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1114 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1115 )]
1116 delta: Decimal,
1117 },
1118
1119 #[serde(rename = "usdClassTransfer")]
1121 UsdClassTransfer {
1122 from: String,
1124 to: String,
1126 #[serde(
1128 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1129 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1130 )]
1131 amount: Decimal,
1132 },
1133
1134 #[serde(rename = "twapPlace")]
1136 TwapPlace {
1137 #[serde(flatten)]
1139 twap: HyperliquidExecTwapRequest,
1140 },
1141
1142 #[serde(rename = "twapCancel")]
1144 TwapCancel {
1145 #[serde(rename = "a")]
1147 asset: AssetId,
1148 #[serde(rename = "t")]
1150 twap_id: u64,
1151 },
1152
1153 #[serde(rename = "noop")]
1155 Noop,
1156}
1157
1158#[derive(Debug, Clone, Serialize)]
1163#[serde(rename_all = "camelCase")]
1164pub struct HyperliquidExecRequest {
1165 pub action: HyperliquidExecAction,
1167 pub nonce: u64,
1169 pub signature: String,
1171 #[serde(skip_serializing_if = "Option::is_none")]
1173 pub vault_address: Option<String>,
1174 #[serde(skip_serializing_if = "Option::is_none")]
1177 pub expires_after: Option<u64>,
1178}
1179
1180#[derive(Debug, Clone, Serialize, Deserialize)]
1182pub struct HyperliquidExecResponse {
1183 pub status: String,
1185 pub response: HyperliquidExecResponseData,
1187}
1188
1189#[derive(Debug, Clone, Serialize, Deserialize)]
1191#[serde(tag = "type")]
1192pub enum HyperliquidExecResponseData {
1193 #[serde(rename = "order")]
1195 Order {
1196 data: HyperliquidExecOrderResponseData,
1198 },
1199 #[serde(rename = "cancel")]
1201 Cancel {
1202 data: HyperliquidExecCancelResponseData,
1204 },
1205 #[serde(rename = "modify")]
1207 Modify {
1208 data: HyperliquidExecModifyResponseData,
1210 },
1211 #[serde(rename = "default")]
1213 Default,
1214 #[serde(other)]
1216 Unknown,
1217}
1218
1219#[derive(Debug, Clone, Serialize, Deserialize)]
1221pub struct HyperliquidExecOrderResponseData {
1222 pub statuses: Vec<HyperliquidExecOrderStatus>,
1224}
1225
1226#[derive(Debug, Clone, Serialize, Deserialize)]
1228pub struct HyperliquidExecCancelResponseData {
1229 pub statuses: Vec<HyperliquidExecCancelStatus>,
1231}
1232
1233#[derive(Debug, Clone, Serialize, Deserialize)]
1235pub struct HyperliquidExecModifyResponseData {
1236 pub statuses: Vec<HyperliquidExecModifyStatus>,
1238}
1239
1240#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1242#[serde(untagged)]
1243pub enum HyperliquidExecOrderStatus {
1244 Resting {
1246 resting: HyperliquidExecRestingInfo,
1248 },
1249 Filled {
1251 filled: HyperliquidExecFilledInfo,
1253 },
1254 Error {
1256 error: String,
1258 },
1259}
1260
1261#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1263pub struct HyperliquidExecRestingInfo {
1264 pub oid: OrderId,
1266}
1267
1268#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1270pub struct HyperliquidExecFilledInfo {
1271 #[serde(
1273 rename = "totalSz",
1274 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1275 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1276 )]
1277 pub total_sz: Decimal,
1278 #[serde(
1280 rename = "avgPx",
1281 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1282 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1283 )]
1284 pub avg_px: Decimal,
1285 pub oid: OrderId,
1287}
1288
1289#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1291#[serde(untagged)]
1292pub enum HyperliquidExecCancelStatus {
1293 Success(String), Error {
1297 error: String,
1299 },
1300}
1301
1302#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1304#[serde(untagged)]
1305pub enum HyperliquidExecModifyStatus {
1306 Success(String), Error {
1310 error: String,
1312 },
1313}
1314
1315#[derive(Debug, Clone, Serialize, Deserialize)]
1318#[serde(rename_all = "camelCase")]
1319pub struct ClearinghouseState {
1320 #[serde(default)]
1322 pub asset_positions: Vec<AssetPosition>,
1323 #[serde(default)]
1325 pub cross_margin_summary: Option<CrossMarginSummary>,
1326 #[serde(default)]
1328 pub time: Option<u64>,
1329}
1330
1331#[derive(Debug, Clone, Serialize, Deserialize)]
1333#[serde(rename_all = "camelCase")]
1334pub struct AssetPosition {
1335 pub position: PositionData,
1337 #[serde(rename = "type")]
1339 pub position_type: HyperliquidPositionType,
1340}
1341
1342#[derive(Debug, Clone, Serialize, Deserialize)]
1344#[serde(rename_all = "camelCase")]
1345pub struct PositionData {
1346 pub coin: String,
1348 #[serde(
1350 rename = "cumFunding",
1351 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1352 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1353 )]
1354 pub cum_funding: Decimal,
1355 #[serde(
1357 rename = "entryPx",
1358 serialize_with = "crate::common::parse::serialize_optional_decimal_as_str",
1359 deserialize_with = "crate::common::parse::deserialize_optional_decimal_from_str",
1360 default
1361 )]
1362 pub entry_px: Option<Decimal>,
1363 #[serde(
1365 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1366 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1367 )]
1368 pub leverage: Decimal,
1369 #[serde(
1371 rename = "liquidationPx",
1372 serialize_with = "crate::common::parse::serialize_optional_decimal_as_str",
1373 deserialize_with = "crate::common::parse::deserialize_optional_decimal_from_str",
1374 default
1375 )]
1376 pub liquidation_px: Option<Decimal>,
1377 #[serde(
1379 rename = "marginUsed",
1380 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1381 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1382 )]
1383 pub margin_used: Decimal,
1384 #[serde(
1386 rename = "maxTradeSzs",
1387 serialize_with = "crate::common::parse::serialize_vec_decimal_as_str",
1388 deserialize_with = "crate::common::parse::deserialize_vec_decimal_from_str"
1389 )]
1390 pub max_trade_szs: Vec<Decimal>,
1391 #[serde(
1393 rename = "positionValue",
1394 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1395 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1396 )]
1397 pub position_value: Decimal,
1398 #[serde(
1400 rename = "returnOnEquity",
1401 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1402 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1403 )]
1404 pub return_on_equity: Decimal,
1405 #[serde(
1407 rename = "szi",
1408 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1409 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1410 )]
1411 pub szi: Decimal,
1412 #[serde(
1414 rename = "unrealizedPnl",
1415 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1416 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1417 )]
1418 pub unrealized_pnl: Decimal,
1419}
1420
1421#[derive(Debug, Clone, Serialize, Deserialize)]
1423#[serde(rename_all = "camelCase")]
1424pub struct CrossMarginSummary {
1425 #[serde(
1427 rename = "accountValue",
1428 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1429 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1430 )]
1431 pub account_value: Decimal,
1432 #[serde(
1434 rename = "totalNtlPos",
1435 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1436 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1437 )]
1438 pub total_ntl_pos: Decimal,
1439 #[serde(
1441 rename = "totalRawUsd",
1442 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1443 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1444 )]
1445 pub total_raw_usd: Decimal,
1446 #[serde(
1448 rename = "totalMarginUsed",
1449 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1450 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1451 )]
1452 pub total_margin_used: Decimal,
1453 #[serde(
1455 rename = "withdrawable",
1456 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1457 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1458 )]
1459 pub withdrawable: Decimal,
1460}