1use alloy_primitives::Address;
17use rust_decimal::Decimal;
18use serde::{Deserialize, Serialize};
19use ustr::Ustr;
20
21use crate::common::enums::{
22 HyperliquidSide, HyperliquidTpSl, HyperliquidTrailingOffsetType, HyperliquidTriggerPriceType,
23};
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct HyperliquidMeta {
28 #[serde(default)]
29 pub universe: Vec<HyperliquidAssetInfo>,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
34#[serde(rename_all = "camelCase")]
35pub struct HyperliquidCandle {
36 #[serde(rename = "t")]
38 pub timestamp: u64,
39 #[serde(rename = "o")]
41 pub open: String,
42 #[serde(rename = "h")]
44 pub high: String,
45 #[serde(rename = "l")]
47 pub low: String,
48 #[serde(rename = "c")]
50 pub close: String,
51 #[serde(rename = "v")]
53 pub volume: String,
54 #[serde(rename = "n", default)]
56 pub num_trades: Option<u64>,
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct HyperliquidCandleSnapshot {
62 #[serde(default)]
64 pub data: Vec<HyperliquidCandle>,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
69#[serde(rename_all = "camelCase")]
70pub struct HyperliquidAssetInfo {
71 pub name: Ustr,
73 pub sz_decimals: u32,
75 #[serde(default)]
77 pub max_leverage: Option<u32>,
78 #[serde(default)]
80 pub only_isolated: Option<bool>,
81 #[serde(default)]
83 pub is_delisted: Option<bool>,
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
92#[serde(rename_all = "camelCase")]
93pub struct PerpMeta {
94 pub universe: Vec<PerpAsset>,
96 #[serde(default)]
98 pub margin_tables: Vec<(u32, MarginTable)>,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
103#[serde(rename_all = "camelCase")]
104pub struct PerpAsset {
105 pub name: String,
107 pub sz_decimals: u32,
109 #[serde(default)]
111 pub max_leverage: Option<u32>,
112 #[serde(default)]
114 pub only_isolated: Option<bool>,
115 #[serde(default)]
117 pub is_delisted: Option<bool>,
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
122#[serde(rename_all = "camelCase")]
123pub struct MarginTable {
124 pub description: String,
126 #[serde(default)]
128 pub margin_tiers: Vec<MarginTier>,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
133#[serde(rename_all = "camelCase")]
134pub struct MarginTier {
135 pub lower_bound: String,
137 pub max_leverage: u32,
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize)]
143#[serde(rename_all = "camelCase")]
144pub struct SpotMeta {
145 pub tokens: Vec<SpotToken>,
147 pub universe: Vec<SpotPair>,
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize)]
153#[serde(rename_all = "snake_case")]
154pub struct EvmContract {
155 pub address: Address,
157 pub evm_extra_wei_decimals: i32,
159}
160
161#[derive(Debug, Clone, Serialize, Deserialize)]
163#[serde(rename_all = "camelCase")]
164pub struct SpotToken {
165 pub name: String,
167 pub sz_decimals: u32,
169 pub wei_decimals: u32,
171 pub index: u32,
173 pub token_id: String,
175 pub is_canonical: bool,
177 #[serde(default)]
179 pub evm_contract: Option<EvmContract>,
180 #[serde(default)]
182 pub full_name: Option<String>,
183 #[serde(default)]
185 pub deployer_trading_fee_share: Option<String>,
186}
187
188#[derive(Debug, Clone, Serialize, Deserialize)]
190#[serde(rename_all = "camelCase")]
191pub struct SpotPair {
192 pub name: String,
194 pub tokens: [u32; 2],
196 pub index: u32,
198 pub is_canonical: bool,
200}
201
202#[derive(Debug, Clone, Serialize, Deserialize)]
209#[serde(untagged)]
210pub enum PerpMetaAndCtxs {
211 Payload(Box<(PerpMeta, Vec<PerpAssetCtx>)>),
213}
214
215#[derive(Debug, Clone, Serialize, Deserialize)]
217#[serde(rename_all = "camelCase")]
218pub struct PerpAssetCtx {
219 #[serde(default)]
221 pub mark_px: Option<String>,
222 #[serde(default)]
224 pub mid_px: Option<String>,
225 #[serde(default)]
227 pub funding: Option<String>,
228 #[serde(default)]
230 pub open_interest: Option<String>,
231}
232
233#[derive(Debug, Clone, Serialize, Deserialize)]
236#[serde(untagged)]
237pub enum SpotMetaAndCtxs {
238 Payload(Box<(SpotMeta, Vec<SpotAssetCtx>)>),
240}
241
242#[derive(Debug, Clone, Serialize, Deserialize)]
244#[serde(rename_all = "camelCase")]
245pub struct SpotAssetCtx {
246 #[serde(default)]
248 pub mark_px: Option<String>,
249 #[serde(default)]
251 pub mid_px: Option<String>,
252 #[serde(default)]
254 pub day_volume: Option<String>,
255}
256
257#[derive(Debug, Clone, Serialize, Deserialize)]
259pub struct HyperliquidL2Book {
260 pub coin: Ustr,
262 pub levels: Vec<Vec<HyperliquidLevel>>,
264 pub time: u64,
266}
267
268#[derive(Debug, Clone, Serialize, Deserialize)]
270pub struct HyperliquidLevel {
271 pub px: String,
273 pub sz: String,
275}
276
277pub type HyperliquidFills = Vec<HyperliquidFill>;
281
282#[derive(Debug, Clone, Serialize, Deserialize)]
284pub struct HyperliquidFill {
285 pub coin: Ustr,
287 pub px: String,
289 pub sz: String,
291 pub side: HyperliquidSide,
293 pub time: u64,
295 #[serde(rename = "startPosition")]
297 pub start_position: String,
298 pub dir: String,
300 #[serde(rename = "closedPnl")]
302 pub closed_pnl: String,
303 pub hash: String,
305 pub oid: u64,
307 pub crossed: bool,
309 pub fee: String,
311}
312
313#[derive(Debug, Clone, Serialize, Deserialize)]
315pub struct HyperliquidOrderStatus {
316 #[serde(default)]
317 pub statuses: Vec<HyperliquidOrderStatusEntry>,
318}
319
320#[derive(Debug, Clone, Serialize, Deserialize)]
322pub struct HyperliquidOrderStatusEntry {
323 pub order: HyperliquidOrderInfo,
325 pub status: String,
327 #[serde(rename = "statusTimestamp")]
329 pub status_timestamp: u64,
330}
331
332#[derive(Debug, Clone, Serialize, Deserialize)]
334pub struct HyperliquidOrderInfo {
335 pub coin: Ustr,
337 pub side: HyperliquidSide,
339 #[serde(rename = "limitPx")]
341 pub limit_px: String,
342 pub sz: String,
344 pub oid: u64,
346 pub timestamp: u64,
348 #[serde(rename = "origSz")]
350 pub orig_sz: String,
351}
352
353#[derive(Debug, Clone, Serialize)]
355pub struct HyperliquidExchangeRequest<T> {
356 pub action: T,
358 #[serde(rename = "nonce")]
360 pub nonce: u64,
361 #[serde(rename = "signature")]
363 pub signature: String,
364 #[serde(rename = "vaultAddress", skip_serializing_if = "Option::is_none")]
366 pub vault_address: Option<String>,
367}
368
369impl<T> HyperliquidExchangeRequest<T>
370where
371 T: Serialize,
372{
373 pub fn new(action: T, nonce: u64, signature: String) -> Self {
375 Self {
376 action,
377 nonce,
378 signature,
379 vault_address: None,
380 }
381 }
382
383 pub fn with_vault(action: T, nonce: u64, signature: String, vault_address: String) -> Self {
385 Self {
386 action,
387 nonce,
388 signature,
389 vault_address: Some(vault_address),
390 }
391 }
392
393 pub fn to_sign_value(&self) -> serde_json::Result<serde_json::Value> {
395 serde_json::to_value(self)
396 }
397}
398
399#[derive(Debug, Clone, Serialize, Deserialize)]
401#[serde(untagged)]
402pub enum HyperliquidExchangeResponse {
403 Status {
405 status: String,
407 response: serde_json::Value,
409 },
410 Error {
412 error: String,
414 },
415}
416
417#[derive(Debug, Clone, Serialize, Deserialize)]
423#[serde(rename_all = "camelCase")]
424pub struct HyperliquidTriggerOrderParams {
425 #[serde(rename = "isMarket")]
427 pub is_market: bool,
428 #[serde(rename = "triggerPx")]
430 pub trigger_px: String,
431 pub tpsl: HyperliquidTpSl,
433 #[serde(rename = "triggerPxType", skip_serializing_if = "Option::is_none")]
435 pub trigger_px_type: Option<HyperliquidTriggerPriceType>,
436}
437
438#[derive(Debug, Clone, Serialize, Deserialize)]
440#[serde(rename_all = "camelCase")]
441pub struct HyperliquidTrailingStopParams {
442 #[serde(
444 rename = "trailingOffset",
445 serialize_with = "crate::common::parse::serialize_decimal_as_str",
446 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
447 )]
448 pub trailing_offset: Decimal,
449 #[serde(rename = "trailingOffsetType")]
451 pub trailing_offset_type: HyperliquidTrailingOffsetType,
452 #[serde(rename = "activationPx", skip_serializing_if = "Option::is_none")]
454 pub activation_px: Option<String>,
455 pub tpsl: HyperliquidTpSl,
457}
458
459#[derive(Debug, Clone, Serialize, Deserialize)]
461#[serde(rename_all = "camelCase")]
462pub struct HyperliquidPlaceTriggerOrderRequest {
463 #[serde(rename = "a")]
465 pub asset: AssetId,
466 #[serde(rename = "b")]
468 pub is_buy: bool,
469 #[serde(
471 rename = "s",
472 serialize_with = "crate::common::parse::serialize_decimal_as_str",
473 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
474 )]
475 pub sz: Decimal,
476 #[serde(rename = "limitPx", skip_serializing_if = "Option::is_none")]
478 pub limit_px: Option<String>,
479 #[serde(flatten)]
481 pub trigger_params: HyperliquidTriggerOrderParams,
482 #[serde(rename = "reduceOnly", skip_serializing_if = "Option::is_none")]
484 pub reduce_only: Option<bool>,
485 #[serde(skip_serializing_if = "Option::is_none")]
487 pub cloid: Option<Cloid>,
488}
489
490#[derive(Debug, Clone, Serialize, Deserialize)]
492#[serde(rename_all = "camelCase")]
493pub struct HyperliquidModifyTriggerOrderRequest {
494 pub oid: OrderId,
496 #[serde(rename = "a")]
498 pub asset: AssetId,
499 #[serde(rename = "triggerPx")]
501 pub trigger_px: String,
502 #[serde(rename = "limitPx", skip_serializing_if = "Option::is_none")]
504 pub limit_px: Option<String>,
505 #[serde(
507 skip_serializing_if = "Option::is_none",
508 serialize_with = "crate::common::parse::serialize_optional_decimal_as_str",
509 deserialize_with = "crate::common::parse::deserialize_optional_decimal_from_str"
510 )]
511 pub sz: Option<Decimal>,
512}
513
514#[derive(Debug, Clone, Serialize, Deserialize)]
516#[serde(rename_all = "camelCase")]
517pub struct HyperliquidCancelTriggerOrderRequest {
518 #[serde(rename = "a")]
520 pub asset: AssetId,
521 pub oid: OrderId,
523}
524
525#[derive(Debug, Clone, Serialize, Deserialize)]
527#[serde(rename_all = "camelCase")]
528pub struct HyperliquidTriggerOrderStatus {
529 pub oid: OrderId,
531 pub status: String,
533 #[serde(rename = "statusTimestamp")]
535 pub status_timestamp: u64,
536 pub order: HyperliquidTriggerOrderInfo,
538}
539
540#[derive(Debug, Clone, Serialize, Deserialize)]
542#[serde(rename_all = "camelCase")]
543pub struct HyperliquidTriggerOrderInfo {
544 pub coin: Ustr,
546 pub side: HyperliquidSide,
548 #[serde(rename = "limitPx", skip_serializing_if = "Option::is_none")]
550 pub limit_px: Option<String>,
551 #[serde(rename = "triggerPx")]
553 pub trigger_px: String,
554 pub sz: String,
556 #[serde(rename = "isMarket")]
558 pub is_market: bool,
559 pub tpsl: HyperliquidTpSl,
561 pub oid: OrderId,
563 pub timestamp: u64,
565 #[serde(default)]
567 pub triggered: bool,
568 #[serde(rename = "triggerTime", skip_serializing_if = "Option::is_none")]
570 pub trigger_time: Option<u64>,
571}
572
573#[derive(Debug, Clone, Serialize, Deserialize)]
575#[serde(rename_all = "camelCase")]
576pub struct HyperliquidBracketOrderRequest {
577 #[serde(rename = "a")]
579 pub asset: AssetId,
580 #[serde(rename = "b")]
582 pub is_buy: bool,
583 #[serde(
585 rename = "s",
586 serialize_with = "crate::common::parse::serialize_decimal_as_str",
587 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
588 )]
589 pub sz: Decimal,
590 #[serde(rename = "limitPx")]
592 pub limit_px: String,
593 #[serde(rename = "tpTriggerPx")]
595 pub tp_trigger_px: String,
596 #[serde(rename = "tpLimitPx", skip_serializing_if = "Option::is_none")]
598 pub tp_limit_px: Option<String>,
599 #[serde(rename = "tpIsMarket", default)]
601 pub tp_is_market: bool,
602 #[serde(rename = "slTriggerPx")]
604 pub sl_trigger_px: String,
605 #[serde(rename = "slLimitPx", skip_serializing_if = "Option::is_none")]
607 pub sl_limit_px: Option<String>,
608 #[serde(rename = "slIsMarket", default)]
610 pub sl_is_market: bool,
611 #[serde(skip_serializing_if = "Option::is_none")]
613 pub cloid: Option<Cloid>,
614}
615
616#[derive(Debug, Clone, Serialize, Deserialize)]
618#[serde(rename_all = "camelCase")]
619pub struct HyperliquidOcoOrderRequest {
620 #[serde(rename = "a")]
622 pub asset: AssetId,
623 #[serde(rename = "b")]
625 pub is_buy: bool,
626 #[serde(
628 rename = "s",
629 serialize_with = "crate::common::parse::serialize_decimal_as_str",
630 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
631 )]
632 pub sz: Decimal,
633 #[serde(rename = "triggerPx1")]
635 pub trigger_px_1: String,
636 #[serde(rename = "limitPx1", skip_serializing_if = "Option::is_none")]
638 pub limit_px_1: Option<String>,
639 #[serde(rename = "isMarket1", default)]
641 pub is_market_1: bool,
642 #[serde(rename = "tpsl1")]
644 pub tpsl_1: HyperliquidTpSl,
645 #[serde(rename = "triggerPx2")]
647 pub trigger_px_2: String,
648 #[serde(rename = "limitPx2", skip_serializing_if = "Option::is_none")]
650 pub limit_px_2: Option<String>,
651 #[serde(rename = "isMarket2", default)]
653 pub is_market_2: bool,
654 #[serde(rename = "tpsl2")]
656 pub tpsl_2: HyperliquidTpSl,
657 #[serde(rename = "reduceOnly", skip_serializing_if = "Option::is_none")]
659 pub reduce_only: Option<bool>,
660}
661
662#[cfg(test)]
667mod tests {
668 use rstest::rstest;
669
670 use super::*;
671
672 #[rstest]
673 fn test_meta_deserialization() {
674 let json = r#"{"universe": [{"name": "BTC", "szDecimals": 5}]}"#;
675
676 let meta: HyperliquidMeta = serde_json::from_str(json).unwrap();
677
678 assert_eq!(meta.universe.len(), 1);
679 assert_eq!(meta.universe[0].name, "BTC");
680 assert_eq!(meta.universe[0].sz_decimals, 5);
681 }
682
683 #[rstest]
684 fn test_l2_book_deserialization() {
685 let json = r#"{"coin": "BTC", "levels": [[{"px": "50000", "sz": "1.5"}], [{"px": "50100", "sz": "2.0"}]], "time": 1234567890}"#;
686
687 let book: HyperliquidL2Book = serde_json::from_str(json).unwrap();
688
689 assert_eq!(book.coin, "BTC");
690 assert_eq!(book.levels.len(), 2);
691 assert_eq!(book.time, 1234567890);
692 }
693
694 #[rstest]
695 fn test_exchange_response_deserialization() {
696 let json = r#"{"status": "ok", "response": {"type": "order"}}"#;
697
698 let response: HyperliquidExchangeResponse = serde_json::from_str(json).unwrap();
699
700 match response {
701 HyperliquidExchangeResponse::Status { status, .. } => assert_eq!(status, "ok"),
702 _ => panic!("Expected status response"),
703 }
704 }
705}
706
707pub mod execution_cloid {
713 use std::fmt;
714
715 use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error};
716
717 #[derive(Clone, PartialEq, Eq, Hash, Debug)]
719 pub struct Cloid(pub [u8; 16]);
720
721 impl Cloid {
722 pub fn from_hex<S: AsRef<str>>(s: S) -> Result<Self, String> {
728 let hex_str = s.as_ref();
729 let without_prefix = hex_str
730 .strip_prefix("0x")
731 .ok_or("CLOID must start with '0x'")?;
732
733 if without_prefix.len() != 32 {
734 return Err("CLOID must be exactly 32 hex characters (128 bits)".to_string());
735 }
736
737 let mut bytes = [0u8; 16];
738 for i in 0..16 {
739 let byte_str = &without_prefix[i * 2..i * 2 + 2];
740 bytes[i] = u8::from_str_radix(byte_str, 16)
741 .map_err(|_| "Invalid hex character in CLOID".to_string())?;
742 }
743
744 Ok(Cloid(bytes))
745 }
746
747 pub fn to_hex(&self) -> String {
749 let mut result = String::with_capacity(34);
750 result.push_str("0x");
751 for byte in &self.0 {
752 result.push_str(&format!("{:02x}", byte));
753 }
754 result
755 }
756 }
757
758 impl fmt::Display for Cloid {
759 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
760 write!(f, "{}", self.to_hex())
761 }
762 }
763
764 impl Serialize for Cloid {
765 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
766 where
767 S: Serializer,
768 {
769 serializer.serialize_str(&self.to_hex())
770 }
771 }
772
773 impl<'de> Deserialize<'de> for Cloid {
774 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
775 where
776 D: Deserializer<'de>,
777 {
778 let s = String::deserialize(deserializer)?;
779 Cloid::from_hex(&s).map_err(D::Error::custom)
780 }
781 }
782}
783
784pub use execution_cloid::Cloid;
785
786pub type AssetId = u32;
791
792pub type OrderId = u64;
794
795#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
799pub enum HyperliquidExecTif {
800 #[serde(rename = "Alo")]
802 Alo,
803 #[serde(rename = "Ioc")]
805 Ioc,
806 #[serde(rename = "Gtc")]
808 Gtc,
809}
810
811#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
813pub enum HyperliquidExecTpSl {
814 #[serde(rename = "tp")]
816 Tp,
817 #[serde(rename = "sl")]
819 Sl,
820}
821
822#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
824pub enum HyperliquidExecGrouping {
825 #[serde(rename = "na")]
827 #[default]
828 Na,
829 #[serde(rename = "normalTpsl")]
831 NormalTpsl,
832 #[serde(rename = "positionTpsl")]
834 PositionTpsl,
835}
836
837#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
839#[serde(untagged)]
840pub enum HyperliquidExecOrderKind {
841 Limit {
843 limit: HyperliquidExecLimitParams,
845 },
846 Trigger {
848 trigger: HyperliquidExecTriggerParams,
850 },
851}
852
853#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
855pub struct HyperliquidExecLimitParams {
856 pub tif: HyperliquidExecTif,
858}
859
860#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
862#[serde(rename_all = "camelCase")]
863pub struct HyperliquidExecTriggerParams {
864 pub is_market: bool,
866 #[serde(
868 serialize_with = "crate::common::parse::serialize_decimal_as_str",
869 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
870 )]
871 pub trigger_px: Decimal,
872 pub tpsl: HyperliquidExecTpSl,
874}
875
876#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
881pub struct HyperliquidExecBuilderFee {
882 #[serde(rename = "b")]
884 pub address: String,
885 #[serde(rename = "f")]
887 pub fee_tenths_bp: u32,
888}
889
890#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
895pub struct HyperliquidExecPlaceOrderRequest {
896 #[serde(rename = "a")]
898 pub asset: AssetId,
899 #[serde(rename = "b")]
901 pub is_buy: bool,
902 #[serde(
904 rename = "p",
905 serialize_with = "crate::common::parse::serialize_decimal_as_str",
906 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
907 )]
908 pub price: Decimal,
909 #[serde(
911 rename = "s",
912 serialize_with = "crate::common::parse::serialize_decimal_as_str",
913 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
914 )]
915 pub size: Decimal,
916 #[serde(rename = "r")]
918 pub reduce_only: bool,
919 #[serde(rename = "t")]
921 pub kind: HyperliquidExecOrderKind,
922 #[serde(rename = "c", skip_serializing_if = "Option::is_none")]
924 pub cloid: Option<Cloid>,
925}
926
927#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
929pub struct HyperliquidExecCancelOrderRequest {
930 #[serde(rename = "a")]
932 pub asset: AssetId,
933 #[serde(rename = "o")]
935 pub oid: OrderId,
936}
937
938#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
940pub struct HyperliquidExecCancelByCloidRequest {
941 #[serde(rename = "a")]
943 pub asset: AssetId,
944 #[serde(rename = "c")]
946 pub cloid: Cloid,
947}
948
949#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
951pub struct HyperliquidExecModifyOrderRequest {
952 #[serde(rename = "a")]
954 pub asset: AssetId,
955 #[serde(rename = "o")]
957 pub oid: OrderId,
958 #[serde(
960 rename = "p",
961 skip_serializing_if = "Option::is_none",
962 serialize_with = "crate::common::parse::serialize_optional_decimal_as_str",
963 deserialize_with = "crate::common::parse::deserialize_optional_decimal_from_str"
964 )]
965 pub price: Option<Decimal>,
966 #[serde(
968 rename = "s",
969 skip_serializing_if = "Option::is_none",
970 serialize_with = "crate::common::parse::serialize_optional_decimal_as_str",
971 deserialize_with = "crate::common::parse::deserialize_optional_decimal_from_str"
972 )]
973 pub size: Option<Decimal>,
974 #[serde(rename = "r", skip_serializing_if = "Option::is_none")]
976 pub reduce_only: Option<bool>,
977 #[serde(rename = "t", skip_serializing_if = "Option::is_none")]
979 pub kind: Option<HyperliquidExecOrderKind>,
980}
981
982#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
984pub struct HyperliquidExecTwapRequest {
985 #[serde(rename = "a")]
987 pub asset: AssetId,
988 #[serde(rename = "b")]
990 pub is_buy: bool,
991 #[serde(
993 rename = "s",
994 serialize_with = "crate::common::parse::serialize_decimal_as_str",
995 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
996 )]
997 pub size: Decimal,
998 #[serde(rename = "m")]
1000 pub duration_ms: u64,
1001}
1002
1003#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1009#[serde(tag = "type", rename_all = "camelCase")]
1010pub enum HyperliquidExecAction {
1011 #[serde(rename = "order")]
1013 Order {
1014 orders: Vec<HyperliquidExecPlaceOrderRequest>,
1016 #[serde(default, skip_serializing_if = "is_default_exec_grouping")]
1018 grouping: HyperliquidExecGrouping,
1019 #[serde(skip_serializing_if = "Option::is_none")]
1021 builder: Option<HyperliquidExecBuilderFee>,
1022 },
1023
1024 #[serde(rename = "cancel")]
1026 Cancel {
1027 cancels: Vec<HyperliquidExecCancelOrderRequest>,
1029 },
1030
1031 #[serde(rename = "cancelByCloid")]
1033 CancelByCloid {
1034 cancels: Vec<HyperliquidExecCancelByCloidRequest>,
1036 },
1037
1038 #[serde(rename = "modify")]
1040 Modify {
1041 #[serde(flatten)]
1043 modify: HyperliquidExecModifyOrderRequest,
1044 },
1045
1046 #[serde(rename = "batchModify")]
1048 BatchModify {
1049 modifies: Vec<HyperliquidExecModifyOrderRequest>,
1051 },
1052
1053 #[serde(rename = "scheduleCancel")]
1055 ScheduleCancel {
1056 #[serde(skip_serializing_if = "Option::is_none")]
1059 time: Option<u64>,
1060 },
1061
1062 #[serde(rename = "updateLeverage")]
1064 UpdateLeverage {
1065 #[serde(rename = "a")]
1067 asset: AssetId,
1068 #[serde(rename = "isCross")]
1070 is_cross: bool,
1071 #[serde(rename = "leverage")]
1073 leverage: u32,
1074 },
1075
1076 #[serde(rename = "updateIsolatedMargin")]
1078 UpdateIsolatedMargin {
1079 #[serde(rename = "a")]
1081 asset: AssetId,
1082 #[serde(
1084 rename = "delta",
1085 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1086 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1087 )]
1088 delta: Decimal,
1089 },
1090
1091 #[serde(rename = "usdClassTransfer")]
1093 UsdClassTransfer {
1094 from: String,
1096 to: String,
1098 #[serde(
1100 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1101 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1102 )]
1103 amount: Decimal,
1104 },
1105
1106 #[serde(rename = "twapPlace")]
1108 TwapPlace {
1109 #[serde(flatten)]
1111 twap: HyperliquidExecTwapRequest,
1112 },
1113
1114 #[serde(rename = "twapCancel")]
1116 TwapCancel {
1117 #[serde(rename = "a")]
1119 asset: AssetId,
1120 #[serde(rename = "t")]
1122 twap_id: u64,
1123 },
1124
1125 #[serde(rename = "noop")]
1127 Noop,
1128}
1129
1130fn is_default_exec_grouping(grouping: &HyperliquidExecGrouping) -> bool {
1132 matches!(grouping, HyperliquidExecGrouping::Na)
1133}
1134
1135#[derive(Debug, Clone, Serialize)]
1140#[serde(rename_all = "camelCase")]
1141pub struct HyperliquidExecRequest {
1142 pub action: HyperliquidExecAction,
1144 pub nonce: u64,
1146 pub signature: String,
1148 #[serde(skip_serializing_if = "Option::is_none")]
1150 pub vault_address: Option<String>,
1151 #[serde(skip_serializing_if = "Option::is_none")]
1154 pub expires_after: Option<u64>,
1155}
1156
1157#[derive(Debug, Clone, Serialize, Deserialize)]
1159pub struct HyperliquidExecResponse {
1160 pub status: String,
1162 pub response: HyperliquidExecResponseData,
1164}
1165
1166#[derive(Debug, Clone, Serialize, Deserialize)]
1168#[serde(tag = "type")]
1169pub enum HyperliquidExecResponseData {
1170 #[serde(rename = "order")]
1172 Order {
1173 data: HyperliquidExecOrderResponseData,
1175 },
1176 #[serde(rename = "cancel")]
1178 Cancel {
1179 data: HyperliquidExecCancelResponseData,
1181 },
1182 #[serde(rename = "modify")]
1184 Modify {
1185 data: HyperliquidExecModifyResponseData,
1187 },
1188 #[serde(rename = "default")]
1190 Default,
1191 #[serde(other)]
1193 Unknown,
1194}
1195
1196#[derive(Debug, Clone, Serialize, Deserialize)]
1198pub struct HyperliquidExecOrderResponseData {
1199 pub statuses: Vec<HyperliquidExecOrderStatus>,
1201}
1202
1203#[derive(Debug, Clone, Serialize, Deserialize)]
1205pub struct HyperliquidExecCancelResponseData {
1206 pub statuses: Vec<HyperliquidExecCancelStatus>,
1208}
1209
1210#[derive(Debug, Clone, Serialize, Deserialize)]
1212pub struct HyperliquidExecModifyResponseData {
1213 pub statuses: Vec<HyperliquidExecModifyStatus>,
1215}
1216
1217#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1219#[serde(untagged)]
1220pub enum HyperliquidExecOrderStatus {
1221 Resting {
1223 resting: HyperliquidExecRestingInfo,
1225 },
1226 Filled {
1228 filled: HyperliquidExecFilledInfo,
1230 },
1231 Error {
1233 error: String,
1235 },
1236}
1237
1238#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1240pub struct HyperliquidExecRestingInfo {
1241 pub oid: OrderId,
1243}
1244
1245#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1247pub struct HyperliquidExecFilledInfo {
1248 #[serde(
1250 rename = "totalSz",
1251 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1252 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1253 )]
1254 pub total_sz: Decimal,
1255 #[serde(
1257 rename = "avgPx",
1258 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1259 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1260 )]
1261 pub avg_px: Decimal,
1262 pub oid: OrderId,
1264}
1265
1266#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1268#[serde(untagged)]
1269pub enum HyperliquidExecCancelStatus {
1270 Success(String), Error {
1274 error: String,
1276 },
1277}
1278
1279#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1281#[serde(untagged)]
1282pub enum HyperliquidExecModifyStatus {
1283 Success(String), Error {
1287 error: String,
1289 },
1290}
1291
1292#[derive(Debug, Clone, Serialize, Deserialize)]
1295#[serde(rename_all = "camelCase")]
1296pub struct ClearinghouseState {
1297 #[serde(default)]
1299 pub asset_positions: Vec<AssetPosition>,
1300 #[serde(default)]
1302 pub cross_margin_summary: Option<CrossMarginSummary>,
1303 #[serde(default)]
1305 pub time: Option<u64>,
1306}
1307
1308#[derive(Debug, Clone, Serialize, Deserialize)]
1310#[serde(rename_all = "camelCase")]
1311pub struct AssetPosition {
1312 pub position: PositionData,
1314 #[serde(rename = "type")]
1316 pub position_type: String,
1317}
1318
1319#[derive(Debug, Clone, Serialize, Deserialize)]
1321#[serde(rename_all = "camelCase")]
1322pub struct PositionData {
1323 pub coin: String,
1325 #[serde(
1327 rename = "cumFunding",
1328 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1329 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1330 )]
1331 pub cum_funding: Decimal,
1332 #[serde(
1334 rename = "entryPx",
1335 serialize_with = "crate::common::parse::serialize_optional_decimal_as_str",
1336 deserialize_with = "crate::common::parse::deserialize_optional_decimal_from_str",
1337 default
1338 )]
1339 pub entry_px: Option<Decimal>,
1340 #[serde(
1342 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1343 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1344 )]
1345 pub leverage: Decimal,
1346 #[serde(
1348 rename = "liquidationPx",
1349 serialize_with = "crate::common::parse::serialize_optional_decimal_as_str",
1350 deserialize_with = "crate::common::parse::deserialize_optional_decimal_from_str",
1351 default
1352 )]
1353 pub liquidation_px: Option<Decimal>,
1354 #[serde(
1356 rename = "marginUsed",
1357 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1358 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1359 )]
1360 pub margin_used: Decimal,
1361 #[serde(
1363 rename = "maxTradeSzs",
1364 serialize_with = "crate::common::parse::serialize_vec_decimal_as_str",
1365 deserialize_with = "crate::common::parse::deserialize_vec_decimal_from_str"
1366 )]
1367 pub max_trade_szs: Vec<Decimal>,
1368 #[serde(
1370 rename = "positionValue",
1371 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1372 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1373 )]
1374 pub position_value: Decimal,
1375 #[serde(
1377 rename = "returnOnEquity",
1378 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1379 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1380 )]
1381 pub return_on_equity: Decimal,
1382 #[serde(
1384 rename = "szi",
1385 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1386 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1387 )]
1388 pub szi: Decimal,
1389 #[serde(
1391 rename = "unrealizedPnl",
1392 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1393 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1394 )]
1395 pub unrealized_pnl: Decimal,
1396}
1397
1398#[derive(Debug, Clone, Serialize, Deserialize)]
1400#[serde(rename_all = "camelCase")]
1401pub struct CrossMarginSummary {
1402 #[serde(
1404 rename = "accountValue",
1405 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1406 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1407 )]
1408 pub account_value: Decimal,
1409 #[serde(
1411 rename = "totalNtlPos",
1412 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1413 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1414 )]
1415 pub total_ntl_pos: Decimal,
1416 #[serde(
1418 rename = "totalRawUsd",
1419 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1420 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1421 )]
1422 pub total_raw_usd: Decimal,
1423 #[serde(
1425 rename = "totalMarginUsed",
1426 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1427 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1428 )]
1429 pub total_margin_used: Decimal,
1430 #[serde(
1432 rename = "withdrawable",
1433 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1434 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1435 )]
1436 pub withdrawable: Decimal,
1437}