1use alloy::primitives::{Address, B128, U256};
6use alloy::sol_types::{eip712_domain, Eip712Domain};
7use either::Either;
8use rust_decimal::Decimal;
9use serde::{Deserialize, Serialize};
10use std::fmt;
11use std::str::FromStr;
12
13pub type Cloid = B128;
19
20pub type OidOrCloid = Either<u64, Cloid>;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
29#[serde(rename_all = "PascalCase")]
30pub enum Chain {
31 #[default]
32 Mainnet,
33 Testnet,
34}
35
36impl Chain {
37 pub fn is_mainnet(&self) -> bool {
39 matches!(self, Chain::Mainnet)
40 }
41
42 pub fn as_str(&self) -> &'static str {
44 match self {
45 Chain::Mainnet => "Mainnet",
46 Chain::Testnet => "Testnet",
47 }
48 }
49
50 pub fn signature_chain_id(&self) -> &'static str {
52 match self {
53 Chain::Mainnet => "0xa4b1", Chain::Testnet => "0x66eee", }
56 }
57
58 pub fn evm_chain_id(&self) -> u64 {
60 match self {
61 Chain::Mainnet => 999,
62 Chain::Testnet => 998,
63 }
64 }
65}
66
67impl fmt::Display for Chain {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 match self {
70 Chain::Mainnet => write!(f, "Mainnet"),
71 Chain::Testnet => write!(f, "Testnet"),
72 }
73 }
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
82#[serde(rename_all = "lowercase")]
83pub enum Side {
84 Buy,
85 Sell,
86}
87
88impl Side {
89 pub fn is_buy(&self) -> bool {
91 matches!(self, Side::Buy)
92 }
93
94 pub fn as_bool(&self) -> bool {
96 self.is_buy()
97 }
98}
99
100impl fmt::Display for Side {
101 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102 match self {
103 Side::Buy => write!(f, "buy"),
104 Side::Sell => write!(f, "sell"),
105 }
106 }
107}
108
109impl FromStr for Side {
110 type Err = String;
111
112 fn from_str(s: &str) -> Result<Self, Self::Err> {
113 match s.to_lowercase().as_str() {
114 "buy" | "b" | "long" => Ok(Side::Buy),
115 "sell" | "s" | "short" => Ok(Side::Sell),
116 _ => Err(format!("invalid side: {}", s)),
117 }
118 }
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
127#[serde(rename_all = "lowercase")]
128pub enum TIF {
129 #[default]
131 Ioc,
132 Gtc,
134 Alo,
136 Market,
138}
139
140impl fmt::Display for TIF {
141 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142 match self {
143 TIF::Ioc => write!(f, "ioc"),
144 TIF::Gtc => write!(f, "gtc"),
145 TIF::Alo => write!(f, "alo"),
146 TIF::Market => write!(f, "market"),
147 }
148 }
149}
150
151impl FromStr for TIF {
152 type Err = String;
153
154 fn from_str(s: &str) -> Result<Self, Self::Err> {
155 match s.to_lowercase().as_str() {
156 "ioc" => Ok(TIF::Ioc),
157 "gtc" => Ok(TIF::Gtc),
158 "alo" | "post_only" => Ok(TIF::Alo),
159 "market" => Ok(TIF::Market),
160 _ => Err(format!("invalid tif: {}", s)),
161 }
162 }
163}
164
165#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
171pub enum TimeInForce {
172 Alo,
173 Ioc,
174 Gtc,
175 FrontendMarket,
176}
177
178impl From<TIF> for TimeInForce {
179 fn from(tif: TIF) -> Self {
180 match tif {
181 TIF::Ioc => TimeInForce::Ioc,
182 TIF::Gtc => TimeInForce::Gtc,
183 TIF::Alo => TimeInForce::Alo,
184 TIF::Market => TimeInForce::Ioc, }
186 }
187}
188
189#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
195#[serde(rename_all = "lowercase")]
196pub enum TpSl {
197 Tp,
199 Sl,
201}
202
203impl fmt::Display for TpSl {
204 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205 match self {
206 TpSl::Tp => write!(f, "tp"),
207 TpSl::Sl => write!(f, "sl"),
208 }
209 }
210}
211
212#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
218#[serde(rename_all = "camelCase")]
219pub enum OrderGrouping {
220 #[default]
222 Na,
223 NormalTpsl,
225 PositionTpsl,
227}
228
229impl fmt::Display for OrderGrouping {
230 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231 match self {
232 OrderGrouping::Na => write!(f, "na"),
233 OrderGrouping::NormalTpsl => write!(f, "normalTpsl"),
234 OrderGrouping::PositionTpsl => write!(f, "positionTpsl"),
235 }
236 }
237}
238
239pub const CORE_MAINNET_EIP712_DOMAIN: Eip712Domain = eip712_domain! {
245 name: "Exchange",
246 version: "1",
247 chain_id: 1337,
248 verifying_contract: Address::ZERO,
249};
250
251#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
257pub struct Signature {
258 #[serde(
259 serialize_with = "serialize_u256_hex",
260 deserialize_with = "deserialize_u256_hex"
261 )]
262 pub r: U256,
263 #[serde(
264 serialize_with = "serialize_u256_hex",
265 deserialize_with = "deserialize_u256_hex"
266 )]
267 pub s: U256,
268 pub v: u64,
269}
270
271impl From<alloy::signers::Signature> for Signature {
272 fn from(sig: alloy::signers::Signature) -> Self {
273 Self {
274 r: sig.r(),
275 s: sig.s(),
276 v: if sig.v() { 28 } else { 27 },
277 }
278 }
279}
280
281#[derive(Debug, Clone, Serialize, Deserialize)]
287#[serde(rename_all = "camelCase")]
288pub enum OrderTypePlacement {
289 Limit {
291 tif: TimeInForce,
292 },
293 #[serde(rename_all = "camelCase")]
295 Trigger {
296 is_market: bool,
297 #[serde(with = "decimal_normalized")]
298 trigger_px: Decimal,
299 tpsl: TpSl,
300 },
301}
302
303#[derive(Debug, Clone, Serialize, Deserialize)]
309#[serde(rename_all = "camelCase")]
310pub struct OrderRequest {
311 #[serde(rename = "a")]
313 pub asset: usize,
314 #[serde(rename = "b")]
316 pub is_buy: bool,
317 #[serde(rename = "p", with = "decimal_normalized")]
319 pub limit_px: Decimal,
320 #[serde(rename = "s", with = "decimal_normalized")]
322 pub sz: Decimal,
323 #[serde(rename = "r")]
325 pub reduce_only: bool,
326 #[serde(rename = "t")]
328 pub order_type: OrderTypePlacement,
329 #[serde(
331 rename = "c",
332 serialize_with = "serialize_cloid_hex",
333 deserialize_with = "deserialize_cloid_hex"
334 )]
335 pub cloid: Cloid,
336}
337
338#[derive(Debug, Clone, Serialize, Deserialize)]
344#[serde(rename_all = "camelCase")]
345pub struct BatchOrder {
346 pub orders: Vec<OrderRequest>,
347 pub grouping: OrderGrouping,
348}
349
350#[derive(Debug, Clone, Serialize, Deserialize)]
356#[serde(rename_all = "camelCase")]
357pub struct Modify {
358 #[serde(with = "oid_or_cloid")]
359 pub oid: OidOrCloid,
360 pub order: OrderRequest,
361}
362
363#[derive(Debug, Clone, Serialize, Deserialize)]
365#[serde(rename_all = "camelCase")]
366pub struct BatchModify {
367 pub modifies: Vec<Modify>,
368}
369
370#[derive(Debug, Clone, Serialize, Deserialize)]
376#[serde(rename_all = "camelCase")]
377pub struct Cancel {
378 #[serde(rename = "a")]
379 pub asset: usize,
380 #[serde(rename = "o")]
381 pub oid: u64,
382}
383
384#[derive(Debug, Clone, Serialize, Deserialize)]
386#[serde(rename_all = "camelCase")]
387pub struct BatchCancel {
388 pub cancels: Vec<Cancel>,
389}
390
391#[derive(Debug, Clone, Serialize, Deserialize)]
393#[serde(rename_all = "camelCase")]
394pub struct CancelByCloid {
395 pub asset: u32,
396 #[serde(with = "const_hex_b128")]
397 pub cloid: B128,
398}
399
400#[derive(Debug, Clone, Serialize, Deserialize)]
402#[serde(rename_all = "camelCase")]
403pub struct BatchCancelCloid {
404 pub cancels: Vec<CancelByCloid>,
405}
406
407#[derive(Debug, Clone, Serialize, Deserialize)]
409#[serde(rename_all = "camelCase")]
410pub struct ScheduleCancel {
411 pub time: Option<u64>,
412}
413
414#[derive(Debug, Clone, Serialize, Deserialize)]
420#[serde(rename_all = "camelCase")]
421pub struct TwapSpec {
422 #[serde(rename = "a")]
423 pub asset: String,
424 #[serde(rename = "b")]
425 pub is_buy: bool,
426 #[serde(rename = "s")]
427 pub sz: String,
428 #[serde(rename = "r")]
429 pub reduce_only: bool,
430 #[serde(rename = "m")]
431 pub duration_minutes: i64,
432 #[serde(rename = "t")]
433 pub randomize: bool,
434}
435
436#[derive(Debug, Clone, Serialize, Deserialize)]
438#[serde(rename_all = "camelCase")]
439pub struct TwapOrder {
440 pub twap: TwapSpec,
441}
442
443#[derive(Debug, Clone, Serialize, Deserialize)]
445#[serde(rename_all = "camelCase")]
446pub struct TwapCancel {
447 #[serde(rename = "a")]
448 pub asset: String,
449 #[serde(rename = "t")]
450 pub twap_id: i64,
451}
452
453#[derive(Debug, Clone, Serialize, Deserialize)]
459#[serde(rename_all = "camelCase")]
460pub struct UpdateLeverage {
461 pub asset: u32,
462 pub is_cross: bool,
463 pub leverage: i32,
464}
465
466#[derive(Debug, Clone, Serialize, Deserialize)]
468#[serde(rename_all = "camelCase")]
469pub struct UpdateIsolatedMargin {
470 pub asset: u32,
471 pub is_buy: bool,
472 pub ntli: i64,
473}
474
475#[derive(Debug, Clone, Serialize, Deserialize)]
477#[serde(rename_all = "camelCase")]
478pub struct TopUpIsolatedOnlyMargin {
479 pub asset: u32,
480 pub leverage: String,
481}
482
483#[derive(Debug, Clone, Serialize, Deserialize)]
489#[serde(rename_all = "camelCase")]
490pub struct UsdSend {
491 pub hyperliquid_chain: Chain,
492 pub signature_chain_id: String,
493 pub destination: String,
494 pub amount: String,
495 pub time: u64,
496}
497
498#[derive(Debug, Clone, Serialize, Deserialize)]
500#[serde(rename_all = "camelCase")]
501pub struct SpotSend {
502 pub hyperliquid_chain: Chain,
503 pub signature_chain_id: String,
504 pub token: String,
505 pub destination: String,
506 pub amount: String,
507 pub time: u64,
508}
509
510#[derive(Debug, Clone, Serialize, Deserialize)]
512#[serde(rename_all = "camelCase")]
513pub struct Withdraw3 {
514 pub hyperliquid_chain: Chain,
515 pub signature_chain_id: String,
516 pub destination: String,
517 pub amount: String,
518 pub time: u64,
519}
520
521#[derive(Debug, Clone, Serialize, Deserialize)]
523#[serde(rename_all = "camelCase")]
524pub struct UsdClassTransfer {
525 pub hyperliquid_chain: Chain,
526 pub signature_chain_id: String,
527 pub amount: String,
528 pub to_perp: bool,
529 pub nonce: u64,
530}
531
532#[derive(Debug, Clone, Serialize, Deserialize)]
534#[serde(rename_all = "camelCase")]
535pub struct SendAsset {
536 pub hyperliquid_chain: Chain,
537 pub signature_chain_id: String,
538 pub destination: String,
539 pub source_dex: String,
540 pub destination_dex: String,
541 pub token: String,
542 pub amount: String,
543 pub from_sub_account: String,
544 pub nonce: u64,
545}
546
547#[derive(Debug, Clone, Serialize, Deserialize)]
553#[serde(rename_all = "camelCase")]
554pub struct VaultTransfer {
555 pub vault_address: String,
556 pub is_deposit: bool,
557 pub usd: f64,
558}
559
560#[derive(Debug, Clone, Serialize, Deserialize)]
566#[serde(rename_all = "camelCase")]
567pub struct ApproveAgent {
568 pub hyperliquid_chain: Chain,
569 pub signature_chain_id: String,
570 pub agent_address: String,
571 pub agent_name: Option<String>,
572 pub nonce: u64,
573}
574
575#[derive(Debug, Clone, Serialize, Deserialize)]
577#[serde(rename_all = "camelCase")]
578pub struct ApproveBuilderFee {
579 pub hyperliquid_chain: Chain,
580 pub signature_chain_id: String,
581 pub max_fee_rate: String,
582 pub builder: String,
583 pub nonce: u64,
584}
585
586#[derive(Debug, Clone, Serialize, Deserialize)]
592#[serde(rename_all = "camelCase")]
593pub struct UserSetAbstraction {
594 pub hyperliquid_chain: Chain,
595 pub signature_chain_id: String,
596 pub user: String,
597 pub abstraction: String,
598 pub nonce: u64,
599}
600
601#[derive(Debug, Clone, Serialize, Deserialize)]
603#[serde(rename_all = "camelCase")]
604pub struct AgentSetAbstraction {
605 pub abstraction: String,
606}
607
608#[derive(Debug, Clone, Serialize, Deserialize)]
614#[serde(rename_all = "camelCase")]
615pub struct CDeposit {
616 pub hyperliquid_chain: Chain,
617 pub signature_chain_id: String,
618 pub wei: u128,
619 pub nonce: u64,
620}
621
622#[derive(Debug, Clone, Serialize, Deserialize)]
624#[serde(rename_all = "camelCase")]
625pub struct CWithdraw {
626 pub hyperliquid_chain: Chain,
627 pub signature_chain_id: String,
628 pub wei: u128,
629 pub nonce: u64,
630}
631
632#[derive(Debug, Clone, Serialize, Deserialize)]
634#[serde(rename_all = "camelCase")]
635pub struct TokenDelegate {
636 pub hyperliquid_chain: Chain,
637 pub signature_chain_id: String,
638 pub validator: String,
639 pub is_undelegate: bool,
640 pub wei: u128,
641 pub nonce: u64,
642}
643
644#[derive(Debug, Clone, Serialize, Deserialize)]
650#[serde(rename_all = "camelCase")]
651pub struct ReserveRequestWeight {
652 pub weight: i32,
653}
654
655#[derive(Debug, Clone, Serialize, Deserialize)]
657#[serde(rename_all = "camelCase")]
658pub struct Noop {}
659
660#[derive(Debug, Clone, Serialize, Deserialize)]
662#[serde(rename_all = "camelCase")]
663pub struct ValidatorL1Stream {
664 pub risk_free_rate: String,
665}
666
667#[derive(Debug, Clone, Serialize, Deserialize)]
669#[serde(rename_all = "camelCase")]
670pub struct ClosePosition {
671 pub asset: String,
672 pub user: String,
673}
674
675#[derive(Debug, Clone, Serialize, Deserialize)]
681#[serde(tag = "type")]
682#[serde(rename_all = "camelCase")]
683pub enum Action {
684 Order(BatchOrder),
686 BatchModify(BatchModify),
687
688 Cancel(BatchCancel),
690 CancelByCloid(BatchCancelCloid),
691 ScheduleCancel(ScheduleCancel),
692
693 TwapOrder(TwapOrder),
695 TwapCancel(TwapCancel),
696
697 UpdateLeverage(UpdateLeverage),
699 UpdateIsolatedMargin(UpdateIsolatedMargin),
700 TopUpIsolatedOnlyMargin(TopUpIsolatedOnlyMargin),
701
702 UsdSend(UsdSend),
704 SpotSend(SpotSend),
705 Withdraw3(Withdraw3),
706 UsdClassTransfer(UsdClassTransfer),
707 SendAsset(SendAsset),
708
709 VaultTransfer(VaultTransfer),
711
712 ApproveAgent(ApproveAgent),
714 ApproveBuilderFee(ApproveBuilderFee),
715
716 UserSetAbstraction(UserSetAbstraction),
718 AgentSetAbstraction(AgentSetAbstraction),
719
720 CDeposit(CDeposit),
722 CWithdraw(CWithdraw),
723 TokenDelegate(TokenDelegate),
724
725 ReserveRequestWeight(ReserveRequestWeight),
727
728 Noop(Noop),
730
731 ValidatorL1Stream(ValidatorL1Stream),
733
734 ClosePosition(ClosePosition),
736}
737
738impl Action {
739 pub fn hash(
741 &self,
742 nonce: u64,
743 vault_address: Option<Address>,
744 expires_after: Option<u64>,
745 ) -> Result<alloy::primitives::B256, rmp_serde::encode::Error> {
746 crate::signing::rmp_hash(self, nonce, vault_address, expires_after)
747 }
748}
749
750#[derive(Debug, Clone, Serialize, Deserialize)]
756#[serde(rename_all = "camelCase")]
757pub struct ActionRequest {
758 pub action: Action,
759 pub nonce: u64,
760 pub signature: Signature,
761 #[serde(skip_serializing_if = "Option::is_none")]
762 pub vault_address: Option<Address>,
763 #[serde(skip_serializing_if = "Option::is_none")]
764 pub expires_after: Option<u64>,
765}
766
767#[derive(Debug, Clone, Serialize, Deserialize)]
773pub struct Builder {
774 #[serde(rename = "b")]
776 pub address: String,
777 #[serde(rename = "f")]
779 pub fee: u16,
780}
781
782pub mod decimal_normalized {
788 use rust_decimal::Decimal;
789 use serde::{de, Deserialize, Deserializer, Serializer};
790 use std::str::FromStr;
791
792 pub fn serialize<S>(value: &Decimal, serializer: S) -> Result<S::Ok, S::Error>
793 where
794 S: Serializer,
795 {
796 let normalized = value.normalize();
797 serializer.serialize_str(&normalized.to_string())
798 }
799
800 pub fn deserialize<'de, D>(deserializer: D) -> Result<Decimal, D::Error>
801 where
802 D: Deserializer<'de>,
803 {
804 let s = String::deserialize(deserializer)?;
805 Decimal::from_str(&s)
806 .map(|d| d.normalize())
807 .map_err(de::Error::custom)
808 }
809}
810
811pub mod oid_or_cloid {
813 use super::Cloid;
814 use either::Either;
815 use serde::{de, Deserializer, Serializer};
816
817 pub fn serialize<S>(value: &Either<u64, Cloid>, serializer: S) -> Result<S::Ok, S::Error>
818 where
819 S: Serializer,
820 {
821 match value {
822 Either::Left(oid) => serializer.serialize_u64(*oid),
823 Either::Right(cloid) => serializer.serialize_str(&format!("{:#x}", cloid)),
824 }
825 }
826
827 pub fn deserialize<'de, D>(deserializer: D) -> Result<Either<u64, Cloid>, D::Error>
828 where
829 D: Deserializer<'de>,
830 {
831 struct Visitor;
832
833 impl<'de> serde::de::Visitor<'de> for Visitor {
834 type Value = Either<u64, Cloid>;
835
836 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
837 f.write_str("a u64 oid or a hex string cloid")
838 }
839
840 fn visit_u64<E: de::Error>(self, v: u64) -> Result<Self::Value, E> {
841 Ok(Either::Left(v))
842 }
843
844 fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
845 v.parse::<Cloid>().map(Either::Right).map_err(de::Error::custom)
846 }
847 }
848
849 deserializer.deserialize_any(Visitor)
850 }
851}
852
853pub mod const_hex_b128 {
855 use alloy::primitives::B128;
856 use serde::{Deserialize, Deserializer, Serializer};
857
858 pub fn serialize<S>(value: &B128, serializer: S) -> Result<S::Ok, S::Error>
859 where
860 S: Serializer,
861 {
862 serializer.serialize_str(&format!("{:#x}", value))
863 }
864
865 pub fn deserialize<'de, D>(deserializer: D) -> Result<B128, D::Error>
866 where
867 D: Deserializer<'de>,
868 {
869 let s = String::deserialize(deserializer)?;
870 s.parse::<B128>().map_err(serde::de::Error::custom)
871 }
872}
873
874fn serialize_cloid_hex<S>(value: &Cloid, serializer: S) -> Result<S::Ok, S::Error>
875where
876 S: serde::Serializer,
877{
878 serializer.serialize_str(&format!("{:#x}", value))
879}
880
881fn deserialize_cloid_hex<'de, D>(deserializer: D) -> Result<Cloid, D::Error>
882where
883 D: serde::Deserializer<'de>,
884{
885 let s = String::deserialize(deserializer)?;
886 s.parse::<Cloid>().map_err(serde::de::Error::custom)
887}
888
889fn serialize_u256_hex<S>(value: &U256, serializer: S) -> Result<S::Ok, S::Error>
890where
891 S: serde::Serializer,
892{
893 serializer.serialize_str(&format!("{:#x}", value))
894}
895
896fn deserialize_u256_hex<'de, D>(deserializer: D) -> Result<U256, D::Error>
897where
898 D: serde::Deserializer<'de>,
899{
900 let s = String::deserialize(deserializer)?;
901 let s = s.strip_prefix("0x").unwrap_or(&s);
902 U256::from_str_radix(s, 16).map_err(serde::de::Error::custom)
903}