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