diff --git a/v4-client-rs/client/examples/support/constants.rs b/v4-client-rs/client/examples/support/constants.rs index b9ba39cf..db260dca 100644 --- a/v4-client-rs/client/examples/support/constants.rs +++ b/v4-client-rs/client/examples/support/constants.rs @@ -1 +1,2 @@ +#[allow(dead_code)] pub const TEST_MNEMONIC: &str = "mirror actor skill push coach wait confirm orchard lunch mobile athlete gossip awake miracle matter bus reopen team ladder lazy list timber render wait"; diff --git a/v4-client-rs/client/examples/vault_endpoint.rs b/v4-client-rs/client/examples/vault_endpoint.rs new file mode 100644 index 00000000..110b3afb --- /dev/null +++ b/v4-client-rs/client/examples/vault_endpoint.rs @@ -0,0 +1,43 @@ +mod support; +use anyhow::{Error, Result}; +use dydx::config::ClientConfig; +use dydx::indexer::{IndexerClient, PnlTickInterval}; + +pub struct Rester { + indexer: IndexerClient, +} + +impl Rester { + pub async fn connect() -> Result { + let config = ClientConfig::from_file("client/tests/testnet.toml").await?; + let indexer = IndexerClient::new(config.indexer); + Ok(Self { indexer }) + } +} + +#[tokio::main] +async fn main() -> Result<()> { + tracing_subscriber::fmt().try_init().map_err(Error::msg)?; + let rester = Rester::connect().await?; + let indexer = rester.indexer; + + // Test values + let resolution = PnlTickInterval::Hour; + + let pnls = indexer + .vaults() + .get_megavault_historical_pnl(resolution) + .await?; + tracing::info!("MegaVault historical PnLs: {pnls:?}"); + + let vaults_pnls = indexer + .vaults() + .get_vaults_historical_pnl(resolution) + .await?; + tracing::info!("Vaults historical PnLs: {vaults_pnls:?}"); + + let positions = indexer.vaults().get_megavault_positions().await?; + tracing::info!("MegaVault positions: {positions:?}"); + + Ok(()) +} diff --git a/v4-client-rs/client/src/indexer/mod.rs b/v4-client-rs/client/src/indexer/mod.rs index 8f26c0ab..ec649395 100644 --- a/v4-client-rs/client/src/indexer/mod.rs +++ b/v4-client-rs/client/src/indexer/mod.rs @@ -44,6 +44,11 @@ impl IndexerClient { self.rest.utility() } + /// Get vaults query dispatcher. + pub fn vaults(&self) -> rest::Vaults { + self.rest.vaults() + } + /// Get feeds dispatcher. pub fn feed(&mut self) -> Feeds<'_> { Feeds::new(&mut self.sock) diff --git a/v4-client-rs/client/src/indexer/rest/client/mod.rs b/v4-client-rs/client/src/indexer/rest/client/mod.rs index 9b27af44..295dcc88 100644 --- a/v4-client-rs/client/src/indexer/rest/client/mod.rs +++ b/v4-client-rs/client/src/indexer/rest/client/mod.rs @@ -1,6 +1,7 @@ pub mod accounts; pub mod markets; pub mod utility; +pub mod vaults; use super::config::RestConfig; use super::options::*; @@ -10,6 +11,7 @@ use markets::Markets; use reqwest::Client; use serde::Serialize; use utility::Utility; +use vaults::Vaults; #[derive(Serialize)] #[serde(rename_all = "camelCase")] @@ -55,4 +57,9 @@ impl RestClient { pub(crate) fn utility(&self) -> Utility<'_> { Utility::new(self) } + + /// Get vaults query dispatcher. + pub(crate) fn vaults(&self) -> Vaults<'_> { + Vaults::new(self) + } } diff --git a/v4-client-rs/client/src/indexer/rest/client/vaults.rs b/v4-client-rs/client/src/indexer/rest/client/vaults.rs new file mode 100644 index 00000000..e0fa63bb --- /dev/null +++ b/v4-client-rs/client/src/indexer/rest/client/vaults.rs @@ -0,0 +1,75 @@ +use super::*; +use anyhow::Error; + +/// Vaults dispatcher. +/// +/// Check [the example](https://github.com/NethermindEth/dydx-v4-rust/blob/trunk/client/examples/vaults_endpoint.rs). +pub struct Vaults<'a> { + rest: &'a RestClient, +} + +impl<'a> Vaults<'a> { + /// Create a new vaults dispatcher. + pub(crate) fn new(rest: &'a RestClient) -> Self { + Self { rest } + } + + /// MegaVault historical PnL. + pub async fn get_megavault_historical_pnl( + &self, + resolution: PnlTickInterval, + ) -> Result, Error> { + let rest = &self.rest; + const URI: &str = "/v4/vault/v1/megavault/historicalPnl"; + let url = format!("{}{URI}", rest.config.endpoint); + let resp = rest + .client + .get(url) + .query(&[("resolution", resolution)]) + .send() + .await? + .error_for_status()? + .json::() + .await? + .megavault_pnl; + Ok(resp) + } + + /// Vaults historical PnL. + pub async fn get_vaults_historical_pnl( + &self, + resolution: PnlTickInterval, + ) -> Result, Error> { + let rest = &self.rest; + const URI: &str = "/v4/vault/v1/vaults/historicalPnl"; + let url = format!("{}{URI}", rest.config.endpoint); + let resp = rest + .client + .get(url) + .query(&[("resolution", resolution)]) + .send() + .await? + .error_for_status()? + .json::() + .await? + .vaults_pnl; + Ok(resp) + } + + /// MegaVault positions. + pub async fn get_megavault_positions(&self) -> Result, Error> { + let rest = &self.rest; + const URI: &str = "/v4/vault/v1/megavault/positions"; + let url = format!("{}{URI}", rest.config.endpoint); + let resp = rest + .client + .get(url) + .send() + .await? + .error_for_status()? + .json::() + .await? + .positions; + Ok(resp) + } +} diff --git a/v4-client-rs/client/src/indexer/rest/mod.rs b/v4-client-rs/client/src/indexer/rest/mod.rs index dc71ef71..5d943a4e 100644 --- a/v4-client-rs/client/src/indexer/rest/mod.rs +++ b/v4-client-rs/client/src/indexer/rest/mod.rs @@ -11,3 +11,4 @@ pub use types::*; pub use client::accounts::Accounts; pub use client::markets::Markets; pub use client::utility::Utility; +pub use client::vaults::Vaults; diff --git a/v4-client-rs/client/src/indexer/rest/types.rs b/v4-client-rs/client/src/indexer/rest/types.rs index 8c830e00..649b1cb9 100644 --- a/v4-client-rs/client/src/indexer/rest/types.rs +++ b/v4-client-rs/client/src/indexer/rest/types.rs @@ -30,6 +30,18 @@ pub struct ErrorMsg { #[derive(Deserialize, Debug, Clone, From, Display, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct PnlTickId(pub String); +/// PnL tick resolution. +#[derive( + Deserialize, Serialize, Debug, Clone, Copy, From, Display, PartialEq, Eq, PartialOrd, Ord, Hash, +)] +#[serde(rename_all = "lowercase")] +pub enum PnlTickInterval { + /// Hour. + Hour, + /// Day. + Day, +} + /// Transfer id. #[derive( Serialize, Deserialize, Debug, Clone, From, Display, PartialEq, Eq, PartialOrd, Ord, Hash, @@ -301,3 +313,51 @@ pub struct HistoricalTradingRewardAggregation { /// Aggregation period. pub period: TradingRewardAggregationPeriod, } + +/// MegaVault Profit and loss reports. +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct MegaVaultHistoricalPnlResponse { + /// List of PnL reports. + pub megavault_pnl: Vec, +} + +/// MegaVault Profit and loss reports. +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct MegaVaultPositionResponse { + /// List MegaVault positions. + pub positions: Vec, +} + +/// Vaults profit and loss reports. +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct VaultsHistoricalPnLResponse { + /// List of PnL reports. + pub vaults_pnl: Vec, +} + +/// Vault Profit and loss reports. +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct VaultHistoricalPnl { + /// Associated ticker. + pub ticker: String, + /// List of PnL reports. + pub historical_pnl: Vec, +} + +/// Vault position. +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct VaultPosition { + /// Associated ticker. + pub ticker: String, + /// Asset position. + pub asset_position: Option, + /// Perpetual position. + pub perpetual_position: Option, + /// Equity. + pub equity: BigDecimal, +} diff --git a/v4-client-rs/client/tests/test_indexer_rest.rs b/v4-client-rs/client/tests/test_indexer_rest.rs index b895ec0b..dc65a74e 100644 --- a/v4-client-rs/client/tests/test_indexer_rest.rs +++ b/v4-client-rs/client/tests/test_indexer_rest.rs @@ -282,6 +282,35 @@ async fn test_indexer_account_get_rewards_aggregated() -> Result<()> { Ok(()) } +#[tokio::test] +async fn test_indexer_vaults_get_megavault_historical_pnl() -> Result<()> { + let env = TestEnv::testnet().await?; + let resolution = PnlTickInterval::Hour; + env.indexer + .vaults() + .get_megavault_historical_pnl(resolution) + .await?; + Ok(()) +} + +#[tokio::test] +async fn test_indexer_vaults_get_vaults_historical_pnl() -> Result<()> { + let env = TestEnv::testnet().await?; + let resolution = PnlTickInterval::Hour; + env.indexer + .vaults() + .get_vaults_historical_pnl(resolution) + .await?; + Ok(()) +} + +#[tokio::test] +async fn test_indexer_vaults_get_megavault_positions() -> Result<()> { + let env = TestEnv::testnet().await?; + env.indexer.vaults().get_megavault_positions().await?; + Ok(()) +} + #[tokio::test] async fn test_perpetual_market_quantization() -> Result<()> { let env = TestEnv::testnet().await?;