bybit/
client.rs

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