bybit_rust_api/rest/account/
account_client.rs

1use crate::rest::account::dto::account_info::AccountInfoResult;
2use crate::rest::account::dto::account_wallet::{GetWalletBalanceParams, WalletBalanceResult};
3use crate::rest::account::dto::collateral::{BorrowHistoryResult, CollateralInfoResult};
4use crate::rest::account::dto::contract_transaction_log::{ContractTransactionLogResult, GetContractTransactionLogParams};
5use crate::rest::account::dto::fee_rate::FeeRateResult;
6use crate::rest::account::dto::mmp::{MmpStateResult, ModifyMmpParams};
7use crate::rest::account::dto::transaction_log::{GetTransactionLogParams, TransactionLogResult};
8use crate::rest::client::{RestClient, SecType, ServerResponse};
9use anyhow::Result;
10use serde_json::{json, to_value};
11
12pub struct AccountClient {
13    client: RestClient,
14}
15
16impl AccountClient {
17    pub fn new(client: RestClient) -> Self {
18        AccountClient { client }
19    }
20
21    /// Get wallet balance
22    ///
23    /// API: GET /v5/account/wallet-balance
24    /// https://bybit-exchange.github.io/docs/v5/account/wallet-balance
25    pub async fn get_wallet_balance(
26        &self,
27        params: GetWalletBalanceParams,
28    ) -> Result<ServerResponse<WalletBalanceResult>> {
29        let endpoint = "v5/account/wallet-balance";
30        let query = to_value(&params)?;
31        let response = self.client.get(endpoint, query, SecType::Signed).await?;
32        Ok(response)
33    }
34
35    /// Get fee rate
36    ///
37    /// API: GET /v5/account/fee-rate
38    /// https://bybit-exchange.github.io/docs/v5/account/fee-rate
39    pub async fn get_fee_rate(
40        &self,
41        category: &str,
42        symbol: Option<&str>,
43        base_coin: Option<&str>,
44    ) -> Result<ServerResponse<FeeRateResult>> {
45        let endpoint = "v5/account/fee-rate";
46        let mut params = json!({
47            "category": category,
48        });
49
50        if let Some(symbol) = symbol {
51            params["symbol"] = json!(symbol);
52        }
53        if let Some(base_coin) = base_coin {
54            params["baseCoin"] = json!(base_coin);
55        }
56
57        let response = self.client.get(endpoint, params, SecType::Signed).await?;
58        Ok(response)
59    }
60
61    /// Get account info
62    ///
63    /// API: GET /v5/account/info
64    /// https://bybit-exchange.github.io/docs/v5/account/account-info
65    pub async fn get_account_info(&self) -> Result<ServerResponse<AccountInfoResult>> {
66        let endpoint = "v5/account/info";
67        let response = self
68            .client
69            .get(endpoint, json!({}), SecType::Signed)
70            .await?;
71        Ok(response)
72    }
73
74    /// Get transaction log
75    ///
76    /// API: GET /v5/account/transaction-log
77    /// https://bybit-exchange.github.io/docs/v5/account/transaction-log
78    pub async fn get_transaction_log(
79        &self,
80        params: GetTransactionLogParams,
81    ) -> Result<ServerResponse<TransactionLogResult>> {
82        let endpoint = "v5/account/transaction-log";
83        let query = to_value(&params)?;
84        let response = self.client.get(endpoint, query, SecType::Signed).await?;
85        Ok(response)
86    }
87
88    /// Set margin mode
89    ///
90    /// API: POST /v5/account/set-margin-mode
91    /// https://bybit-exchange.github.io/docs/v5/account/set-margin-mode
92    pub async fn set_margin_mode(&self, margin_mode: &str) -> Result<ServerResponse<serde_json::Value>> {
93        let endpoint = "v5/account/set-margin-mode";
94        let body = json!({
95            "setMarginMode": margin_mode,
96        });
97
98        let response = self.client.post(endpoint, body, SecType::Signed).await?;
99        Ok(response)
100    }
101
102    /// Set MMP
103    ///
104    /// API: POST /v5/account/mmp-modify
105    /// https://bybit-exchange.github.io/docs/v5/account/set-mmp
106    pub async fn set_mmp(&self, params: ModifyMmpParams) -> Result<ServerResponse<serde_json::Value>> {
107        let endpoint = "v5/account/mmp-modify";
108        let body = to_value(&params)?;
109        let response = self.client.post(endpoint, body, SecType::Signed).await?;
110        Ok(response)
111    }
112
113    /// Reset MMP
114    ///
115    /// API: POST /v5/account/mmp-reset
116    /// https://bybit-exchange.github.io/docs/v5/account/reset-mmp
117    pub async fn reset_mmp(&self, base_coin: &str) -> Result<ServerResponse<serde_json::Value>> {
118        let endpoint = "v5/account/mmp-reset";
119        let body = json!({
120            "baseCoin": base_coin,
121        });
122
123        let response = self.client.post(endpoint, body, SecType::Signed).await?;
124        Ok(response)
125    }
126
127    /// Get MMP state
128    ///
129    /// API: GET /v5/account/mmp-state
130    /// https://bybit-exchange.github.io/docs/v5/account/get-mmp-state
131    pub async fn get_mmp_state(
132        &self,
133        base_coin: &str,
134    ) -> Result<ServerResponse<MmpStateResult>> {
135        let endpoint = "v5/account/mmp-state";
136        let params = json!({
137            "baseCoin": base_coin,
138        });
139
140        let response = self.client.get(endpoint, params, SecType::Signed).await?;
141        Ok(response)
142    }
143
144    /// Get SMP group list
145    ///
146    /// API: GET /v5/account/smp-group
147    /// https://bybit-exchange.github.io/docs/v5/account/smp-group
148    pub async fn get_smp_group_list(&self) -> Result<ServerResponse<serde_json::Value>> {
149        let endpoint = "v5/account/smp-group";
150        let response = self
151            .client
152            .get(endpoint, json!({}), SecType::Signed)
153            .await?;
154        Ok(response)
155    }
156
157    /// Get coin Greeks
158    ///
159    /// API: GET /v5/asset/coin-greeks
160    /// https://bybit-exchange.github.io/docs/v5/account/coin-greeks
161    pub async fn get_coin_greeks(
162        &self,
163        base_coin: Option<&str>,
164    ) -> Result<ServerResponse<serde_json::Value>> {
165        let endpoint = "v5/asset/coin-greeks";
166        let mut params = json!({});
167
168        if let Some(base_coin) = base_coin {
169            params["baseCoin"] = json!(base_coin);
170        }
171
172        let response = self.client.get(endpoint, params, SecType::Signed).await?;
173        Ok(response)
174    }
175
176    /// Get collateral info
177    ///
178    /// API: GET /v5/account/collateral-info
179    /// https://bybit-exchange.github.io/docs/v5/account/collateral-info
180    pub async fn get_collateral_info(
181        &self,
182        currency: Option<&str>,
183    ) -> Result<ServerResponse<CollateralInfoResult>> {
184        let endpoint = "v5/account/collateral-info";
185        let mut params = json!({});
186
187        if let Some(currency) = currency {
188            params["currency"] = json!(currency);
189        }
190
191        let response = self.client.get(endpoint, params, SecType::Signed).await?;
192        Ok(response)
193    }
194
195    /// Get borrow history
196    ///
197    /// API: GET /v5/account/borrow-history
198    /// https://bybit-exchange.github.io/docs/v5/account/borrow-history
199    pub async fn get_borrow_history(
200        &self,
201        currency: Option<&str>,
202        start_time: Option<i64>,
203        end_time: Option<i64>,
204        limit: Option<i32>,
205        cursor: Option<&str>,
206    ) -> Result<ServerResponse<BorrowHistoryResult>> {
207        let endpoint = "v5/account/borrow-history";
208        let mut params = json!({});
209
210        if let Some(currency) = currency {
211            params["currency"] = json!(currency);
212        }
213        if let Some(start_time) = start_time {
214            params["startTime"] = json!(start_time);
215        }
216        if let Some(end_time) = end_time {
217            params["endTime"] = json!(end_time);
218        }
219        if let Some(limit) = limit {
220            params["limit"] = json!(limit);
221        }
222        if let Some(cursor) = cursor {
223            params["cursor"] = json!(cursor);
224        }
225
226        let response = self.client.get(endpoint, params, SecType::Signed).await?;
227        Ok(response)
228    }
229
230    /// Set disconnect cancel all
231    ///
232    /// API: POST /v5/order/disconnected-cancel-all
233    /// https://bybit-exchange.github.io/docs/v5/account/set-dcp
234    pub async fn set_disconnect_cancel_all(
235        &self,
236        time_window: i32,
237    ) -> Result<ServerResponse<serde_json::Value>> {
238        let endpoint = "v5/order/disconnected-cancel-all";
239        let body = json!({
240            "timeWindow": time_window,
241        });
242
243        let response = self.client.post(endpoint, body, SecType::Signed).await?;
244        Ok(response)
245    }
246
247    /// Upgrade to unified account
248    ///
249    /// API: POST /v5/account/upgrade-to-uta
250    /// https://bybit-exchange.github.io/docs/v5/account/upgrade-unified-account
251    pub async fn upgrade_to_unified_account(&self) -> Result<ServerResponse<serde_json::Value>> {
252        let endpoint = "v5/account/upgrade-to-uta";
253        let body = json!({});
254
255        let response = self.client.post(endpoint, body, SecType::Signed).await?;
256        Ok(response)
257    }
258
259    /// Get contract transaction log
260    ///
261    /// API: GET /v5/account/contract-transaction-log
262    /// https://bybit-exchange.github.io/docs/v5/account/contract-transaction-log
263    pub async fn get_contract_transaction_log(
264        &self,
265        params: GetContractTransactionLogParams,
266    ) -> Result<ServerResponse<ContractTransactionLogResult>> {
267        let endpoint = "v5/account/contract-transaction-log";
268        let query = to_value(&params)?;
269        let response = self.client.get(endpoint, query, SecType::Signed).await?;
270        Ok(response)
271    }
272
273    /// Query DCP information
274    ///
275    /// API: GET /v5/account/query-dcp-info
276    /// https://bybit-exchange.github.io/docs/v5/account/query-dcp-info
277    pub async fn query_dcp_info(&self) -> Result<ServerResponse<serde_json::Value>> {
278        let endpoint = "v5/account/query-dcp-info";
279        let response = self
280            .client
281            .get(endpoint, json!({}), SecType::Signed)
282            .await?;
283        Ok(response)
284    }
285}
286
287#[cfg(test)]
288mod tests {
289    use super::*;
290    use crate::rest::ApiKeyPair;
291    use crate::rest::enums::account_type::AccountType;
292    use crate::rest::enums::category::Category;
293
294    fn create_test_client() -> AccountClient {
295        let api_key_pair = ApiKeyPair::new(
296            "test".to_string(),
297            "test_key".to_string(),
298            "test_secret".to_string(),
299        );
300        let rest_client = RestClient::new(
301            api_key_pair,
302            "https://api-testnet.bybit.com".to_string(),
303            false,
304        );
305        AccountClient::new(rest_client)
306    }
307
308    #[test]
309    fn test_account_client_creation() {
310        let _client = create_test_client();
311    }
312
313    #[tokio::test]
314    async fn test_get_wallet_balance_params() {
315        let _client = create_test_client();
316        let params = GetWalletBalanceParams {
317            account_type: AccountType::UNIFIED,
318            coin: Some("USDT".to_string()),
319        };
320
321        assert_eq!(params.account_type, AccountType::UNIFIED);
322        assert_eq!(params.coin, Some("USDT".to_string()));
323    }
324
325    #[tokio::test]
326    async fn test_get_fee_rate_params() {
327        let category = "spot";
328        let symbol = Some("BTCUSDT");
329        let base_coin: Option<&str> = None;
330
331        assert_eq!(category, "spot");
332        assert_eq!(symbol, Some("BTCUSDT"));
333        assert_eq!(base_coin, None);
334    }
335
336    #[tokio::test]
337    async fn test_transaction_log_params() {
338        let params = GetTransactionLogParams {
339            account_type: Some(AccountType::UNIFIED),
340            category: Some(Category::Spot),
341            currency: Some("USDT".to_string()),
342            log_type: Some("TRADE".to_string()),
343            limit: Some(50),
344            ..Default::default()
345        };
346
347        assert_eq!(params.account_type, Some(AccountType::UNIFIED));
348        assert_eq!(params.category, Some(Category::Spot));
349        assert_eq!(params.currency, Some("USDT".to_string()));
350        assert_eq!(params.log_type, Some("TRADE".to_string()));
351        assert_eq!(params.limit, Some(50));
352    }
353
354    #[tokio::test]
355    async fn test_set_margin_mode_params() {
356        let set_margin_mode = "PORTFOLIO_MARGIN";
357        assert_eq!(set_margin_mode, "PORTFOLIO_MARGIN");
358    }
359
360    #[tokio::test]
361    async fn test_set_mmp_params() {
362        let params = ModifyMmpParams {
363            base_coin: "BTC".to_string(),
364            window: 5000,
365            frozen_period: 100000,
366            qty_limit: "100".to_string(),
367            delta_limit: "10".to_string(),
368        };
369
370        assert_eq!(params.base_coin, "BTC");
371        assert_eq!(params.window, 5000);
372        assert_eq!(params.frozen_period, 100000);
373        assert_eq!(params.qty_limit, "100");
374        assert_eq!(params.delta_limit, "10");
375    }
376
377    #[tokio::test]
378    async fn test_borrow_history_params() {
379        let currency = Some("USDT");
380        let start_time = Some(1234567890i64);
381        let end_time = Some(1234567899i64);
382        let limit = Some(100);
383        let cursor = Some("next_page");
384
385        assert_eq!(currency, Some("USDT"));
386        assert_eq!(start_time, Some(1234567890));
387        assert_eq!(end_time, Some(1234567899));
388        assert_eq!(limit, Some(100));
389        assert_eq!(cursor, Some("next_page"));
390    }
391
392    #[tokio::test]
393    async fn test_set_disconnect_cancel_all_params() {
394        let time_window = 10;
395        assert_eq!(time_window, 10);
396    }
397
398    #[tokio::test]
399    async fn test_contract_transaction_log_params() {
400        let params = GetContractTransactionLogParams {
401            category: Some(Category::Linear),
402            base_coin: Some("BTC".to_string()),
403            log_type: Some("SETTLEMENT".to_string()),
404            limit: Some(50),
405            ..Default::default()
406        };
407
408        assert_eq!(params.category, Some(Category::Linear));
409        assert_eq!(params.base_coin, Some("BTC".to_string()));
410        assert_eq!(params.log_type, Some("SETTLEMENT".to_string()));
411        assert_eq!(params.limit, Some(50));
412    }
413}