diff --git a/src/errors.rs b/src/errors.rs index 00d56fb1..db525233 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -61,4 +61,6 @@ pub enum Error { SignatureFailure(String), #[error("Vault address not found")] VaultAddressNotFound, + #[error("Subscription error: {0:?}")] + SubscriptionError(String), } diff --git a/src/exchange/exchange_client.rs b/src/exchange/exchange_client.rs index 70b686b8..e2c71fed 100644 --- a/src/exchange/exchange_client.rs +++ b/src/exchange/exchange_client.rs @@ -30,7 +30,7 @@ use crate::{ VaultTransfer, Withdraw3, }; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ExchangeClient { pub http_client: HttpClient, pub wallet: PrivateKeySigner, @@ -139,7 +139,7 @@ impl ExchangeClient { }) } - async fn post( + pub async fn post( &self, action: serde_json::Value, signature: Signature, diff --git a/src/info/info_client.rs b/src/info/info_client.rs index b3d8ba2a..f51e2880 100644 --- a/src/info/info_client.rs +++ b/src/info/info_client.rs @@ -96,7 +96,7 @@ pub enum InfoRequest { }, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct InfoClient { pub http_client: HttpClient, pub(crate) ws_manager: Option, diff --git a/src/info/sub_structs.rs b/src/info/sub_structs.rs index d244a063..53515d3f 100644 --- a/src/info/sub_structs.rs +++ b/src/info/sub_structs.rs @@ -108,12 +108,13 @@ pub struct Vip { pub ntl_cutoff: String, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] pub struct UserTokenBalance { pub coin: String, pub hold: String, pub total: String, + pub token: u32, pub entry_ntl: String, } diff --git a/src/lib.rs b/src/lib.rs index 86f20e2a..2f16e4df 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,16 +1,16 @@ #![deny(unreachable_pub)] -mod consts; -mod eip712; -mod errors; -mod exchange; -mod helpers; -mod info; -mod market_maker; -mod meta; -mod prelude; -mod req; -mod signature; -mod ws; +pub mod consts; +pub mod eip712; +pub mod errors; +pub mod exchange; +pub mod helpers; +pub mod info; +pub mod market_maker; +pub mod meta; +pub mod prelude; +pub mod req; +pub mod signature; +pub mod ws; pub use consts::{EPSILON, LOCAL_API_URL, MAINNET_API_URL, TESTNET_API_URL}; pub use eip712::Eip712; pub use errors::Error; diff --git a/src/meta.rs b/src/meta.rs index 5c983dfe..499ccb15 100644 --- a/src/meta.rs +++ b/src/meta.rs @@ -1,14 +1,14 @@ use std::collections::HashMap; use alloy::primitives::B128; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; #[derive(Deserialize, Debug, Clone)] pub struct Meta { pub universe: Vec, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Deserialize, Debug, Clone, Serialize)] pub struct SpotMeta { pub universe: Vec, pub tokens: Vec, @@ -52,6 +52,21 @@ pub enum SpotMetaAndAssetCtxs { Context(Vec), } +impl SpotMetaAndAssetCtxs { + pub fn get_spot_meta(&self) -> &SpotMeta { + match self { + SpotMetaAndAssetCtxs::SpotMeta(meta) => meta, + _ => panic!("Not a spot meta"), + } + } + pub fn get_context(&self) -> &Vec { + match self { + SpotMetaAndAssetCtxs::Context(ctxs) => ctxs, + _ => panic!("Not a context"), + } + } +} + #[derive(Deserialize, Debug, Clone)] #[serde(untagged)] pub enum MetaAndAssetCtxs { @@ -59,7 +74,7 @@ pub enum MetaAndAssetCtxs { Context(Vec), } -#[derive(Deserialize, Debug, Clone)] +#[derive(Deserialize, Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct SpotAssetContext { pub day_ntl_vlm: String, @@ -94,7 +109,7 @@ pub struct AssetMeta { pub only_isolated: Option, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Deserialize, Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct SpotAssetMeta { pub tokens: [usize; 2], @@ -103,7 +118,7 @@ pub struct SpotAssetMeta { pub is_canonical: bool, } -#[derive(Debug, Deserialize, Clone)] +#[derive(Debug, Deserialize, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct TokenInfo { pub name: String, @@ -112,4 +127,5 @@ pub struct TokenInfo { pub index: usize, pub token_id: B128, pub is_canonical: bool, + pub full_name: Option, } diff --git a/src/req.rs b/src/req.rs index f7d5e430..e458b226 100644 --- a/src/req.rs +++ b/src/req.rs @@ -10,7 +10,7 @@ struct ErrorData { msg: String, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct HttpClient { pub client: Client, pub base_url: String, diff --git a/src/ws/message_types.rs b/src/ws/message_types.rs index 332a9e2b..5177b0e1 100644 --- a/src/ws/message_types.rs +++ b/src/ws/message_types.rs @@ -2,6 +2,11 @@ use serde::Deserialize; use crate::ws::sub_structs::*; +#[derive(Deserialize, Clone, Debug)] +pub struct SubscriptionError { + pub data: String, +} + #[derive(Deserialize, Clone, Debug)] pub struct Trades { pub data: Vec, @@ -58,6 +63,7 @@ pub struct WebData2 { } #[derive(Deserialize, Clone, Debug)] +#[serde(rename_all = "camelCase")] pub struct ActiveAssetCtx { pub data: ActiveAssetCtxData, } diff --git a/src/ws/ws_manager.rs b/src/ws/ws_manager.rs index 4035baf3..e2267a19 100755 --- a/src/ws/ws_manager.rs +++ b/src/ws/ws_manager.rs @@ -35,13 +35,16 @@ use crate::{ WebData2, }; +use super::SubscriptionError; + #[derive(Debug)] struct SubscriptionData { sending_channel: UnboundedSender, subscription_id: u32, id: String, } -#[derive(Debug)] + +#[derive(Debug, Clone)] pub(crate) struct WsManager { stop_flag: Arc, writer: Arc>, protocol::Message>>>, @@ -92,6 +95,7 @@ pub enum Message { ActiveAssetData(ActiveAssetData), ActiveSpotAssetCtx(ActiveSpotAssetCtx), Bbo(Bbo), + Error(SubscriptionError), Pong, } @@ -296,6 +300,15 @@ impl WsManager { Message::SubscriptionResponse | Message::Pong => Ok(String::default()), Message::NoData => Ok("".to_string()), Message::HyperliquidError(err) => Ok(format!("hyperliquid error: {err:?}")), + Message::Error(err) => { + let error_str = err.data.to_string(); + let identifier = error_str + .split("Invalid subscription ") + .nth(1) + .unwrap_or("Invalid subscription") + .to_string(); + Ok(identifier) + } } } @@ -309,8 +322,10 @@ impl WsManager { if !data.starts_with('{') { return Ok(()); } + let message = serde_json::from_str::(&data) .map_err(|e| Error::JsonParse(e.to_string()))?; + let identifier = WsManager::get_identifier(&message)?; if identifier.is_empty() { return Ok(()); @@ -411,15 +426,11 @@ impl WsManager { ) -> Result { let mut subscriptions = self.subscriptions.lock().await; - let identifier_entry = if let Subscription::UserEvents { user: _ } = - serde_json::from_str::(&identifier) - .map_err(|e| Error::JsonParse(e.to_string()))? - { + let subscription = serde_json::from_str::(&identifier) + .map_err(|e| Error::JsonParse(e.to_string()))?; + let identifier_entry = if let Subscription::UserEvents { user: _ } = subscription { "userEvents".to_string() - } else if let Subscription::OrderUpdates { user: _ } = - serde_json::from_str::(&identifier) - .map_err(|e| Error::JsonParse(e.to_string()))? - { + } else if let Subscription::OrderUpdates { user: _ } = subscription { "orderUpdates".to_string() } else { identifier.clone()