diff --git a/src/info/info_client.rs b/src/info/info_client.rs index 7d9313c..20a620e 100644 --- a/src/info/info_client.rs +++ b/src/info/info_client.rs @@ -7,7 +7,7 @@ use crate::{ prelude::*, req::HttpClient, ws::{Subscription, WsManager}, - BaseUrl, Error, Message, + BaseUrl, Error, Message, PerpetualsAssetContextsResponse, }; use ethers::types::H160; @@ -29,6 +29,8 @@ pub struct CandleSnapshotRequest { #[serde(tag = "type")] #[serde(rename_all = "camelCase")] pub enum InfoRequest { + #[serde(rename = "metaAndAssetCtxs")] + PerpetualsMetaAndAssetCtxs, #[serde(rename = "clearinghouseState")] UserState { user: H160, @@ -115,6 +117,15 @@ impl InfoClient { .await } + /// Retrieve perpetuals asset contexts (includes mark price, current funding, open interest, etc). + pub async fn perpetuals_meta_and_asset_ctxs(&self) -> Result { + let input = InfoRequest::PerpetualsMetaAndAssetCtxs; + let data = serde_json::to_string(&input).map_err(|e| Error::JsonParse(e.to_string()))?; + + let return_data = self.http_client.post("/info", data).await?; + serde_json::from_str(&return_data).map_err(|e| Error::JsonParse(e.to_string())) + } + pub async fn open_orders(&self, address: H160) -> Result> { let input = InfoRequest::OpenOrders { user: address }; let data = serde_json::to_string(&input).map_err(|e| Error::JsonParse(e.to_string()))?; @@ -225,3 +236,44 @@ impl InfoClient { serde_json::from_str(&return_data).map_err(|e| Error::JsonParse(e.to_string())) } } + +#[cfg(test)] +mod tests { + use std::time::{SystemTime, UNIX_EPOCH}; + + use super::*; + + #[tokio::test] + async fn test_perpetuals_meta_and_asset_ctxs() { + let info_client = InfoClient::new(None, None).await.unwrap(); + let result = info_client.perpetuals_meta_and_asset_ctxs().await.unwrap(); + dbg!(result.into_universe_and_asset_contexts()); + } + + #[tokio::test] + async fn test_all_mids() { + let info_client = InfoClient::new(None, None).await.unwrap(); + let result = info_client.all_mids().await; + dbg!(result); + } + + #[tokio::test] + async fn test_l2_snapshot() { + let info_client = InfoClient::new(None, None).await.unwrap(); + let result = info_client.l2_snapshot("ETH".to_string()).await; + dbg!(result); + } + + #[tokio::test] + async fn test_candles_snapshot() { + let info_client = InfoClient::new(None, None).await.unwrap(); + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(); + let result = info_client + .candles_snapshot("ETH".to_string(), "1m".to_string(), now, now) + .await; + dbg!(result); + } +} diff --git a/src/info/response_structs.rs b/src/info/response_structs.rs index dff1715..87c9736 100644 --- a/src/info/response_structs.rs +++ b/src/info/response_structs.rs @@ -1,5 +1,109 @@ -use crate::info::{AssetPosition, Level, MarginSummary}; -use serde::Deserialize; +use crate::{ + info::{AssetPosition, Level, MarginSummary}, + PerpetualAssetContext, Universe, UniverseItem, +}; +use serde::{Deserialize, Deserializer}; +use serde_json::Value; + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PerpetualsAssetContextsResponse([PerpetualsAssetContexts; 2]); + +impl PerpetualsAssetContextsResponse { + /// Consumes the response and returns universe. + pub fn into_universe(self) -> Universe { + match self.0[0].clone() { + PerpetualsAssetContexts::Universe(u) => u, + _ => panic!("Expected universe."), + } + } + + /// Consumes the response and returns asset contexts. + pub fn into_asset_contexts(self) -> Vec { + match self.0[1].clone() { + PerpetualsAssetContexts::AssetContexts(ac) => ac, + _ => panic!("Expected asset contexts."), + } + } + + /// Consumes the response and returns an array of tuples containing an universe item and asset contexts. + /// Each element of universe should match each element of asset contexts. + pub fn into_universe_and_asset_contexts(self) -> Vec<(UniverseItem, PerpetualAssetContext)> { + let universe = match self.0[0].clone() { + PerpetualsAssetContexts::Universe(u) => u, + _ => panic!("Expected universe."), + }; + let asset_contexts = match self.0[1].clone() { + PerpetualsAssetContexts::AssetContexts(ac) => ac, + _ => panic!("Expected asset contexts."), + }; + universe + .universe + .into_iter() + .zip(asset_contexts.into_iter()) + .collect() + } + + /// Returns a reference to the universe. + pub fn universe(&self) -> &Universe { + match &self.0[0] { + PerpetualsAssetContexts::Universe(u) => u, + _ => panic!("Expected universe."), + } + } + + /// Returns a reference to the asset contexts. + pub fn asset_contexts(&self) -> &Vec { + match &self.0[1] { + PerpetualsAssetContexts::AssetContexts(ac) => ac, + _ => panic!("Expected asset contexts."), + } + } + + /// Returns a reference to an array of tuples containing an universe item and asset contexts. + /// Each element of universe should match each element of asset contexts. + pub fn universe_and_asset_contexts(&self) -> Vec<(&UniverseItem, &PerpetualAssetContext)> { + let universe = match &self.0[0] { + PerpetualsAssetContexts::Universe(u) => u, + _ => panic!("Expected universe."), + }; + let asset_contexts = match &self.0[1] { + PerpetualsAssetContexts::AssetContexts(ac) => ac, + _ => panic!("Expected asset contexts."), + }; + universe + .universe + .iter() + .zip(asset_contexts.iter()) + .collect() + } +} + +#[derive(Debug, Clone)] +pub enum PerpetualsAssetContexts { + Universe(Universe), + AssetContexts(Vec), +} + +impl<'de> Deserialize<'de> for PerpetualsAssetContexts { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let value: Value = Deserialize::deserialize(deserializer)?; + if value.is_object() { + Ok(PerpetualsAssetContexts::Universe( + serde_json::from_value(value).map_err(serde::de::Error::custom)?, + )) + } else if value.is_array() { + Ok(PerpetualsAssetContexts::AssetContexts( + serde_json::from_value(value).map_err(serde::de::Error::custom)?, + )) + } else { + Err(serde::de::Error::custom("Expected object or array")) + } + } +} #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] diff --git a/src/info/sub_structs.rs b/src/info/sub_structs.rs index e9678d6..a1029cb 100644 --- a/src/info/sub_structs.rs +++ b/src/info/sub_structs.rs @@ -1,5 +1,37 @@ use serde::Deserialize; +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Universe { + pub universe: Vec, +} + +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct UniverseItem { + pub name: String, + pub sz_decimals: u32, + pub max_leverage: u32, + pub only_isolated: bool, +} + +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct PerpetualAssetContext { + pub day_ntl_vlm: String, + pub funding: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub impact_pxs: Option>, + pub mark_px: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub mid_px: Option, + pub open_interest: String, + pub oracle_px: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub premium: Option, + pub prev_day_px: String, +} + #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct Leverage {