bybit/
client.rs

1use crate::prelude::*;
2
3/// The main client struct that wraps the reqwest client.
4///
5/// It stores the API key, secret key, and host to make requests to the Bybit API.
6#[derive(Clone)]
7pub struct Client {
8    /// The API key for the Bybit account.
9    pub api_key: String,
10
11    /// The secret key for the Bybit account.
12    pub secret_key: String,
13
14    /// The host to make requests to.
15    pub host: String,
16
17    /// The reqwest client that makes the HTTP requests.
18    pub inner_client: ReqwestClient,
19}
20
21impl Client {
22    /// Create a new instance of `Client`.
23    ///
24    /// # Arguments
25    ///
26    /// * `api_key` - The API key for the Bybit account. It can be `None` if the client is not for authenticated requests.
27    /// * `secret_key` - The secret key for the Bybit account. It can be `None` if the client is not for authenticated requests.
28    /// * `host` - The host to make requests to.
29    ///
30    /// # Returns
31    ///
32    /// A new instance of `Client`.
33    pub fn new(api_key: Option<String>, secret_key: Option<String>, host: String) -> Self {
34        // Create a new instance of the reqwest client.
35        let inner_client = ReqwestClient::builder()
36            .build()
37            .expect("Failed to build reqwest client");
38
39        // Create a new instance of `Client` with the provided arguments.
40        Client {
41            // Set the API key. If `api_key` is `None`, set it to an empty string.
42            api_key: match api_key {
43                Some(api_key) => api_key,
44                None => "".into(),
45            },
46            // Set the secret key. If `secret_key` is `None`, set it to an empty string.
47            secret_key: match secret_key {
48                Some(secret_key) => secret_key,
49                None => "".into(),
50            },
51            // Set the host.
52            host,
53            // Set the reqwest client.
54            inner_client,
55        }
56    }
57
58    /// Makes an unsigned HTTP GET request to the specified endpoint.
59    ///
60    /// # Arguments
61    ///
62    /// * `endpoint` - The endpoint to make the request to.
63    /// * `request` - The query string to append to the URL.
64    ///
65    /// # Returns
66    ///
67    /// A `Result` containing the response deserialized to the specified type `T`.
68    pub async fn get<T: DeserializeOwned + Send + 'static>(
69        &self,
70        endpoint: API,
71        request: Option<String>,
72    ) -> Result<T, BybitError> {
73        // Construct the full URL
74        let mut url = format!("{}/{}", self.host, endpoint.as_ref());
75        // If there is a query string, append it to the URL
76        if let Some(request) = request {
77            if !request.is_empty() {
78                url.push('?');
79                url.push_str(&request);
80            }
81        }
82
83        // Make the request using the reqwest client
84        let response = self.inner_client.get(url).send().await?;
85        // Handle the response using the `handler` method
86        self.handler(response).await
87    }
88
89    /// Makes a signed HTTP GET request to the specified endpoint.
90    ///
91    /// # Arguments
92    ///
93    /// * `endpoint` - The endpoint to make the request to.
94    /// * `recv_window` - The receive window for the request in milliseconds.
95    /// * `request` - The query string to append to the URL.
96    ///
97    /// # Returns
98    ///
99    /// A `Result` containing the response deserialized to the specified type `T`.
100    pub async fn get_signed<T: DeserializeOwned + Send + 'static>(
101        &self,
102        endpoint: API,
103        recv_window: u16,
104        request: Option<String>,
105    ) -> Result<T, BybitError> {
106        // Construct the full URL
107        let mut url: String = format!("{}/{}", self.host, endpoint.as_ref());
108        // If there is a query string, append it to the URL
109        let query_string = request.unwrap_or_default();
110        if !query_string.is_empty() {
111            url.push_str(format!("?{}", query_string).as_str());
112        }
113
114        // Sign the request, passing the query string for signature
115        // The request is signed with the API secret key and requires
116        // the `recv_window` for the request to be within the specified timeframe.
117        let headers = self.build_signed_headers(false, true, recv_window, Some(query_string))?;
118
119        // Make the signed HTTP GET request
120        let client = &self.inner_client;
121        let response = client.get(url.as_str()).headers(headers).send().await?;
122
123        // Handle the response
124        self.handler(response).await
125    }
126
127    /// Makes an unsigned HTTP POST request to the specified endpoint.
128    ///
129    /// # Arguments
130    ///
131    /// * `endpoint` - The endpoint to make the request to.
132    /// * `request` - The query string to append to the URL. Only used if provided.
133    ///
134    /// # Returns
135    ///
136    /// A `Result` containing the response deserialized to the specified type `T`.
137    pub async fn post<T: DeserializeOwned + Send + 'static>(
138        &self,
139        endpoint: API,
140        request: Option<String>,
141    ) -> Result<T, BybitError> {
142        // Construct the URL by appending the base host and endpoint to it
143        let mut url: String = format!("{}/{}", self.host, endpoint.as_ref());
144
145        // If a request is provided, append it to the URL as a query string
146        if let Some(request) = request {
147            if !request.is_empty() {
148                url.push_str(format!("?{}", request).as_str());
149            }
150        }
151
152        // Get a reference to the inner client
153        let client = &self.inner_client;
154
155        // Send the POST request to the constructed URL
156        let response = client.post(url.as_str()).send().await?;
157
158        // Handle the response by passing it to the handler method
159        self.handler(response).await
160    }
161
162    /// Makes a signed HTTP POST request to the specified endpoint.
163    ///
164    /// # Arguments
165    ///
166    /// * `endpoint` - The endpoint to make the request to.
167    /// * `recv_window` - The receive window for the request in milliseconds.
168    /// * `raw_request_body` - The raw request body to sign. Only used if provided.
169    ///
170    /// # Returns
171    ///
172    /// A `Result` containing the response deserialized to the specified type `T`.
173    pub async fn post_signed<T: DeserializeOwned + Send + 'static>(
174        &self,
175        endpoint: API,
176        recv_window: u16,
177        raw_request_body: Option<String>,
178    ) -> Result<T, BybitError> {
179        // Construct the full URL
180        let url = format!("{}{}", self.host, endpoint.as_ref());
181
182        // Sign the request, passing the raw request body for signature
183        // The request is signed with the API secret key and requires
184        // the `recv_window` for the request to be within the specified timeframe.
185        let headers =
186            self.build_signed_headers(true, true, recv_window, raw_request_body.clone())?;
187
188        // Make the signed HTTP POST request
189        let client = &self.inner_client;
190        let response = client
191            .post(url)
192            .headers(headers)
193            .body(raw_request_body.unwrap_or_default())
194            .send()
195            .await?;
196
197        // Handle the response
198        self.handler(response).await
199    }
200
201    /// Builds the signed headers for an HTTP request.
202    ///
203    /// # Arguments
204    ///
205    /// * `content_type` - Whether to include the `Content-Type` header.
206    /// * `signed` - Whether to include the signature in the headers.
207    /// * `recv_window` - The receive window for the request in milliseconds.
208    /// * `request` - The request body to sign.
209    ///
210    /// # Returns
211    ///
212    /// A `Result` containing the signed headers.
213    fn build_signed_headers<'str>(
214        &self,
215        content_type: bool,
216        signed: bool,
217        recv_window: u16,
218        request: Option<String>,
219    ) -> Result<HeaderMap, BybitError> {
220        // Initialize the custom headers map
221        let mut custom_headers = HeaderMap::new();
222        // Set the User-Agent header
223        custom_headers.insert(USER_AGENT, HeaderValue::from_static("bybit-rs"));
224        // Get the current timestamp
225        let timestamp = get_timestamp().to_string();
226        // Get the receive window
227        let window = recv_window.to_string();
228        // Sign the request
229        let signature = self.sign_message(&timestamp, &window, request)?;
230
231        // Set the headers
232        let signature_header = HeaderName::from_static("x-bapi-sign");
233        let api_key_header = HeaderName::from_static("x-bapi-api-key");
234        let timestamp_header = HeaderName::from_static("x-bapi-timestamp");
235        let recv_window_header = HeaderName::from_static("x-bapi-recv-window");
236
237        if signed {
238            // Insert the signature header
239            custom_headers.insert(
240                signature_header,
241                HeaderValue::from_str(&signature.to_owned())?,
242            );
243            // Insert the API key header
244            custom_headers.insert(
245                api_key_header,
246                HeaderValue::from_str(&self.api_key.to_owned())?,
247            );
248        }
249        // Insert the timestamp header
250        custom_headers.insert(
251            timestamp_header,
252            HeaderValue::from_str(&timestamp.to_owned())?,
253        );
254        // Insert the receive window header
255        custom_headers.insert(
256            recv_window_header,
257            HeaderValue::from_str(&window.to_owned())?,
258        );
259        // Insert the Content-Type header if required
260        if content_type {
261            custom_headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
262        }
263        // Return the signed headers
264        Ok(custom_headers).map_err(BybitError::ReqError)
265    }
266
267    fn mac_from_secret_key(&self) -> Result<Hmac<Sha256>, BybitError> {
268        Hmac::<Sha256>::new_from_slice(self.secret_key.as_bytes())
269            .map_err(|e| BybitError::Base(format!("Failed to create Hmac, error: {:?}", e)))
270    }
271
272    /// Signs a POST request message.
273    ///
274    /// # Arguments
275    ///
276    /// * `timestamp` - The timestamp of the request.
277    /// * `recv_window` - The receive window of the request.
278    /// * `request` - The request body as an optional string.
279    ///
280    /// # Returns
281    ///
282    /// The signed message as a hex-encoded string.
283    ///
284    /// # Description
285    ///
286    /// This function takes the timestamp, receive window, and an optional request body as input.
287    /// It creates a string by concatenating the timestamp, API key, and receive window.
288    /// If a request body is provided, it appends it to the sign message.
289    /// The function then uses the HMAC-SHA256 algorithm to sign the message.
290    /// The result is hex-encoded and returned as a string.
291    fn sign_message(
292        &self,
293        timestamp: &str,
294        recv_window: &str,
295        request: Option<String>,
296    ) -> Result<String, BybitError> {
297        // Create a new HMAC SHA256 instance with the secret key
298        let mut mac = self.mac_from_secret_key()?;
299
300        // Create the sign message by concatenating the timestamp, API key, and receive window
301        let mut sign_message = format!("{}{}{}", timestamp, self.api_key, recv_window);
302
303        // If a request body is provided, append it to the sign message
304        if let Some(req) = request {
305            sign_message.push_str(&req);
306        }
307
308        // Update the MAC with the sign message
309        mac.update(sign_message.as_bytes());
310
311        // Finalize the MAC and encode the result as a hex string
312        let hex_signature = hex_encode(mac.finalize().into_bytes());
313
314        Ok(hex_signature)
315    }
316
317    /// Internal function to sign a POST request message.
318    ///
319    /// # Arguments
320    ///
321    /// * `timestamp` - The timestamp of the request.
322    /// * `recv_window` - The receive window of the request.
323    /// * `request` - The request body as an optional string.
324    ///
325    /// # Returns
326    ///
327    /// The signed message as a hex-encoded string.
328    fn _sign_post_message(
329        &self,
330        timestamp: &str,
331        recv_window: &str,
332        request: Option<String>,
333    ) -> Result<String, BybitError> {
334        // Create a new HMAC SHA256 instance with the secret key
335        let mut mac = self.mac_from_secret_key()?;
336
337        // Update the MAC with the timestamp
338        mac.update(timestamp.as_bytes());
339        // Update the MAC with the API key
340        mac.update(self.api_key.as_bytes());
341        // Update the MAC with the receive window
342        mac.update(recv_window.as_bytes());
343        // Update the MAC with the request body, if provided
344        if let Some(req) = request {
345            mac.update(req.as_bytes());
346        }
347
348        // Finalize the MAC and encode the result as a hex string
349        let hex_signature = hex_encode(mac.finalize().into_bytes());
350
351        Ok(hex_signature)
352    }
353
354    /// Internal function to handle the response from a HTTP request.
355    ///
356    /// # Arguments
357    ///
358    /// * `response` - The HTTP response from the request.
359    ///
360    /// # Returns
361    ///
362    /// The result of deserializing the response body into a specific type.
363    /// Returns `Ok(T)` if the response status is `StatusCode::OK`,
364
365    /// returns `Err(BybitError::BybitError(BybitContentError))` if the response
366    /// status is `StatusCode::BAD_REQUEST`, returns `Err(BybitError::InternalServerError)`
367    /// if the response status is `StatusCode::INTERNAL_SERVER_ERROR`,
368
369    /// returns `Err(BybitError::ServiceUnavailable)` if the response status is
370    /// `StatusCode::SERVICE_UNAVAILABLE`, returns `Err(BybitError::Unauthorized)`
371    /// if the response status is `StatusCode::UNAUTHORIZED`, and returns
372    /// `Err(BybitError::StatusCode(status))` if the response status is any other
373    /// value.
374
375    async fn handler<T: DeserializeOwned + Send + 'static>(
376        &self,
377        response: ReqwestResponse,
378    ) -> Result<T, BybitError> {
379        // Match the status code of the response
380        let status = response.status();
381        let response_text = &response.text().await.map_err(BybitError::from)?;
382        match status {
383            StatusCode::OK => {
384                // Deserialize the entire response into BybitApiResponseOpaquePayload first.
385                let wrapper: BybitApiResponseOpaquePayload = serde_json::from_str(response_text)
386                    .map_err(|e| {
387                        BybitError::Base(format!(
388                            "Failed to parse response wrapper: {} | Response: {}",
389                            e, response_text
390                        ))
391                    })?;
392
393                match wrapper.ret_code {
394                    0 => {
395                        // If ret_code is 0, the operation was successful at the API level.
396                        // The actual data is in `response_json.result` (a serde_json::Value).
397                        // Deserialize this `Value` into the target type `T`.
398                        serde_json::from_str(response_text).map_err(|e| {
399                            BybitError::Base(format!(
400                                "Failed to parse full response into {}: {} | Response: {}",
401                                type_name::<T>(),
402                                e,
403                                response_text
404                            ))
405                        })
406                    }
407                    _ => {
408                        // If ret_code is non-zero, it's an API error.
409                        Err(BybitError::BybitError(BybitContentError {
410                            code: wrapper.ret_code,
411                            msg: wrapper.ret_msg,
412                        }))
413                    }
414                }
415            }
416            StatusCode::BAD_REQUEST => {
417                let error: BybitContentError =
418                    serde_json::from_str(response_text).map_err(BybitError::from)?;
419                Err(BybitError::BybitError(error))
420            }
421            StatusCode::INTERNAL_SERVER_ERROR => Err(BybitError::InternalServerError),
422            StatusCode::SERVICE_UNAVAILABLE => Err(BybitError::ServiceUnavailable),
423            StatusCode::UNAUTHORIZED => Err(BybitError::Unauthorized),
424            status => Err(BybitError::StatusCode(status.as_u16())),
425        }
426    }
427
428    /// Connects to the Bybit WebSocket endpoint and sends an authentication message.
429    ///
430    /// # Arguments
431    ///
432    /// * `endpoint` - The WebSocket endpoint to connect to.
433    /// * `request_body` - An optional request body to send after authenticating.
434    /// * `private` - A boolean indicating whether to send the authentication message.
435    /// * `alive_dur` - An optional duration in seconds to set the `alive` field of the
436    ///   authentication message to.
437    ///
438    /// # Returns
439    ///
440    /// Returns a `Result` containing a `WebSocketStream` if the connection and authentication
441    /// are successful, or a `BybitError` if an error occurs.
442    pub async fn wss_connect(
443        &self,
444        endpoint: WebsocketAPI,
445        request_body: Option<String>,
446        private: bool,
447        alive_dur: Option<u16>,
448    ) -> Result<WebSocketStream<MaybeTlsStream<TcpStream>>, BybitError> {
449        // Construct the WebSocket URL
450        let unparsed_url = format!("{}{}", self.host, endpoint.as_ref()).to_string();
451        let url = WsUrl::parse(unparsed_url.as_str())?;
452
453        // Calculate the expiration time for the authentication message
454        let expiry_time = alive_dur.unwrap_or(9) as u64 * 1000 * 60;
455        let expires = get_timestamp() + expiry_time;
456
457        // Calculate the signature for the authentication message
458        let mut mac = self.mac_from_secret_key()?;
459        mac.update(format!("GET/realtime{expires}").as_bytes());
460        let signature = hex_encode(mac.finalize().into_bytes());
461
462        // Generate a random UUID for the request ID
463        let uuid = generate_random_uid(5);
464
465        // Connect to the WebSocket endpoint
466        match connect_async(url.as_ref()).await {
467            // If the connection is successful, send the authentication message
468            Ok((mut ws_stream, _)) => {
469                let auth_msg = json!({
470                    "req_id": uuid,
471                    "op": "auth",
472                    "args": [self.api_key, expires, signature]
473                });
474                if private {
475                    // Send the authentication message if `private` is true
476                    ws_stream
477                        .send(WsMessage::Text(auth_msg.to_string()))
478                        .await?;
479                }
480                // Send the request body if it is not empty
481                let request = request_body.unwrap_or_default();
482                if !request.is_empty() {
483                    ws_stream.send(WsMessage::Text(request)).await?;
484                }
485                Ok(ws_stream)
486            }
487            // If the connection fails, return a BybitError
488            Err(err) => Err(BybitError::Tungstenite(err)),
489        }
490    }
491}