use std::fmt::Display;
use alloy_primitives::{Address, keccak256};
use nautilus_model::identifiers::ClientOrderId;
use rust_decimal::Decimal;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use ustr::Ustr;
use crate::common::enums::{
HyperliquidFillDirection, HyperliquidLeverageType,
HyperliquidOrderStatus as HyperliquidOrderStatusEnum, HyperliquidPositionType, HyperliquidSide,
};
pub type HyperliquidCandleSnapshot = Vec<HyperliquidCandle>;
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct Cloid(pub [u8; 16]);
impl Cloid {
pub fn from_hex<S: AsRef<str>>(s: S) -> Result<Self, String> {
let hex_str = s.as_ref();
let without_prefix = hex_str
.strip_prefix("0x")
.ok_or("CLOID must start with '0x'")?;
if without_prefix.len() != 32 {
return Err("CLOID must be exactly 32 hex characters (128 bits)".to_string());
}
let mut bytes = [0u8; 16];
for i in 0..16 {
let byte_str = &without_prefix[i * 2..i * 2 + 2];
bytes[i] = u8::from_str_radix(byte_str, 16)
.map_err(|_| "Invalid hex character in CLOID".to_string())?;
}
Ok(Self(bytes))
}
#[must_use]
pub fn from_client_order_id(client_order_id: ClientOrderId) -> Self {
let hash = keccak256(client_order_id.as_str().as_bytes());
let mut bytes = [0u8; 16];
bytes.copy_from_slice(&hash[..16]);
Self(bytes)
}
pub fn to_hex(&self) -> String {
let mut result = String::with_capacity(34);
result.push_str("0x");
for byte in &self.0 {
result.push_str(&format!("{byte:02x}"));
}
result
}
}
impl Display for Cloid {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_hex())
}
}
impl Serialize for Cloid {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_hex())
}
}
impl<'de> Deserialize<'de> for Cloid {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Self::from_hex(&s).map_err(serde::de::Error::custom)
}
}
pub type AssetId = u32;
pub type OrderId = u64;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct HyperliquidAssetInfo {
pub name: Ustr,
pub sz_decimals: u32,
#[serde(default)]
pub max_leverage: Option<u32>,
#[serde(default)]
pub only_isolated: Option<bool>,
#[serde(default)]
pub is_delisted: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PerpMeta {
pub universe: Vec<PerpAsset>,
#[serde(default)]
pub margin_tables: Vec<(u32, MarginTable)>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PerpAsset {
pub name: String,
pub sz_decimals: u32,
#[serde(default)]
pub max_leverage: Option<u32>,
#[serde(default)]
pub only_isolated: Option<bool>,
#[serde(default)]
pub is_delisted: Option<bool>,
#[serde(default)]
pub growth_mode: Option<String>,
#[serde(default)]
pub margin_mode: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MarginTable {
pub description: String,
#[serde(default)]
pub margin_tiers: Vec<MarginTier>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MarginTier {
pub lower_bound: String,
pub max_leverage: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SpotMeta {
pub tokens: Vec<SpotToken>,
pub universe: Vec<SpotPair>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct EvmContract {
pub address: Address,
pub evm_extra_wei_decimals: i32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SpotToken {
pub name: String,
pub sz_decimals: u32,
pub wei_decimals: u32,
pub index: u32,
pub token_id: String,
pub is_canonical: bool,
#[serde(default)]
pub evm_contract: Option<EvmContract>,
#[serde(default)]
pub full_name: Option<String>,
#[serde(default)]
pub deployer_trading_fee_share: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SpotPair {
pub name: String,
pub tokens: [u32; 2],
pub index: u32,
pub is_canonical: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum PerpMetaAndCtxs {
Payload(Box<(PerpMeta, Vec<PerpAssetCtx>)>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PerpAssetCtx {
#[serde(default)]
pub mark_px: Option<String>,
#[serde(default)]
pub mid_px: Option<String>,
#[serde(default)]
pub funding: Option<String>,
#[serde(default)]
pub open_interest: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum SpotMetaAndCtxs {
Payload(Box<(SpotMeta, Vec<SpotAssetCtx>)>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SpotAssetCtx {
#[serde(default)]
pub mark_px: Option<String>,
#[serde(default)]
pub mid_px: Option<String>,
#[serde(default)]
pub day_volume: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HyperliquidL2Book {
pub coin: Ustr,
pub levels: Vec<Vec<HyperliquidLevel>>,
pub time: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HyperliquidLevel {
pub px: String,
pub sz: String,
}
pub type HyperliquidFills = Vec<HyperliquidFill>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HyperliquidMeta {
#[serde(default)]
pub universe: Vec<HyperliquidAssetInfo>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct HyperliquidCandle {
#[serde(rename = "t")]
pub timestamp: u64,
#[serde(rename = "T")]
pub end_timestamp: u64,
#[serde(rename = "o")]
pub open: String,
#[serde(rename = "h")]
pub high: String,
#[serde(rename = "l")]
pub low: String,
#[serde(rename = "c")]
pub close: String,
#[serde(rename = "v")]
pub volume: String,
#[serde(rename = "n", default)]
pub num_trades: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HyperliquidFill {
pub coin: Ustr,
pub px: String,
pub sz: String,
pub side: HyperliquidSide,
pub time: u64,
#[serde(rename = "startPosition")]
pub start_position: String,
pub dir: HyperliquidFillDirection,
#[serde(rename = "closedPnl")]
pub closed_pnl: String,
pub hash: String,
pub oid: u64,
pub crossed: bool,
pub fee: String,
#[serde(rename = "feeToken")]
pub fee_token: Ustr,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HyperliquidOrderStatus {
#[serde(default)]
pub statuses: Vec<HyperliquidOrderStatusEntry>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HyperliquidOrderStatusEntry {
pub order: HyperliquidOrderInfo,
pub status: HyperliquidOrderStatusEnum,
#[serde(rename = "statusTimestamp")]
pub status_timestamp: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HyperliquidOrderInfo {
pub coin: Ustr,
pub side: HyperliquidSide,
#[serde(rename = "limitPx")]
pub limit_px: String,
pub sz: String,
pub oid: u64,
pub timestamp: u64,
#[serde(rename = "origSz")]
pub orig_sz: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct HyperliquidSignature {
pub r: String,
pub s: String,
pub v: u64,
}
impl HyperliquidSignature {
pub fn from_hex(sig_hex: &str) -> Result<Self, String> {
let sig_hex = sig_hex.strip_prefix("0x").unwrap_or(sig_hex);
if sig_hex.len() != 130 {
return Err(format!(
"Invalid signature length: expected 130 hex chars, was {}",
sig_hex.len()
));
}
let r = format!("0x{}", &sig_hex[0..64]);
let s = format!("0x{}", &sig_hex[64..128]);
let v = u64::from_str_radix(&sig_hex[128..130], 16)
.map_err(|e| format!("Failed to parse v component: {e}"))?;
Ok(Self { r, s, v })
}
}
#[derive(Debug, Clone, Serialize)]
pub struct HyperliquidExchangeRequest<T> {
#[serde(rename = "action")]
pub action: T,
#[serde(rename = "nonce")]
pub nonce: u64,
#[serde(rename = "signature")]
pub signature: HyperliquidSignature,
#[serde(rename = "vaultAddress", skip_serializing_if = "Option::is_none")]
pub vault_address: Option<String>,
#[serde(rename = "expiresAfter", skip_serializing_if = "Option::is_none")]
pub expires_after: Option<u64>,
}
impl<T> HyperliquidExchangeRequest<T>
where
T: Serialize,
{
pub fn new(action: T, nonce: u64, signature: &str) -> Result<Self, String> {
Ok(Self {
action,
nonce,
signature: HyperliquidSignature::from_hex(signature)?,
vault_address: None,
expires_after: None,
})
}
pub fn with_vault(
action: T,
nonce: u64,
signature: &str,
vault_address: String,
) -> Result<Self, String> {
Ok(Self {
action,
nonce,
signature: HyperliquidSignature::from_hex(signature)?,
vault_address: Some(vault_address),
expires_after: None,
})
}
pub fn to_sign_value(&self) -> serde_json::Result<serde_json::Value> {
serde_json::to_value(self)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum HyperliquidExchangeResponse {
Status {
status: String,
response: serde_json::Value,
},
Error {
error: String,
},
}
impl HyperliquidExchangeResponse {
pub fn is_ok(&self) -> bool {
matches!(self, Self::Status { status, .. } if status == RESPONSE_STATUS_OK)
}
}
pub const RESPONSE_STATUS_OK: &str = "ok";
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
#[rstest]
fn test_meta_deserialization() {
let json = r#"{"universe": [{"name": "BTC", "szDecimals": 5}]}"#;
let meta: HyperliquidMeta = serde_json::from_str(json).unwrap();
assert_eq!(meta.universe.len(), 1);
assert_eq!(meta.universe[0].name, "BTC");
assert_eq!(meta.universe[0].sz_decimals, 5);
}
#[rstest]
fn test_perp_asset_hip3_fields() {
let json = r#"{
"name": "xyz:TSLA",
"szDecimals": 3,
"maxLeverage": 10,
"onlyIsolated": true,
"growthMode": "enabled",
"marginMode": "strictIsolated"
}"#;
let asset: PerpAsset = serde_json::from_str(json).unwrap();
assert_eq!(asset.name, "xyz:TSLA");
assert_eq!(asset.sz_decimals, 3);
assert_eq!(asset.max_leverage, Some(10));
assert_eq!(asset.only_isolated, Some(true));
assert_eq!(asset.growth_mode.as_deref(), Some("enabled"));
assert_eq!(asset.margin_mode.as_deref(), Some("strictIsolated"));
}
#[rstest]
fn test_perp_asset_hip3_fields_absent() {
let json = r#"{"name": "BTC", "szDecimals": 5}"#;
let asset: PerpAsset = serde_json::from_str(json).unwrap();
assert_eq!(asset.growth_mode, None);
assert_eq!(asset.margin_mode, None);
}
#[rstest]
fn test_l2_book_deserialization() {
let json = r#"{"coin": "BTC", "levels": [[{"px": "50000", "sz": "1.5"}], [{"px": "50100", "sz": "2.0"}]], "time": 1234567890}"#;
let book: HyperliquidL2Book = serde_json::from_str(json).unwrap();
assert_eq!(book.coin, "BTC");
assert_eq!(book.levels.len(), 2);
assert_eq!(book.time, 1234567890);
}
#[rstest]
fn test_exchange_response_deserialization() {
let json = r#"{"status": "ok", "response": {"type": "order"}}"#;
let response: HyperliquidExchangeResponse = serde_json::from_str(json).unwrap();
assert!(response.is_ok());
}
#[rstest]
fn test_msgpack_serialization_matches_python() {
let action = HyperliquidExecAction::Order {
orders: vec![],
grouping: HyperliquidExecGrouping::Na,
builder: None,
};
let json = serde_json::to_string(&action).unwrap();
assert!(
json.contains(r#""type":"order""#),
"JSON should have type tag: {json}"
);
let msgpack_bytes = rmp_serde::to_vec_named(&action).unwrap();
let decoded: serde_json::Value = rmp_serde::from_slice(&msgpack_bytes).unwrap();
assert!(
decoded.get("type").is_some(),
"MsgPack should have type tag. Decoded: {decoded:?}"
);
assert_eq!(
decoded.get("type").unwrap().as_str().unwrap(),
"order",
"Type should be 'order'"
);
assert!(decoded.get("orders").is_some(), "Should have orders field");
assert!(
decoded.get("grouping").is_some(),
"Should have grouping field"
);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum HyperliquidExecTif {
#[serde(rename = "Alo")]
Alo,
#[serde(rename = "Ioc")]
Ioc,
#[serde(rename = "Gtc")]
Gtc,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum HyperliquidExecTpSl {
#[serde(rename = "tp")]
Tp,
#[serde(rename = "sl")]
Sl,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum HyperliquidExecGrouping {
#[serde(rename = "na")]
#[default]
Na,
#[serde(rename = "normalTpsl")]
NormalTpsl,
#[serde(rename = "positionTpsl")]
PositionTpsl,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum HyperliquidExecOrderKind {
Limit {
limit: HyperliquidExecLimitParams,
},
Trigger {
trigger: HyperliquidExecTriggerParams,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct HyperliquidExecLimitParams {
pub tif: HyperliquidExecTif,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct HyperliquidExecTriggerParams {
pub is_market: bool,
#[serde(
serialize_with = "crate::common::parse::serialize_decimal_as_str",
deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
)]
pub trigger_px: Decimal,
pub tpsl: HyperliquidExecTpSl,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct HyperliquidExecBuilderFee {
#[serde(rename = "b")]
pub address: String,
#[serde(rename = "f")]
pub fee_tenths_bp: u32,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct HyperliquidExecPlaceOrderRequest {
#[serde(rename = "a")]
pub asset: AssetId,
#[serde(rename = "b")]
pub is_buy: bool,
#[serde(
rename = "p",
serialize_with = "crate::common::parse::serialize_decimal_as_str",
deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
)]
pub price: Decimal,
#[serde(
rename = "s",
serialize_with = "crate::common::parse::serialize_decimal_as_str",
deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
)]
pub size: Decimal,
#[serde(rename = "r")]
pub reduce_only: bool,
#[serde(rename = "t")]
pub kind: HyperliquidExecOrderKind,
#[serde(rename = "c", skip_serializing_if = "Option::is_none")]
pub cloid: Option<Cloid>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct HyperliquidExecCancelOrderRequest {
#[serde(rename = "a")]
pub asset: AssetId,
#[serde(rename = "o")]
pub oid: OrderId,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct HyperliquidExecCancelByCloidRequest {
pub asset: AssetId,
pub cloid: Cloid,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct HyperliquidExecModifyOrderRequest {
pub oid: OrderId,
pub order: HyperliquidExecPlaceOrderRequest,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct HyperliquidExecTwapRequest {
#[serde(rename = "a")]
pub asset: AssetId,
#[serde(rename = "b")]
pub is_buy: bool,
#[serde(
rename = "s",
serialize_with = "crate::common::parse::serialize_decimal_as_str",
deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
)]
pub size: Decimal,
#[serde(rename = "m")]
pub duration_ms: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum HyperliquidExecAction {
#[serde(rename = "order")]
Order {
orders: Vec<HyperliquidExecPlaceOrderRequest>,
#[serde(default)]
grouping: HyperliquidExecGrouping,
#[serde(skip_serializing_if = "Option::is_none")]
builder: Option<HyperliquidExecBuilderFee>,
},
#[serde(rename = "cancel")]
Cancel {
cancels: Vec<HyperliquidExecCancelOrderRequest>,
},
#[serde(rename = "cancelByCloid")]
CancelByCloid {
cancels: Vec<HyperliquidExecCancelByCloidRequest>,
},
#[serde(rename = "modify")]
Modify {
#[serde(flatten)]
modify: HyperliquidExecModifyOrderRequest,
},
#[serde(rename = "batchModify")]
BatchModify {
modifies: Vec<HyperliquidExecModifyOrderRequest>,
},
#[serde(rename = "scheduleCancel")]
ScheduleCancel {
#[serde(skip_serializing_if = "Option::is_none")]
time: Option<u64>,
},
#[serde(rename = "updateLeverage")]
UpdateLeverage {
#[serde(rename = "a")]
asset: AssetId,
#[serde(rename = "isCross")]
is_cross: bool,
#[serde(rename = "leverage")]
leverage: u32,
},
#[serde(rename = "updateIsolatedMargin")]
UpdateIsolatedMargin {
#[serde(rename = "a")]
asset: AssetId,
#[serde(
rename = "delta",
serialize_with = "crate::common::parse::serialize_decimal_as_str",
deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
)]
delta: Decimal,
},
#[serde(rename = "usdClassTransfer")]
UsdClassTransfer {
from: String,
to: String,
#[serde(
serialize_with = "crate::common::parse::serialize_decimal_as_str",
deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
)]
amount: Decimal,
},
#[serde(rename = "twapPlace")]
TwapPlace {
#[serde(flatten)]
twap: HyperliquidExecTwapRequest,
},
#[serde(rename = "twapCancel")]
TwapCancel {
#[serde(rename = "a")]
asset: AssetId,
#[serde(rename = "t")]
twap_id: u64,
},
#[serde(rename = "noop")]
Noop,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct HyperliquidExecRequest {
pub action: HyperliquidExecAction,
pub nonce: u64,
pub signature: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub vault_address: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires_after: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HyperliquidExecResponse {
pub status: String,
pub response: HyperliquidExecResponseData,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum HyperliquidExecResponseData {
#[serde(rename = "order")]
Order {
data: HyperliquidExecOrderResponseData,
},
#[serde(rename = "cancel")]
Cancel {
data: HyperliquidExecCancelResponseData,
},
#[serde(rename = "modify")]
Modify {
data: HyperliquidExecModifyResponseData,
},
#[serde(rename = "default")]
Default,
#[serde(other)]
Unknown,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HyperliquidExecOrderResponseData {
pub statuses: Vec<HyperliquidExecOrderStatus>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HyperliquidExecCancelResponseData {
pub statuses: Vec<HyperliquidExecCancelStatus>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HyperliquidExecModifyResponseData {
pub statuses: Vec<HyperliquidExecModifyStatus>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum HyperliquidExecOrderStatus {
Resting {
resting: HyperliquidExecRestingInfo,
},
Filled {
filled: HyperliquidExecFilledInfo,
},
Error {
error: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct HyperliquidExecRestingInfo {
pub oid: OrderId,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct HyperliquidExecFilledInfo {
#[serde(
rename = "totalSz",
serialize_with = "crate::common::parse::serialize_decimal_as_str",
deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
)]
pub total_sz: Decimal,
#[serde(
rename = "avgPx",
serialize_with = "crate::common::parse::serialize_decimal_as_str",
deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
)]
pub avg_px: Decimal,
pub oid: OrderId,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum HyperliquidExecCancelStatus {
Success(String), Error {
error: String,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum HyperliquidExecModifyStatus {
Success(String), Error {
error: String,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ClearinghouseState {
#[serde(default)]
pub asset_positions: Vec<AssetPosition>,
#[serde(default)]
pub cross_margin_summary: Option<CrossMarginSummary>,
#[serde(
default,
serialize_with = "crate::common::parse::serialize_optional_decimal_as_str",
deserialize_with = "crate::common::parse::deserialize_optional_decimal_from_str"
)]
pub withdrawable: Option<Decimal>,
#[serde(default)]
pub time: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AssetPosition {
pub position: PositionData,
#[serde(rename = "type")]
pub position_type: HyperliquidPositionType,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LeverageInfo {
#[serde(rename = "type")]
pub leverage_type: HyperliquidLeverageType,
pub value: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CumFundingInfo {
#[serde(
rename = "allTime",
serialize_with = "crate::common::parse::serialize_decimal_as_str",
deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
)]
pub all_time: Decimal,
#[serde(
rename = "sinceOpen",
serialize_with = "crate::common::parse::serialize_decimal_as_str",
deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
)]
pub since_open: Decimal,
#[serde(
rename = "sinceChange",
serialize_with = "crate::common::parse::serialize_decimal_as_str",
deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
)]
pub since_change: Decimal,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PositionData {
pub coin: Ustr,
#[serde(rename = "cumFunding")]
pub cum_funding: CumFundingInfo,
#[serde(
rename = "entryPx",
serialize_with = "crate::common::parse::serialize_optional_decimal_as_str",
deserialize_with = "crate::common::parse::deserialize_optional_decimal_from_str",
default
)]
pub entry_px: Option<Decimal>,
pub leverage: LeverageInfo,
#[serde(
rename = "liquidationPx",
serialize_with = "crate::common::parse::serialize_optional_decimal_as_str",
deserialize_with = "crate::common::parse::deserialize_optional_decimal_from_str",
default
)]
pub liquidation_px: Option<Decimal>,
#[serde(
rename = "marginUsed",
serialize_with = "crate::common::parse::serialize_decimal_as_str",
deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
)]
pub margin_used: Decimal,
#[serde(rename = "maxLeverage", default)]
pub max_leverage: Option<u32>,
#[serde(
rename = "positionValue",
serialize_with = "crate::common::parse::serialize_decimal_as_str",
deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
)]
pub position_value: Decimal,
#[serde(
rename = "returnOnEquity",
serialize_with = "crate::common::parse::serialize_decimal_as_str",
deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
)]
pub return_on_equity: Decimal,
#[serde(
rename = "szi",
serialize_with = "crate::common::parse::serialize_decimal_as_str",
deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
)]
pub szi: Decimal,
#[serde(
rename = "unrealizedPnl",
serialize_with = "crate::common::parse::serialize_decimal_as_str",
deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
)]
pub unrealized_pnl: Decimal,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CrossMarginSummary {
#[serde(
rename = "accountValue",
serialize_with = "crate::common::parse::serialize_decimal_as_str",
deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
)]
pub account_value: Decimal,
#[serde(
rename = "totalNtlPos",
serialize_with = "crate::common::parse::serialize_decimal_as_str",
deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
)]
pub total_ntl_pos: Decimal,
#[serde(
rename = "totalRawUsd",
serialize_with = "crate::common::parse::serialize_decimal_as_str",
deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
)]
pub total_raw_usd: Decimal,
#[serde(
rename = "totalMarginUsed",
serialize_with = "crate::common::parse::serialize_decimal_as_str",
deserialize_with = "crate::common::parse::deserialize_decimal_from_str"
)]
pub total_margin_used: Decimal,
#[serde(
rename = "withdrawable",
default,
serialize_with = "crate::common::parse::serialize_optional_decimal_as_str",
deserialize_with = "crate::common::parse::deserialize_optional_decimal_from_str"
)]
pub withdrawable: Option<Decimal>,
}