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 HyperliquidSignature {
356 pub r: String,
358 pub s: String,
360 pub v: u64,
362}
363
364impl HyperliquidSignature {
365 pub fn from_hex(sig_hex: &str) -> Result<Self, String> {
367 let sig_hex = sig_hex.strip_prefix("0x").unwrap_or(sig_hex);
368
369 if sig_hex.len() != 130 {
370 return Err(format!(
371 "Invalid signature length: expected 130 hex chars, got {}",
372 sig_hex.len()
373 ));
374 }
375
376 let r = format!("0x{}", &sig_hex[0..64]);
377 let s = format!("0x{}", &sig_hex[64..128]);
378 let v = u64::from_str_radix(&sig_hex[128..130], 16)
379 .map_err(|e| format!("Failed to parse v component: {}", e))?;
380
381 Ok(Self { r, s, v })
382 }
383}
384
385#[derive(Debug, Clone, Serialize)]
387pub struct HyperliquidExchangeRequest<T> {
388 #[serde(rename = "action")]
390 pub action: T,
391 #[serde(rename = "nonce")]
393 pub nonce: u64,
394 #[serde(rename = "signature")]
396 pub signature: HyperliquidSignature,
397 #[serde(rename = "vaultAddress", skip_serializing_if = "Option::is_none")]
399 pub vault_address: Option<String>,
400 #[serde(rename = "expiresAfter", skip_serializing_if = "Option::is_none")]
402 pub expires_after: Option<u64>,
403}
404
405impl<T> HyperliquidExchangeRequest<T>
406where
407 T: Serialize,
408{
409 pub fn new(action: T, nonce: u64, signature: String) -> Result<Self, String> {
411 Ok(Self {
412 action,
413 nonce,
414 signature: HyperliquidSignature::from_hex(&signature)?,
415 vault_address: None,
416 expires_after: None,
417 })
418 }
419
420 pub fn with_vault(
422 action: T,
423 nonce: u64,
424 signature: String,
425 vault_address: String,
426 ) -> Result<Self, String> {
427 Ok(Self {
428 action,
429 nonce,
430 signature: HyperliquidSignature::from_hex(&signature)?,
431 vault_address: Some(vault_address),
432 expires_after: None,
433 })
434 }
435
436 pub fn to_sign_value(&self) -> serde_json::Result<serde_json::Value> {
438 serde_json::to_value(self)
439 }
440}
441
442#[derive(Debug, Clone, Serialize, Deserialize)]
444#[serde(untagged)]
445pub enum HyperliquidExchangeResponse {
446 Status {
448 status: String,
450 response: serde_json::Value,
452 },
453 Error {
455 error: String,
457 },
458}
459
460#[derive(Debug, Clone, Serialize, Deserialize)]
466#[serde(rename_all = "camelCase")]
467pub struct HyperliquidTriggerOrderParams {
468 #[serde(rename = "isMarket")]
470 pub is_market: bool,
471 #[serde(rename = "triggerPx")]
473 pub trigger_px: String,
474 pub tpsl: HyperliquidTpSl,
476 #[serde(rename = "triggerPxType", skip_serializing_if = "Option::is_none")]
478 pub trigger_px_type: Option<HyperliquidTriggerPriceType>,
479}
480
481#[derive(Debug, Clone, Serialize, Deserialize)]
483#[serde(rename_all = "camelCase")]
484pub struct HyperliquidTrailingStopParams {
485 #[serde(
487 rename = "trailingOffset",
488 serialize_with = "crate::common::parse::serialize_decimal_as_str",
489 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
490 )]
491 pub trailing_offset: Decimal,
492 #[serde(rename = "trailingOffsetType")]
494 pub trailing_offset_type: HyperliquidTrailingOffsetType,
495 #[serde(rename = "activationPx", skip_serializing_if = "Option::is_none")]
497 pub activation_px: Option<String>,
498 pub tpsl: HyperliquidTpSl,
500}
501
502#[derive(Debug, Clone, Serialize, Deserialize)]
504#[serde(rename_all = "camelCase")]
505pub struct HyperliquidPlaceTriggerOrderRequest {
506 #[serde(rename = "a")]
508 pub asset: AssetId,
509 #[serde(rename = "b")]
511 pub is_buy: bool,
512 #[serde(
514 rename = "s",
515 serialize_with = "crate::common::parse::serialize_decimal_as_str",
516 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
517 )]
518 pub sz: Decimal,
519 #[serde(rename = "limitPx", skip_serializing_if = "Option::is_none")]
521 pub limit_px: Option<String>,
522 #[serde(flatten)]
524 pub trigger_params: HyperliquidTriggerOrderParams,
525 #[serde(rename = "reduceOnly", skip_serializing_if = "Option::is_none")]
527 pub reduce_only: Option<bool>,
528 #[serde(skip_serializing_if = "Option::is_none")]
530 pub cloid: Option<Cloid>,
531}
532
533#[derive(Debug, Clone, Serialize, Deserialize)]
535#[serde(rename_all = "camelCase")]
536pub struct HyperliquidModifyTriggerOrderRequest {
537 pub oid: OrderId,
539 #[serde(rename = "a")]
541 pub asset: AssetId,
542 #[serde(rename = "triggerPx")]
544 pub trigger_px: String,
545 #[serde(rename = "limitPx", skip_serializing_if = "Option::is_none")]
547 pub limit_px: Option<String>,
548 #[serde(
550 skip_serializing_if = "Option::is_none",
551 serialize_with = "crate::common::parse::serialize_optional_decimal_as_str",
552 deserialize_with = "crate::common::parse::deserialize_optional_decimal_from_str"
553 )]
554 pub sz: Option<Decimal>,
555}
556
557#[derive(Debug, Clone, Serialize, Deserialize)]
559#[serde(rename_all = "camelCase")]
560pub struct HyperliquidCancelTriggerOrderRequest {
561 #[serde(rename = "a")]
563 pub asset: AssetId,
564 pub oid: OrderId,
566}
567
568#[derive(Debug, Clone, Serialize, Deserialize)]
570#[serde(rename_all = "camelCase")]
571pub struct HyperliquidTriggerOrderStatus {
572 pub oid: OrderId,
574 pub status: String,
576 #[serde(rename = "statusTimestamp")]
578 pub status_timestamp: u64,
579 pub order: HyperliquidTriggerOrderInfo,
581}
582
583#[derive(Debug, Clone, Serialize, Deserialize)]
585#[serde(rename_all = "camelCase")]
586pub struct HyperliquidTriggerOrderInfo {
587 pub coin: Ustr,
589 pub side: HyperliquidSide,
591 #[serde(rename = "limitPx", skip_serializing_if = "Option::is_none")]
593 pub limit_px: Option<String>,
594 #[serde(rename = "triggerPx")]
596 pub trigger_px: String,
597 pub sz: String,
599 #[serde(rename = "isMarket")]
601 pub is_market: bool,
602 pub tpsl: HyperliquidTpSl,
604 pub oid: OrderId,
606 pub timestamp: u64,
608 #[serde(default)]
610 pub triggered: bool,
611 #[serde(rename = "triggerTime", skip_serializing_if = "Option::is_none")]
613 pub trigger_time: Option<u64>,
614}
615
616#[derive(Debug, Clone, Serialize, Deserialize)]
618#[serde(rename_all = "camelCase")]
619pub struct HyperliquidBracketOrderRequest {
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 = "limitPx")]
635 pub limit_px: String,
636 #[serde(rename = "tpTriggerPx")]
638 pub tp_trigger_px: String,
639 #[serde(rename = "tpLimitPx", skip_serializing_if = "Option::is_none")]
641 pub tp_limit_px: Option<String>,
642 #[serde(rename = "tpIsMarket", default)]
644 pub tp_is_market: bool,
645 #[serde(rename = "slTriggerPx")]
647 pub sl_trigger_px: String,
648 #[serde(rename = "slLimitPx", skip_serializing_if = "Option::is_none")]
650 pub sl_limit_px: Option<String>,
651 #[serde(rename = "slIsMarket", default)]
653 pub sl_is_market: bool,
654 #[serde(skip_serializing_if = "Option::is_none")]
656 pub cloid: Option<Cloid>,
657}
658
659#[derive(Debug, Clone, Serialize, Deserialize)]
661#[serde(rename_all = "camelCase")]
662pub struct HyperliquidOcoOrderRequest {
663 #[serde(rename = "a")]
665 pub asset: AssetId,
666 #[serde(rename = "b")]
668 pub is_buy: bool,
669 #[serde(
671 rename = "s",
672 serialize_with = "crate::common::parse::serialize_decimal_as_str",
673 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
674 )]
675 pub sz: Decimal,
676 #[serde(rename = "triggerPx1")]
678 pub trigger_px_1: String,
679 #[serde(rename = "limitPx1", skip_serializing_if = "Option::is_none")]
681 pub limit_px_1: Option<String>,
682 #[serde(rename = "isMarket1", default)]
684 pub is_market_1: bool,
685 #[serde(rename = "tpsl1")]
687 pub tpsl_1: HyperliquidTpSl,
688 #[serde(rename = "triggerPx2")]
690 pub trigger_px_2: String,
691 #[serde(rename = "limitPx2", skip_serializing_if = "Option::is_none")]
693 pub limit_px_2: Option<String>,
694 #[serde(rename = "isMarket2", default)]
696 pub is_market_2: bool,
697 #[serde(rename = "tpsl2")]
699 pub tpsl_2: HyperliquidTpSl,
700 #[serde(rename = "reduceOnly", skip_serializing_if = "Option::is_none")]
702 pub reduce_only: Option<bool>,
703}
704
705#[cfg(test)]
710mod tests {
711 use rstest::rstest;
712
713 use super::*;
714
715 #[rstest]
716 fn test_meta_deserialization() {
717 let json = r#"{"universe": [{"name": "BTC", "szDecimals": 5}]}"#;
718
719 let meta: HyperliquidMeta = serde_json::from_str(json).unwrap();
720
721 assert_eq!(meta.universe.len(), 1);
722 assert_eq!(meta.universe[0].name, "BTC");
723 assert_eq!(meta.universe[0].sz_decimals, 5);
724 }
725
726 #[rstest]
727 fn test_l2_book_deserialization() {
728 let json = r#"{"coin": "BTC", "levels": [[{"px": "50000", "sz": "1.5"}], [{"px": "50100", "sz": "2.0"}]], "time": 1234567890}"#;
729
730 let book: HyperliquidL2Book = serde_json::from_str(json).unwrap();
731
732 assert_eq!(book.coin, "BTC");
733 assert_eq!(book.levels.len(), 2);
734 assert_eq!(book.time, 1234567890);
735 }
736
737 #[rstest]
738 fn test_exchange_response_deserialization() {
739 let json = r#"{"status": "ok", "response": {"type": "order"}}"#;
740
741 let response: HyperliquidExchangeResponse = serde_json::from_str(json).unwrap();
742
743 match response {
744 HyperliquidExchangeResponse::Status { status, .. } => assert_eq!(status, "ok"),
745 _ => panic!("Expected status response"),
746 }
747 }
748}
749
750pub mod execution_cloid {
756 use std::fmt;
757
758 use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error};
759
760 #[derive(Clone, PartialEq, Eq, Hash, Debug)]
762 pub struct Cloid(pub [u8; 16]);
763
764 impl Cloid {
765 pub fn from_hex<S: AsRef<str>>(s: S) -> Result<Self, String> {
771 let hex_str = s.as_ref();
772 let without_prefix = hex_str
773 .strip_prefix("0x")
774 .ok_or("CLOID must start with '0x'")?;
775
776 if without_prefix.len() != 32 {
777 return Err("CLOID must be exactly 32 hex characters (128 bits)".to_string());
778 }
779
780 let mut bytes = [0u8; 16];
781 for i in 0..16 {
782 let byte_str = &without_prefix[i * 2..i * 2 + 2];
783 bytes[i] = u8::from_str_radix(byte_str, 16)
784 .map_err(|_| "Invalid hex character in CLOID".to_string())?;
785 }
786
787 Ok(Self(bytes))
788 }
789
790 pub fn to_hex(&self) -> String {
792 let mut result = String::with_capacity(34);
793 result.push_str("0x");
794 for byte in &self.0 {
795 result.push_str(&format!("{:02x}", byte));
796 }
797 result
798 }
799 }
800
801 impl fmt::Display for Cloid {
802 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
803 write!(f, "{}", self.to_hex())
804 }
805 }
806
807 impl Serialize for Cloid {
808 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
809 where
810 S: Serializer,
811 {
812 serializer.serialize_str(&self.to_hex())
813 }
814 }
815
816 impl<'de> Deserialize<'de> for Cloid {
817 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
818 where
819 D: Deserializer<'de>,
820 {
821 let s = String::deserialize(deserializer)?;
822 Self::from_hex(&s).map_err(D::Error::custom)
823 }
824 }
825}
826
827pub use execution_cloid::Cloid;
828
829pub type AssetId = u32;
834
835pub type OrderId = u64;
837
838#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
842pub enum HyperliquidExecTif {
843 #[serde(rename = "Alo")]
845 Alo,
846 #[serde(rename = "Ioc")]
848 Ioc,
849 #[serde(rename = "Gtc")]
851 Gtc,
852}
853
854#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
856pub enum HyperliquidExecTpSl {
857 #[serde(rename = "tp")]
859 Tp,
860 #[serde(rename = "sl")]
862 Sl,
863}
864
865#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
867pub enum HyperliquidExecGrouping {
868 #[serde(rename = "na")]
870 #[default]
871 Na,
872 #[serde(rename = "normalTpsl")]
874 NormalTpsl,
875 #[serde(rename = "positionTpsl")]
877 PositionTpsl,
878}
879
880#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
882#[serde(untagged)]
883pub enum HyperliquidExecOrderKind {
884 Limit {
886 limit: HyperliquidExecLimitParams,
888 },
889 Trigger {
891 trigger: HyperliquidExecTriggerParams,
893 },
894}
895
896#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
898pub struct HyperliquidExecLimitParams {
899 pub tif: HyperliquidExecTif,
901}
902
903#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
905#[serde(rename_all = "camelCase")]
906pub struct HyperliquidExecTriggerParams {
907 pub is_market: bool,
909 #[serde(
911 serialize_with = "crate::common::parse::serialize_decimal_as_str",
912 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
913 )]
914 pub trigger_px: Decimal,
915 pub tpsl: HyperliquidExecTpSl,
917}
918
919#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
924pub struct HyperliquidExecBuilderFee {
925 #[serde(rename = "b")]
927 pub address: String,
928 #[serde(rename = "f")]
930 pub fee_tenths_bp: u32,
931}
932
933#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
938pub struct HyperliquidExecPlaceOrderRequest {
939 #[serde(rename = "a")]
941 pub asset: AssetId,
942 #[serde(rename = "b")]
944 pub is_buy: bool,
945 #[serde(
947 rename = "p",
948 serialize_with = "crate::common::parse::serialize_decimal_as_str",
949 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
950 )]
951 pub price: Decimal,
952 #[serde(
954 rename = "s",
955 serialize_with = "crate::common::parse::serialize_decimal_as_str",
956 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
957 )]
958 pub size: Decimal,
959 #[serde(rename = "r")]
961 pub reduce_only: bool,
962 #[serde(rename = "t")]
964 pub kind: HyperliquidExecOrderKind,
965 #[serde(rename = "c", skip_serializing_if = "Option::is_none")]
967 pub cloid: Option<Cloid>,
968}
969
970#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
972pub struct HyperliquidExecCancelOrderRequest {
973 #[serde(rename = "a")]
975 pub asset: AssetId,
976 #[serde(rename = "o")]
978 pub oid: OrderId,
979}
980
981#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
983pub struct HyperliquidExecCancelByCloidRequest {
984 #[serde(rename = "a")]
986 pub asset: AssetId,
987 #[serde(rename = "c")]
989 pub cloid: Cloid,
990}
991
992#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
994pub struct HyperliquidExecModifyOrderRequest {
995 #[serde(rename = "a")]
997 pub asset: AssetId,
998 #[serde(rename = "o")]
1000 pub oid: OrderId,
1001 #[serde(
1003 rename = "p",
1004 skip_serializing_if = "Option::is_none",
1005 serialize_with = "crate::common::parse::serialize_optional_decimal_as_str",
1006 deserialize_with = "crate::common::parse::deserialize_optional_decimal_from_str"
1007 )]
1008 pub price: Option<Decimal>,
1009 #[serde(
1011 rename = "s",
1012 skip_serializing_if = "Option::is_none",
1013 serialize_with = "crate::common::parse::serialize_optional_decimal_as_str",
1014 deserialize_with = "crate::common::parse::deserialize_optional_decimal_from_str"
1015 )]
1016 pub size: Option<Decimal>,
1017 #[serde(rename = "r", skip_serializing_if = "Option::is_none")]
1019 pub reduce_only: Option<bool>,
1020 #[serde(rename = "t", skip_serializing_if = "Option::is_none")]
1022 pub kind: Option<HyperliquidExecOrderKind>,
1023}
1024
1025#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1027pub struct HyperliquidExecTwapRequest {
1028 #[serde(rename = "a")]
1030 pub asset: AssetId,
1031 #[serde(rename = "b")]
1033 pub is_buy: bool,
1034 #[serde(
1036 rename = "s",
1037 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1038 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1039 )]
1040 pub size: Decimal,
1041 #[serde(rename = "m")]
1043 pub duration_ms: u64,
1044}
1045
1046#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1052#[serde(tag = "type")]
1053pub enum HyperliquidExecAction {
1054 #[serde(rename = "order")]
1056 Order {
1057 orders: Vec<HyperliquidExecPlaceOrderRequest>,
1059 #[serde(default)]
1061 grouping: HyperliquidExecGrouping,
1062 #[serde(skip_serializing_if = "Option::is_none")]
1064 builder: Option<HyperliquidExecBuilderFee>,
1065 },
1066
1067 #[serde(rename = "cancel")]
1069 Cancel {
1070 cancels: Vec<HyperliquidExecCancelOrderRequest>,
1072 },
1073
1074 #[serde(rename = "cancelByCloid")]
1076 CancelByCloid {
1077 cancels: Vec<HyperliquidExecCancelByCloidRequest>,
1079 },
1080
1081 #[serde(rename = "modify")]
1083 Modify {
1084 #[serde(flatten)]
1086 modify: HyperliquidExecModifyOrderRequest,
1087 },
1088
1089 #[serde(rename = "batchModify")]
1091 BatchModify {
1092 modifies: Vec<HyperliquidExecModifyOrderRequest>,
1094 },
1095
1096 #[serde(rename = "scheduleCancel")]
1098 ScheduleCancel {
1099 #[serde(skip_serializing_if = "Option::is_none")]
1102 time: Option<u64>,
1103 },
1104
1105 #[serde(rename = "updateLeverage")]
1107 UpdateLeverage {
1108 #[serde(rename = "a")]
1110 asset: AssetId,
1111 #[serde(rename = "isCross")]
1113 is_cross: bool,
1114 #[serde(rename = "leverage")]
1116 leverage: u32,
1117 },
1118
1119 #[serde(rename = "updateIsolatedMargin")]
1121 UpdateIsolatedMargin {
1122 #[serde(rename = "a")]
1124 asset: AssetId,
1125 #[serde(
1127 rename = "delta",
1128 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1129 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1130 )]
1131 delta: Decimal,
1132 },
1133
1134 #[serde(rename = "usdClassTransfer")]
1136 UsdClassTransfer {
1137 from: String,
1139 to: String,
1141 #[serde(
1143 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1144 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1145 )]
1146 amount: Decimal,
1147 },
1148
1149 #[serde(rename = "twapPlace")]
1151 TwapPlace {
1152 #[serde(flatten)]
1154 twap: HyperliquidExecTwapRequest,
1155 },
1156
1157 #[serde(rename = "twapCancel")]
1159 TwapCancel {
1160 #[serde(rename = "a")]
1162 asset: AssetId,
1163 #[serde(rename = "t")]
1165 twap_id: u64,
1166 },
1167
1168 #[serde(rename = "noop")]
1170 Noop,
1171}
1172
1173#[derive(Debug, Clone, Serialize)]
1178#[serde(rename_all = "camelCase")]
1179pub struct HyperliquidExecRequest {
1180 pub action: HyperliquidExecAction,
1182 pub nonce: u64,
1184 pub signature: String,
1186 #[serde(skip_serializing_if = "Option::is_none")]
1188 pub vault_address: Option<String>,
1189 #[serde(skip_serializing_if = "Option::is_none")]
1192 pub expires_after: Option<u64>,
1193}
1194
1195#[derive(Debug, Clone, Serialize, Deserialize)]
1197pub struct HyperliquidExecResponse {
1198 pub status: String,
1200 pub response: HyperliquidExecResponseData,
1202}
1203
1204#[derive(Debug, Clone, Serialize, Deserialize)]
1206#[serde(tag = "type")]
1207pub enum HyperliquidExecResponseData {
1208 #[serde(rename = "order")]
1210 Order {
1211 data: HyperliquidExecOrderResponseData,
1213 },
1214 #[serde(rename = "cancel")]
1216 Cancel {
1217 data: HyperliquidExecCancelResponseData,
1219 },
1220 #[serde(rename = "modify")]
1222 Modify {
1223 data: HyperliquidExecModifyResponseData,
1225 },
1226 #[serde(rename = "default")]
1228 Default,
1229 #[serde(other)]
1231 Unknown,
1232}
1233
1234#[derive(Debug, Clone, Serialize, Deserialize)]
1236pub struct HyperliquidExecOrderResponseData {
1237 pub statuses: Vec<HyperliquidExecOrderStatus>,
1239}
1240
1241#[derive(Debug, Clone, Serialize, Deserialize)]
1243pub struct HyperliquidExecCancelResponseData {
1244 pub statuses: Vec<HyperliquidExecCancelStatus>,
1246}
1247
1248#[derive(Debug, Clone, Serialize, Deserialize)]
1250pub struct HyperliquidExecModifyResponseData {
1251 pub statuses: Vec<HyperliquidExecModifyStatus>,
1253}
1254
1255#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1257#[serde(untagged)]
1258pub enum HyperliquidExecOrderStatus {
1259 Resting {
1261 resting: HyperliquidExecRestingInfo,
1263 },
1264 Filled {
1266 filled: HyperliquidExecFilledInfo,
1268 },
1269 Error {
1271 error: String,
1273 },
1274}
1275
1276#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1278pub struct HyperliquidExecRestingInfo {
1279 pub oid: OrderId,
1281}
1282
1283#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1285pub struct HyperliquidExecFilledInfo {
1286 #[serde(
1288 rename = "totalSz",
1289 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1290 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1291 )]
1292 pub total_sz: Decimal,
1293 #[serde(
1295 rename = "avgPx",
1296 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1297 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1298 )]
1299 pub avg_px: Decimal,
1300 pub oid: OrderId,
1302}
1303
1304#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1306#[serde(untagged)]
1307pub enum HyperliquidExecCancelStatus {
1308 Success(String), Error {
1312 error: String,
1314 },
1315}
1316
1317#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1319#[serde(untagged)]
1320pub enum HyperliquidExecModifyStatus {
1321 Success(String), Error {
1325 error: String,
1327 },
1328}
1329
1330#[derive(Debug, Clone, Serialize, Deserialize)]
1333#[serde(rename_all = "camelCase")]
1334pub struct ClearinghouseState {
1335 #[serde(default)]
1337 pub asset_positions: Vec<AssetPosition>,
1338 #[serde(default)]
1340 pub cross_margin_summary: Option<CrossMarginSummary>,
1341 #[serde(default)]
1343 pub time: Option<u64>,
1344}
1345
1346#[derive(Debug, Clone, Serialize, Deserialize)]
1348#[serde(rename_all = "camelCase")]
1349pub struct AssetPosition {
1350 pub position: PositionData,
1352 #[serde(rename = "type")]
1354 pub position_type: String,
1355}
1356
1357#[derive(Debug, Clone, Serialize, Deserialize)]
1359#[serde(rename_all = "camelCase")]
1360pub struct PositionData {
1361 pub coin: String,
1363 #[serde(
1365 rename = "cumFunding",
1366 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1367 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1368 )]
1369 pub cum_funding: Decimal,
1370 #[serde(
1372 rename = "entryPx",
1373 serialize_with = "crate::common::parse::serialize_optional_decimal_as_str",
1374 deserialize_with = "crate::common::parse::deserialize_optional_decimal_from_str",
1375 default
1376 )]
1377 pub entry_px: Option<Decimal>,
1378 #[serde(
1380 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1381 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1382 )]
1383 pub leverage: Decimal,
1384 #[serde(
1386 rename = "liquidationPx",
1387 serialize_with = "crate::common::parse::serialize_optional_decimal_as_str",
1388 deserialize_with = "crate::common::parse::deserialize_optional_decimal_from_str",
1389 default
1390 )]
1391 pub liquidation_px: Option<Decimal>,
1392 #[serde(
1394 rename = "marginUsed",
1395 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1396 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1397 )]
1398 pub margin_used: Decimal,
1399 #[serde(
1401 rename = "maxTradeSzs",
1402 serialize_with = "crate::common::parse::serialize_vec_decimal_as_str",
1403 deserialize_with = "crate::common::parse::deserialize_vec_decimal_from_str"
1404 )]
1405 pub max_trade_szs: Vec<Decimal>,
1406 #[serde(
1408 rename = "positionValue",
1409 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1410 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1411 )]
1412 pub position_value: Decimal,
1413 #[serde(
1415 rename = "returnOnEquity",
1416 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1417 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1418 )]
1419 pub return_on_equity: Decimal,
1420 #[serde(
1422 rename = "szi",
1423 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1424 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1425 )]
1426 pub szi: Decimal,
1427 #[serde(
1429 rename = "unrealizedPnl",
1430 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1431 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1432 )]
1433 pub unrealized_pnl: Decimal,
1434}
1435
1436#[derive(Debug, Clone, Serialize, Deserialize)]
1438#[serde(rename_all = "camelCase")]
1439pub struct CrossMarginSummary {
1440 #[serde(
1442 rename = "accountValue",
1443 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1444 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1445 )]
1446 pub account_value: Decimal,
1447 #[serde(
1449 rename = "totalNtlPos",
1450 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1451 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1452 )]
1453 pub total_ntl_pos: Decimal,
1454 #[serde(
1456 rename = "totalRawUsd",
1457 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1458 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1459 )]
1460 pub total_raw_usd: Decimal,
1461 #[serde(
1463 rename = "totalMarginUsed",
1464 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1465 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1466 )]
1467 pub total_margin_used: Decimal,
1468 #[serde(
1470 rename = "withdrawable",
1471 serialize_with = "crate::common::parse::serialize_decimal_as_str",
1472 deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
1473 )]
1474 pub withdrawable: Decimal,
1475}