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