bybit_rust_api/rest/user/
user_client.rs

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