bybit_rust_api/rest/user/
user_client.rs

1use crate::rest::client::{RestClient, SecType, ServerResponse};
2use anyhow::Result;
3use serde_json::json;
4
5pub struct UserClient {
6    client: RestClient,
7}
8
9impl UserClient {
10    pub fn new(client: RestClient) -> Self {
11        UserClient { client }
12    }
13
14    /// Create a new sub user
15    ///
16    /// API: POST /v5/user/create-sub-member
17    /// https://bybit-exchange.github.io/docs/v5/user/create-subuid
18    pub async fn create_sub_member(
19        &self,
20        username: &str,
21        member_type: i32, // 1: normal sub account, 6: custodial sub account
22        password: Option<&str>,
23        note: Option<&str>,
24        switch_option: Option<i32>,
25        is_uta: Option<bool>,
26    ) -> Result<ServerResponse<serde_json::Value>> {
27        let endpoint = "v5/user/create-sub-member";
28        let mut body = json!({
29            "username": username,
30            "memberType": member_type,
31        });
32
33        if let Some(password) = password {
34            body["password"] = json!(password);
35        }
36        if let Some(note) = note {
37            body["note"] = json!(note);
38        }
39        if let Some(switch_option) = switch_option {
40            body["switch"] = json!(switch_option);
41        }
42        if let Some(is_uta) = is_uta {
43            body["isUta"] = json!(is_uta);
44        }
45
46        let response = self.client.post(endpoint, body, SecType::Signed).await?;
47        Ok(response)
48    }
49
50    /// Create sub UID API key
51    ///
52    /// API: POST /v5/user/create-sub-api
53    /// https://bybit-exchange.github.io/docs/v5/user/create-subuid-apikey
54    pub async fn create_sub_api(
55        &self,
56        sub_uid: i64,
57        note: Option<&str>,
58        read_only: i32,
59        permissions: serde_json::Value,
60        ips: Option<Vec<String>>,
61    ) -> Result<ServerResponse<serde_json::Value>> {
62        let endpoint = "v5/user/create-sub-api";
63        let mut body = json!({
64            "subuid": sub_uid,
65            "readOnly": read_only,
66            "permissions": permissions,
67        });
68
69        if let Some(note) = note {
70            body["note"] = json!(note);
71        }
72        if let Some(ips) = ips {
73            body["ips"] = json!(ips);
74        }
75
76        let response = self.client.post(endpoint, body, SecType::Signed).await?;
77        Ok(response)
78    }
79
80    /// Get sub UID list
81    ///
82    /// API: GET /v5/user/query-sub-members
83    /// https://bybit-exchange.github.io/docs/v5/user/subuid-list
84    pub async fn query_sub_members(
85        &self,
86        page_size: Option<i32>,
87        page: Option<i32>,
88    ) -> Result<ServerResponse<serde_json::Value>> {
89        let endpoint = "v5/user/query-sub-members";
90        let mut params = json!({});
91
92        if let Some(page_size) = page_size {
93            params["pageSize"] = json!(page_size);
94        }
95        if let Some(page) = page {
96            params["page"] = json!(page);
97        }
98
99        let response = self.client.get(endpoint, params, SecType::Signed).await?;
100        Ok(response)
101    }
102
103    /// Get all sub UID list (Alternative method)
104    ///
105    /// API: GET /v5/user/submembers
106    /// https://bybit-exchange.github.io/docs/v5/user/subuid-list-all
107    pub async fn get_sub_members(
108        &self,
109        uid: Option<&str>,
110    ) -> Result<ServerResponse<serde_json::Value>> {
111        let endpoint = "v5/user/submembers";
112        let mut params = json!({});
113
114        if let Some(uid) = uid {
115            params["uid"] = json!(uid);
116        }
117
118        let response = self.client.get(endpoint, params, SecType::Signed).await?;
119        Ok(response)
120    }
121
122    /// Get API key information
123    ///
124    /// API: GET /v5/user/query-api
125    /// https://bybit-exchange.github.io/docs/v5/user/apikey-info
126    pub async fn query_api(&self) -> Result<ServerResponse<serde_json::Value>> {
127        let endpoint = "v5/user/query-api";
128        let response = self
129            .client
130            .get(endpoint, json!({}), SecType::Signed)
131            .await?;
132        Ok(response)
133    }
134
135    /// Get sub account API keys
136    ///
137    /// API: GET /v5/user/sub-apikeys
138    /// https://bybit-exchange.github.io/docs/v5/user/sub-apikey-list
139    pub async fn get_sub_api_keys(
140        &self,
141        sub_member_id: &str,
142        limit: Option<i32>,
143        cursor: Option<&str>,
144    ) -> Result<ServerResponse<serde_json::Value>> {
145        let endpoint = "v5/user/sub-apikeys";
146        let mut params = json!({
147            "subMemberId": sub_member_id,
148        });
149
150        if let Some(limit) = limit {
151            params["limit"] = json!(limit);
152        }
153        if let Some(cursor) = cursor {
154            params["cursor"] = json!(cursor);
155        }
156
157        let response = self.client.get(endpoint, params, SecType::Signed).await?;
158        Ok(response)
159    }
160
161    /// Get user account type
162    ///
163    /// API: GET /v5/user/get-member-type
164    /// https://bybit-exchange.github.io/docs/v5/user/account-type
165    pub async fn get_member_type(&self) -> Result<ServerResponse<serde_json::Value>> {
166        let endpoint = "v5/user/get-member-type";
167        let response = self
168            .client
169            .get(endpoint, json!({}), SecType::Signed)
170            .await?;
171        Ok(response)
172    }
173
174    /// Get affiliate user info
175    ///
176    /// API: GET /v5/user/aff-customer-info
177    /// https://bybit-exchange.github.io/docs/v5/user/affiliate-info
178    pub async fn get_affiliate_customer_info(
179        &self,
180        uid: &str,
181    ) -> Result<ServerResponse<serde_json::Value>> {
182        let endpoint = "v5/user/aff-customer-info";
183        let params = json!({
184            "uid": uid,
185        });
186
187        let response = self.client.get(endpoint, params, SecType::Signed).await?;
188        Ok(response)
189    }
190
191    /// Freeze sub UID
192    ///
193    /// API: POST /v5/user/frozen-sub-member
194    /// https://bybit-exchange.github.io/docs/v5/user/frozen-subuid
195    pub async fn freeze_sub_member(
196        &self,
197        sub_uid: i64,
198        frozen: i32, // 0: unfreeze, 1: freeze
199    ) -> Result<ServerResponse<serde_json::Value>> {
200        let endpoint = "v5/user/frozen-sub-member";
201        let body = json!({
202            "subuid": sub_uid,
203            "frozen": frozen,
204        });
205
206        let response = self.client.post(endpoint, body, SecType::Signed).await?;
207        Ok(response)
208    }
209
210    /// Delete sub member
211    ///
212    /// API: POST /v5/user/del-submember
213    /// https://bybit-exchange.github.io/docs/v5/user/del-submember
214    pub async fn delete_sub_member(
215        &self,
216        sub_member_id: &str,
217    ) -> Result<ServerResponse<serde_json::Value>> {
218        let endpoint = "v5/user/del-submember";
219        let body = json!({
220            "subMemberId": sub_member_id,
221        });
222
223        let response = self.client.post(endpoint, body, SecType::Signed).await?;
224        Ok(response)
225    }
226
227    /// Modify master API key
228    ///
229    /// API: POST /v5/user/update-api
230    /// https://bybit-exchange.github.io/docs/v5/user/modify-master-apikey
231    pub async fn update_api(
232        &self,
233        read_only: Option<i32>,
234        ips: Option<Vec<String>>,
235        permissions: Option<serde_json::Value>,
236    ) -> Result<ServerResponse<serde_json::Value>> {
237        let endpoint = "v5/user/update-api";
238        let mut body = json!({});
239
240        if let Some(read_only) = read_only {
241            body["readOnly"] = json!(read_only);
242        }
243        if let Some(ips) = ips {
244            body["ips"] = json!(ips);
245        }
246        if let Some(permissions) = permissions {
247            body["permissions"] = json!(permissions);
248        }
249
250        let response = self.client.post(endpoint, body, SecType::Signed).await?;
251        Ok(response)
252    }
253
254    /// Delete master API key
255    ///
256    /// API: POST /v5/user/delete-api
257    /// https://bybit-exchange.github.io/docs/v5/user/rm-master-apikey
258    pub async fn delete_api(&self) -> Result<ServerResponse<serde_json::Value>> {
259        let endpoint = "v5/user/delete-api";
260        let body = json!({});
261
262        let response = self.client.post(endpoint, body, SecType::Signed).await?;
263        Ok(response)
264    }
265
266    /// Modify sub API key
267    ///
268    /// API: POST /v5/user/update-sub-api
269    /// https://bybit-exchange.github.io/docs/v5/user/modify-sub-apikey
270    pub async fn update_sub_api(
271        &self,
272        api_key: &str,
273        read_only: Option<i32>,
274        ips: Option<Vec<String>>,
275        permissions: Option<serde_json::Value>,
276    ) -> Result<ServerResponse<serde_json::Value>> {
277        let endpoint = "v5/user/update-sub-api";
278        let mut body = json!({
279            "apikey": api_key,
280        });
281
282        if let Some(read_only) = read_only {
283            body["readOnly"] = json!(read_only);
284        }
285        if let Some(ips) = ips {
286            body["ips"] = json!(ips);
287        }
288        if let Some(permissions) = permissions {
289            body["permissions"] = json!(permissions);
290        }
291
292        let response = self.client.post(endpoint, body, SecType::Signed).await?;
293        Ok(response)
294    }
295
296    /// Delete sub API key
297    ///
298    /// API: POST /v5/user/delete-sub-api
299    /// https://bybit-exchange.github.io/docs/v5/user/rm-sub-apikey
300    pub async fn delete_sub_api(&self, api_key: &str) -> Result<ServerResponse<serde_json::Value>> {
301        let endpoint = "v5/user/delete-sub-api";
302        let body = json!({
303            "apikey": api_key,
304        });
305
306        let response = self.client.post(endpoint, body, SecType::Signed).await?;
307        Ok(response)
308    }
309}
310
311#[cfg(test)]
312mod tests {
313    use super::*;
314    use crate::rest::ApiKeyPair;
315
316    fn create_test_client() -> UserClient {
317        let api_key_pair = ApiKeyPair::new(
318            "test".to_string(),
319            "test_key".to_string(),
320            "test_secret".to_string(),
321        );
322        let rest_client = RestClient::new(
323            api_key_pair,
324            "https://api-testnet.bybit.com".to_string(),
325            false,
326        );
327        UserClient::new(rest_client)
328    }
329
330    #[test]
331    fn test_user_client_creation() {
332        let _client = create_test_client();
333    }
334
335    #[tokio::test]
336    async fn test_create_sub_member_params() {
337        let username = "test_sub_user";
338        let member_type = 1;
339        let password = Some("secure_password");
340        let note = Some("Test sub account");
341        let switch_option = Some(1);
342        let is_uta = Some(true);
343
344        assert_eq!(username, "test_sub_user");
345        assert_eq!(member_type, 1);
346        assert_eq!(password, Some("secure_password"));
347        assert_eq!(note, Some("Test sub account"));
348        assert_eq!(switch_option, Some(1));
349        assert_eq!(is_uta, Some(true));
350    }
351
352    #[tokio::test]
353    async fn test_create_sub_api_params() {
354        let sub_uid = 12345678i64;
355        let note = Some("API for trading");
356        let read_only = 0;
357        let permissions = json!({
358            "ContractTrade": ["Order", "Position"],
359            "Spot": ["SpotTrade"]
360        });
361        let ips = Some(vec!["192.168.1.1".to_string(), "10.0.0.1".to_string()]);
362
363        assert_eq!(sub_uid, 12345678);
364        assert_eq!(note, Some("API for trading"));
365        assert_eq!(read_only, 0);
366        assert!(permissions.is_object());
367        assert_eq!(ips.as_ref().unwrap().len(), 2);
368    }
369
370    #[tokio::test]
371    async fn test_freeze_sub_member_params() {
372        let sub_uid = 87654321i64;
373        let frozen_freeze = 1;
374        let frozen_unfreeze = 0;
375
376        assert_eq!(sub_uid, 87654321);
377        assert_eq!(frozen_freeze, 1);
378        assert_eq!(frozen_unfreeze, 0);
379    }
380
381    #[tokio::test]
382    async fn test_query_sub_members_params() {
383        let page_size = Some(20);
384        let page = Some(1);
385
386        assert_eq!(page_size, Some(20));
387        assert_eq!(page, Some(1));
388    }
389
390    #[tokio::test]
391    async fn test_get_sub_api_keys_params() {
392        let sub_member_id = "sub_member_123";
393        let limit = Some(50);
394        let cursor = Some("next_page_cursor");
395
396        assert_eq!(sub_member_id, "sub_member_123");
397        assert_eq!(limit, Some(50));
398        assert_eq!(cursor, Some("next_page_cursor"));
399    }
400
401    #[tokio::test]
402    async fn test_update_api_params() {
403        let read_only = Some(1);
404        let ips = Some(vec!["10.0.0.1".to_string()]);
405        let permissions = Some(json!({
406            "Spot": ["SpotTrade"]
407        }));
408
409        assert_eq!(read_only, Some(1));
410        assert_eq!(ips.as_ref().unwrap().len(), 1);
411        assert!(permissions.as_ref().unwrap().is_object());
412    }
413
414    #[tokio::test]
415    async fn test_delete_sub_api_params() {
416        let api_key = "test_api_key_123";
417
418        assert_eq!(api_key, "test_api_key_123");
419    }
420}