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!("{:02x}", byte));
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)]
787mod tests {
788 use rstest::rstest;
789
790 use super::*;
791
792 #[rstest]
793 fn test_meta_deserialization() {
794 let json = r#"{"universe": [{"name": "BTC", "szDecimals": 5}]}"#;
795
796 let meta: HyperliquidMeta = serde_json::from_str(json).unwrap();
797
798 assert_eq!(meta.universe.len(), 1);
799 assert_eq!(meta.universe[0].name, "BTC");
800 assert_eq!(meta.universe[0].sz_decimals, 5);
801 }
802
803 #[rstest]
804 fn test_l2_book_deserialization() {
805 let json = r#"{"coin": "BTC", "levels": [[{"px": "50000", "sz": "1.5"}], [{"px": "50100", "sz": "2.0"}]], "time": 1234567890}"#;
806
807 let book: HyperliquidL2Book = serde_json::from_str(json).unwrap();
808
809 assert_eq!(book.coin, "BTC");
810 assert_eq!(book.levels.len(), 2);
811 assert_eq!(book.time, 1234567890);
812 }
813
814 #[rstest]
815 fn test_exchange_response_deserialization() {
816 let json = r#"{"status": "ok", "response": {"type": "order"}}"#;
817
818 let response: HyperliquidExchangeResponse = serde_json::from_str(json).unwrap();
819
820 match response {
821 HyperliquidExchangeResponse::Status { status, .. } => assert_eq!(status, "ok"),
822 _ => panic!("Expected status response"),
823 }
824 }
825}
826
827#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
831pub enum HyperliquidExecTif {
832 #[serde(rename = "Alo")]
834 Alo,
835 #[serde(rename = "Ioc")]
837 Ioc,
838 #[serde(rename = "Gtc")]
840 Gtc,
841}
842
843#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
845pub enum HyperliquidExecTpSl {
846 #[serde(rename = "tp")]
848 Tp,
849 #[serde(rename = "sl")]
851 Sl,
852}
853
854#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
856pub enum HyperliquidExecGrouping {
857 #[serde(rename = "na")]
859 #[default]
860 Na,
861 #[serde(rename = "normalTpsl")]
863 NormalTpsl,
864 #[serde(rename = "positionTpsl")]
866 PositionTpsl,
867}
868
869#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
871#[serde(untagged)]
872pub enum HyperliquidExecOrderKind {
873 Limit {
875 limit: HyperliquidExecLimitParams,
877 },
878 Trigger {
880 trigger: HyperliquidExecTriggerParams,
882 },
883}
884
885#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
887pub struct HyperliquidExecLimitParams {
888 pub tif: HyperliquidExecTif,
890}
891
892#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
894#[serde(rename_all = "camelCase")]
895pub struct HyperliquidExecTriggerParams {
896 pub is_market: bool,
898 #[serde(
900 serialize_with = "crate::common::parse::serialize_decimal_as_str",
901 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
902 )]
903 pub trigger_px: Decimal,
904 pub tpsl: HyperliquidExecTpSl,
906}
907
908#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
913pub struct HyperliquidExecBuilderFee {
914 #[serde(rename = "b")]
916 pub address: String,
917 #[serde(rename = "f")]
919 pub fee_tenths_bp: u32,
920}
921
922#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
927pub struct HyperliquidExecPlaceOrderRequest {
928 #[serde(rename = "a")]
930 pub asset: AssetId,
931 #[serde(rename = "b")]
933 pub is_buy: bool,
934 #[serde(
936 rename = "p",
937 serialize_with = "crate::common::parse::serialize_decimal_as_str",
938 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
939 )]
940 pub price: Decimal,
941 #[serde(
943 rename = "s",
944 serialize_with = "crate::common::parse::serialize_decimal_as_str",
945 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
946 )]
947 pub size: Decimal,
948 #[serde(rename = "r")]
950 pub reduce_only: bool,
951 #[serde(rename = "t")]
953 pub kind: HyperliquidExecOrderKind,
954 #[serde(rename = "c", skip_serializing_if = "Option::is_none")]
956 pub cloid: Option<Cloid>,
957}
958
959#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
961pub struct HyperliquidExecCancelOrderRequest {
962 #[serde(rename = "a")]
964 pub asset: AssetId,
965 #[serde(rename = "o")]
967 pub oid: OrderId,
968}
969
970#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
972pub struct HyperliquidExecCancelByCloidRequest {
973 #[serde(rename = "a")]
975 pub asset: AssetId,
976 #[serde(rename = "c")]
978 pub cloid: Cloid,
979}
980
981#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
983pub struct HyperliquidExecModifyOrderRequest {
984 #[serde(rename = "a")]
986 pub asset: AssetId,
987 #[serde(rename = "o")]
989 pub oid: OrderId,
990 #[serde(
992 rename = "p",
993 skip_serializing_if = "Option::is_none",
994 serialize_with = "crate::common::parse::serialize_optional_decimal_as_str",
995 deserialize_with = "crate::common::parse::deserialize_optional_decimal_from_str"
996 )]
997 pub price: Option<Decimal>,
998 #[serde(
1000 rename = "s",
1001 skip_serializing_if = "Option::is_none",
1002 serialize_with = "crate::common::parse::serialize_optional_decimal_as_str",
1003 deserialize_with = "crate::common::parse::deserialize_optional_decimal_from_str"
1004 )]
1005 pub size: Option<Decimal>,
1006 #[serde(rename = "r", skip_serializing_if = "Option::is_none")]
1008 pub reduce_only: Option<bool>,
1009 #[serde(rename = "t", skip_serializing_if = "Option::is_none")]
1011 pub kind: Option<HyperliquidExecOrderKind>,
1012}
1013
1014#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1016pub struct HyperliquidExecTwapRequest {
1017 #[serde(rename = "a")]
1019 pub asset: AssetId,
1020 #[serde(rename = "b")]
1022 pub is_buy: bool,
1023 #[serde(
1025 rename = "s",
1026 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1027 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1028 )]
1029 pub size: Decimal,
1030 #[serde(rename = "m")]
1032 pub duration_ms: u64,
1033}
1034
1035#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1041#[serde(tag = "type")]
1042pub enum HyperliquidExecAction {
1043 #[serde(rename = "order")]
1045 Order {
1046 orders: Vec<HyperliquidExecPlaceOrderRequest>,
1048 #[serde(default)]
1050 grouping: HyperliquidExecGrouping,
1051 #[serde(skip_serializing_if = "Option::is_none")]
1053 builder: Option<HyperliquidExecBuilderFee>,
1054 },
1055
1056 #[serde(rename = "cancel")]
1058 Cancel {
1059 cancels: Vec<HyperliquidExecCancelOrderRequest>,
1061 },
1062
1063 #[serde(rename = "cancelByCloid")]
1065 CancelByCloid {
1066 cancels: Vec<HyperliquidExecCancelByCloidRequest>,
1068 },
1069
1070 #[serde(rename = "modify")]
1072 Modify {
1073 #[serde(flatten)]
1075 modify: HyperliquidExecModifyOrderRequest,
1076 },
1077
1078 #[serde(rename = "batchModify")]
1080 BatchModify {
1081 modifies: Vec<HyperliquidExecModifyOrderRequest>,
1083 },
1084
1085 #[serde(rename = "scheduleCancel")]
1087 ScheduleCancel {
1088 #[serde(skip_serializing_if = "Option::is_none")]
1091 time: Option<u64>,
1092 },
1093
1094 #[serde(rename = "updateLeverage")]
1096 UpdateLeverage {
1097 #[serde(rename = "a")]
1099 asset: AssetId,
1100 #[serde(rename = "isCross")]
1102 is_cross: bool,
1103 #[serde(rename = "leverage")]
1105 leverage: u32,
1106 },
1107
1108 #[serde(rename = "updateIsolatedMargin")]
1110 UpdateIsolatedMargin {
1111 #[serde(rename = "a")]
1113 asset: AssetId,
1114 #[serde(
1116 rename = "delta",
1117 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1118 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1119 )]
1120 delta: Decimal,
1121 },
1122
1123 #[serde(rename = "usdClassTransfer")]
1125 UsdClassTransfer {
1126 from: String,
1128 to: String,
1130 #[serde(
1132 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1133 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1134 )]
1135 amount: Decimal,
1136 },
1137
1138 #[serde(rename = "twapPlace")]
1140 TwapPlace {
1141 #[serde(flatten)]
1143 twap: HyperliquidExecTwapRequest,
1144 },
1145
1146 #[serde(rename = "twapCancel")]
1148 TwapCancel {
1149 #[serde(rename = "a")]
1151 asset: AssetId,
1152 #[serde(rename = "t")]
1154 twap_id: u64,
1155 },
1156
1157 #[serde(rename = "noop")]
1159 Noop,
1160}
1161
1162#[derive(Debug, Clone, Serialize)]
1167#[serde(rename_all = "camelCase")]
1168pub struct HyperliquidExecRequest {
1169 pub action: HyperliquidExecAction,
1171 pub nonce: u64,
1173 pub signature: String,
1175 #[serde(skip_serializing_if = "Option::is_none")]
1177 pub vault_address: Option<String>,
1178 #[serde(skip_serializing_if = "Option::is_none")]
1181 pub expires_after: Option<u64>,
1182}
1183
1184#[derive(Debug, Clone, Serialize, Deserialize)]
1186pub struct HyperliquidExecResponse {
1187 pub status: String,
1189 pub response: HyperliquidExecResponseData,
1191}
1192
1193#[derive(Debug, Clone, Serialize, Deserialize)]
1195#[serde(tag = "type")]
1196pub enum HyperliquidExecResponseData {
1197 #[serde(rename = "order")]
1199 Order {
1200 data: HyperliquidExecOrderResponseData,
1202 },
1203 #[serde(rename = "cancel")]
1205 Cancel {
1206 data: HyperliquidExecCancelResponseData,
1208 },
1209 #[serde(rename = "modify")]
1211 Modify {
1212 data: HyperliquidExecModifyResponseData,
1214 },
1215 #[serde(rename = "default")]
1217 Default,
1218 #[serde(other)]
1220 Unknown,
1221}
1222
1223#[derive(Debug, Clone, Serialize, Deserialize)]
1225pub struct HyperliquidExecOrderResponseData {
1226 pub statuses: Vec<HyperliquidExecOrderStatus>,
1228}
1229
1230#[derive(Debug, Clone, Serialize, Deserialize)]
1232pub struct HyperliquidExecCancelResponseData {
1233 pub statuses: Vec<HyperliquidExecCancelStatus>,
1235}
1236
1237#[derive(Debug, Clone, Serialize, Deserialize)]
1239pub struct HyperliquidExecModifyResponseData {
1240 pub statuses: Vec<HyperliquidExecModifyStatus>,
1242}
1243
1244#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1246#[serde(untagged)]
1247pub enum HyperliquidExecOrderStatus {
1248 Resting {
1250 resting: HyperliquidExecRestingInfo,
1252 },
1253 Filled {
1255 filled: HyperliquidExecFilledInfo,
1257 },
1258 Error {
1260 error: String,
1262 },
1263}
1264
1265#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1267pub struct HyperliquidExecRestingInfo {
1268 pub oid: OrderId,
1270}
1271
1272#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1274pub struct HyperliquidExecFilledInfo {
1275 #[serde(
1277 rename = "totalSz",
1278 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1279 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1280 )]
1281 pub total_sz: Decimal,
1282 #[serde(
1284 rename = "avgPx",
1285 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1286 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1287 )]
1288 pub avg_px: Decimal,
1289 pub oid: OrderId,
1291}
1292
1293#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1295#[serde(untagged)]
1296pub enum HyperliquidExecCancelStatus {
1297 Success(String), Error {
1301 error: String,
1303 },
1304}
1305
1306#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1308#[serde(untagged)]
1309pub enum HyperliquidExecModifyStatus {
1310 Success(String), Error {
1314 error: String,
1316 },
1317}
1318
1319#[derive(Debug, Clone, Serialize, Deserialize)]
1322#[serde(rename_all = "camelCase")]
1323pub struct ClearinghouseState {
1324 #[serde(default)]
1326 pub asset_positions: Vec<AssetPosition>,
1327 #[serde(default)]
1329 pub cross_margin_summary: Option<CrossMarginSummary>,
1330 #[serde(default)]
1332 pub time: Option<u64>,
1333}
1334
1335#[derive(Debug, Clone, Serialize, Deserialize)]
1337#[serde(rename_all = "camelCase")]
1338pub struct AssetPosition {
1339 pub position: PositionData,
1341 #[serde(rename = "type")]
1343 pub position_type: HyperliquidPositionType,
1344}
1345
1346#[derive(Debug, Clone, Serialize, Deserialize)]
1348#[serde(rename_all = "camelCase")]
1349pub struct PositionData {
1350 pub coin: String,
1352 #[serde(
1354 rename = "cumFunding",
1355 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1356 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1357 )]
1358 pub cum_funding: Decimal,
1359 #[serde(
1361 rename = "entryPx",
1362 serialize_with = "crate::common::parse::serialize_optional_decimal_as_str",
1363 deserialize_with = "crate::common::parse::deserialize_optional_decimal_from_str",
1364 default
1365 )]
1366 pub entry_px: Option<Decimal>,
1367 #[serde(
1369 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1370 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1371 )]
1372 pub leverage: Decimal,
1373 #[serde(
1375 rename = "liquidationPx",
1376 serialize_with = "crate::common::parse::serialize_optional_decimal_as_str",
1377 deserialize_with = "crate::common::parse::deserialize_optional_decimal_from_str",
1378 default
1379 )]
1380 pub liquidation_px: Option<Decimal>,
1381 #[serde(
1383 rename = "marginUsed",
1384 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1385 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1386 )]
1387 pub margin_used: Decimal,
1388 #[serde(
1390 rename = "maxTradeSzs",
1391 serialize_with = "crate::common::parse::serialize_vec_decimal_as_str",
1392 deserialize_with = "crate::common::parse::deserialize_vec_decimal_from_str"
1393 )]
1394 pub max_trade_szs: Vec<Decimal>,
1395 #[serde(
1397 rename = "positionValue",
1398 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1399 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1400 )]
1401 pub position_value: Decimal,
1402 #[serde(
1404 rename = "returnOnEquity",
1405 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1406 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1407 )]
1408 pub return_on_equity: Decimal,
1409 #[serde(
1411 rename = "szi",
1412 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1413 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1414 )]
1415 pub szi: Decimal,
1416 #[serde(
1418 rename = "unrealizedPnl",
1419 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1420 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1421 )]
1422 pub unrealized_pnl: Decimal,
1423}
1424
1425#[derive(Debug, Clone, Serialize, Deserialize)]
1427#[serde(rename_all = "camelCase")]
1428pub struct CrossMarginSummary {
1429 #[serde(
1431 rename = "accountValue",
1432 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1433 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1434 )]
1435 pub account_value: Decimal,
1436 #[serde(
1438 rename = "totalNtlPos",
1439 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1440 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1441 )]
1442 pub total_ntl_pos: Decimal,
1443 #[serde(
1445 rename = "totalRawUsd",
1446 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1447 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1448 )]
1449 pub total_raw_usd: Decimal,
1450 #[serde(
1452 rename = "totalMarginUsed",
1453 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1454 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1455 )]
1456 pub total_margin_used: Decimal,
1457 #[serde(
1459 rename = "withdrawable",
1460 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1461 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1462 )]
1463 pub withdrawable: Decimal,
1464}