diff --git a/deribit-client/src/client/deribit_response.rs b/deribit-client/src/client/deribit_response.rs index 1e90d8396..f3a5f6d63 100644 --- a/deribit-client/src/client/deribit_response.rs +++ b/deribit-client/src/client/deribit_response.rs @@ -142,7 +142,7 @@ pub struct Transfer { pub other_side: String, pub created_timestamp: u64, pub updated_timestamp: u64, - pub note: String, + pub note: Option, } #[derive(Deserialize, Debug, Clone)] @@ -163,6 +163,18 @@ pub struct TransferDetails { pub testnet: Option, } +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct TransferSubmitted { + pub jsonrpc: String, + pub result: Transfer, + + pub us_in: Option, + pub us_out: Option, + pub us_diff: Option, + pub testnet: Option, +} + #[derive(Deserialize, Debug, Clone)] pub struct Withdrawal { pub id: u64, @@ -172,9 +184,11 @@ pub struct Withdrawal { pub address: String, pub priority: Decimal, pub state: String, - pub transaction_id: String, + #[serde(deserialize_with = "boolean")] + pub completed: bool, + pub transaction_id: Option, pub created_timestamp: u64, - pub confirmed_timestamp: u64, + pub confirmed_timestamp: Option, pub updated_timestamp: u64, pub note: String, } @@ -371,6 +385,20 @@ pub struct AccountSummaryDetails { pub testnet: Option, } +fn boolean<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result { + Ok(match serde_json::Value::deserialize(deserializer)? { + serde_json::Value::Bool(b) => b, + serde_json::Value::String(s) => s == "yes", + serde_json::Value::Number(num) => { + num.as_i64() + .ok_or_else(|| serde::de::Error::custom("Invalid number"))? + != 0 + } + serde_json::Value::Null => false, + _ => return Err(serde::de::Error::custom("Wrong type, expected boolean")), + }) +} + #[cfg(test)] mod tests { use rust_decimal_macros::dec; @@ -437,6 +465,15 @@ mod tests { assert_eq!(details.result.data[0].currency, Currency::BTC.to_string(),); } + #[test] + fn submit_transfer() { + let response_text = "{\"jsonrpc\": \"2.0\",\"id\": 210,\"result\": {\"updated_timestamp\": 1550226218504,\"type\": \"subaccount\",\"state\": \"confirmed\",\"other_side\": \"MySubAccount\",\"id\": 1,\"direction\": \"payment\",\"currency\": \"ETH\",\"created_timestamp\": 1550226218504,\"amount\": 12.1234}}"; + let details = serde_json::from_str::(response_text).unwrap(); + dbg!(details.clone()); + assert!(!details.result.other_side.is_empty()); + assert_eq!(details.result.other_side, "MySubAccount",); + } + #[test] fn get_withdrawals_empty() { let response_text = "{\"jsonrpc\": \"2.0\",\"result\": {\"data\": [],\"count\": 0},\"usIn\": 1675066247615127,\"usOut\": 1675066247615279,\"usDiff\": 152,\"testnet\": true}"; @@ -446,11 +483,19 @@ mod tests { } #[test] - fn get_withdrawals() { - let response_text = "{\"jsonrpc\": \"2.0\",\"result\": {\"data\": [],\"count\": 0},\"usIn\": 1675066247615127,\"usOut\": 1675066247615279,\"usDiff\": 152,\"testnet\": true}"; - let _details = serde_json::from_str::(response_text).unwrap(); - // assert!(!details.result.data.is_empty()); - // assert_eq!(details.result.data[0].currency, Currency::BTC.to_string(),); + fn get_withdrawals_unconfirmed() { + let response_text = "{\"jsonrpc\":\"2.0\",\"result\":{\"data\":[{\"updated_timestamp\":1675411149209,\"transaction_id\":null,\"state\":\"unconfirmed\",\"priority\":1.0,\"note\":\"\",\"id\":21113,\"fee\":0.0001,\"currency\":\"BTC\",\"created_timestamp\":1675411149209,\"confirmed_timestamp\":null,\"completed\":0,\"amount\":0.0499,\"address\":\"bcrt1q2fnd9qslx03ka9un8cd50n7yh073rq67t0zp2z\"}],\"count\":1},\"usIn\":1675411523122145,\"usOut\":1675411523122283,\"usDiff\":138,\"testnet\":true}"; + let details = serde_json::from_str::(response_text).unwrap(); + assert!(!details.result.data.is_empty()); + assert_eq!(details.result.data[0].currency, Currency::BTC.to_string(),); + } + + #[test] + fn get_withdrawals_confirmed() { + let response_text = "{\"jsonrpc\":\"2.0\",\"result\":{\"data\":[{\"updated_timestamp\":16754124,\"transaction_id\":null,\"state\":\"confirmed\",\"priority\":1.0,\"note\":\"\",\"id\":211,\"fee\":0.0001,\"currency\":\"BTC\",\"created_timestamp\":1675411,\"confirmed_timestamp\":167541,\"completed\":0,\"amount\":0.0499,\"address\":\"bcrt1q2\"}],\"count\":1},\"usIn\":1675412486,\"usOut\":167541248,\"usDiff\":147,\"testnet\":true}"; + let details = serde_json::from_str::(response_text).unwrap(); + assert!(!details.result.data.is_empty()); + assert_eq!(details.result.data[0].currency, Currency::BTC.to_string(),); } #[test] diff --git a/deribit-client/src/client/error.rs b/deribit-client/src/client/error.rs index fbe74526f..51ebbc481 100644 --- a/deribit-client/src/client/error.rs +++ b/deribit-client/src/client/error.rs @@ -11,6 +11,9 @@ pub enum DeribitClientError { #[error("DeribitClientError - DecimalConversion: {0}")] DecimalConversion(#[from] rust_decimal::Error), + #[error("DeribitClientError - CannotConvertOrderStateFromStr")] + CannotConvertOrderStateFromStr, + #[error("DeribitClientError - UnexpectedResponse: {code:?} - {msg:?}")] UnexpectedResponse { msg: String, code: i64 }, #[error("DeribitClientError - RequestParametersError: {code:?} - {msg:?}")] diff --git a/deribit-client/src/client/mod.rs b/deribit-client/src/client/mod.rs index 3a4849793..d028705a3 100644 --- a/deribit-client/src/client/mod.rs +++ b/deribit-client/src/client/mod.rs @@ -138,7 +138,7 @@ impl DeribitClient { } #[instrument(skip(self), err)] - pub async fn get_withdrawals(&self) -> Result, DeribitClientError> { + pub async fn get_withdrawals(&self) -> Result, DeribitClientError> { let endpoint = "/private/get_withdrawals"; let params = format!("?currency={}", Currency::BTC); @@ -152,7 +152,7 @@ impl DeribitClient { .send() .await?; - let details = Self::extract_response_data::(response).await?; + let details = Self::extract_response_data::(response).await?; Ok(details.result.data) } @@ -314,7 +314,7 @@ impl DeribitClient { #[instrument(skip(self), err)] pub async fn get_funding_account_summary(&self) -> Result { let endpoint = "/private/get_account_summary"; - let params = format!("?currency={}", Currency::BTC,); + let params = format!("?currency={}&extended=true", Currency::BTC,); let headers = self.get_private_request_headers(KeyUsage::ForFunding)?; @@ -334,7 +334,7 @@ impl DeribitClient { #[instrument(skip(self), err)] pub async fn get_trading_account_summary(&self) -> Result { let endpoint = "/private/get_account_summary"; - let params = format!("?currency={}", Currency::BTC,); + let params = format!("?currency={}&extended=true", Currency::BTC,); let headers = self.get_private_request_headers(KeyUsage::ForTrading)?; @@ -351,13 +351,84 @@ impl DeribitClient { Ok(details.result) } + #[instrument(skip(self), err)] + pub async fn get_funding_account_id(&self) -> Result { + let summary = self.get_funding_account_summary().await?; + Ok(summary.id.unwrap()) + } + + #[instrument(skip(self), err)] + pub async fn get_trading_account_id(&self) -> Result { + let summary = self.get_trading_account_summary().await?; + Ok(summary.id.unwrap()) + } + + #[instrument(skip(self), err)] + pub async fn transfer_funding_to_trading( + &self, + amount_in_btc: Decimal, + ) -> Result { + let account_id = self.get_trading_account_id().await?; + let endpoint = "/private/submit_transfer_to_subaccount"; + + let params = format!( + "?currency={}&amount={}&destination={}", + Currency::BTC, + amount_in_btc, + account_id + ); + + let headers = self.get_private_request_headers(KeyUsage::ForFunding)?; + + let response = self + .rate_limit_client(endpoint) + .await + .get(self.url_for_path(endpoint, params.as_str())) + .headers(headers) + .send() + .await?; + + let details = Self::extract_response_data::(response).await?; + + Ok(details.result) + } + + #[instrument(skip(self), err)] + pub async fn transfer_trading_to_funding( + &self, + amount_in_btc: Decimal, + ) -> Result { + let account_id = self.get_funding_account_id().await?; + let endpoint = "/private/submit_transfer_to_subaccount"; + + let params = format!( + "?currency={}&amount={}&destination={}", + Currency::BTC, + amount_in_btc, + account_id + ); + + let headers = self.get_private_request_headers(KeyUsage::ForTrading)?; + + let response = self + .rate_limit_client(endpoint) + .await + .get(self.url_for_path(endpoint, params.as_str())) + .headers(headers) + .send() + .await?; + + let details = Self::extract_response_data::(response).await?; + + Ok(details.result) + } + async fn extract_response_data( response: Response, ) -> Result { match response.status() { StatusCode::OK => { let response_text = response.text().await?; - dbg!(response_text.clone()); match serde_json::from_str::(&response_text) { Ok(data) => Ok(data), Err(err) => Err(DeribitClientError::UnexpectedResponse { @@ -368,7 +439,6 @@ impl DeribitClient { } _ => { let response_text = response.text().await?; - dbg!(response_text.clone()); let data = serde_json::from_str::(&response_text)?; Err(DeribitClientError::from(( data.error.message, diff --git a/deribit-client/src/client/primitives.rs b/deribit-client/src/client/primitives.rs index f273d60c2..5dc0d5ca3 100644 --- a/deribit-client/src/client/primitives.rs +++ b/deribit-client/src/client/primitives.rs @@ -1,5 +1,7 @@ use rust_decimal::Decimal; -use std::fmt::Display; +use std::{fmt::Display, str::FromStr}; + +use crate::DeribitClientError; #[derive(serde::Deserialize, Debug, Clone)] #[serde(transparent)] @@ -89,6 +91,42 @@ impl Display for Priority { } } +#[derive(serde::Deserialize, Debug, Clone, PartialEq, Eq)] +pub enum OrderState { + Open, + Filled, + Rejected, + Cancelled, + Untriggered, +} + +impl Display for OrderState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match *self { + OrderState::Open => write!(f, "open"), + OrderState::Filled => write!(f, "filled"), + OrderState::Rejected => write!(f, "rejected"), + OrderState::Cancelled => write!(f, "cancelled"), + OrderState::Untriggered => write!(f, "untriggered"), + } + } +} + +impl FromStr for OrderState { + type Err = DeribitClientError; + + fn from_str(input: &str) -> Result { + match input { + "open" => Ok(OrderState::Open), + "filled" => Ok(OrderState::Filled), + "rejected" => Ok(OrderState::Rejected), + "cancelled" => Ok(OrderState::Cancelled), + "untriggered" => Ok(OrderState::Untriggered), + _ => Err(DeribitClientError::CannotConvertOrderStateFromStr), + } + } +} + #[derive(Debug, Clone)] pub struct LastPrice { pub usd_cents: Decimal,