bybit/
client.rs

1
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    /// Signs a POST request message.
285    ///
286    /// # Arguments
287    ///
288    /// * `timestamp` - The timestamp of the request.
289    /// * `recv_window` - The receive window of the request.
290    /// * `request` - The request body as an optional string.
291    ///
292    /// # Returns
293    ///
294    /// The signed message as a hex-encoded string.
295    ///
296    /// # Description
297    ///
298    /// This function takes the timestamp, receive window, and an optional request body as input.
299    /// It creates a string by concatenating the timestamp, API key, and receive window.
300    /// If a request body is provided, it appends it to the sign message.
301    /// The function then uses the HMAC-SHA256 algorithm to sign the message.
302    /// The result is hex-encoded and returned as a string.
303    fn sign_message(&self, timestamp: &str, recv_window: &str, request: Option<String>) -> String {
304        // Create a new HMAC SHA256 instance with the secret key
305        let mut mac = Hmac::<Sha256>::new_from_slice(self.secret_key.as_bytes()).unwrap();
306
307        // Create the sign message by concatenating the timestamp, API key, and receive window
308        let mut sign_message = format!("{}{}{}", timestamp, self.api_key, recv_window);
309
310        // If a request body is provided, append it to the sign message
311        if let Some(req) = request {
312            sign_message.push_str(&req);
313        }
314
315        // Update the MAC with the sign message
316        mac.update(sign_message.as_bytes());
317
318        // Finalize the MAC and encode the result as a hex string
319        let hex_signature = hex_encode(mac.finalize().into_bytes());
320
321        hex_signature
322    }
323
324    /// Internal function to sign a POST request message.
325    ///
326    /// # Arguments
327    ///
328    /// * `timestamp` - The timestamp of the request.
329    /// * `recv_window` - The receive window of the request.
330    /// * `request` - The request body as an optional string.
331    ///
332    /// # Returns
333    ///
334    /// The signed message as a hex-encoded string.
335    fn _sign_post_message(
336        &self,
337        timestamp: &str,
338        recv_window: &str,
339        request: Option<String>,
340    ) -> String {
341        // Create a new HMAC SHA256 instance with the secret key
342        let mut mac = Hmac::<Sha256>::new_from_slice(self.secret_key.as_bytes()).unwrap();
343
344        // Update the MAC with the timestamp
345        mac.update(timestamp.as_bytes());
346        // Update the MAC with the API key
347        mac.update(self.api_key.as_bytes());
348        // Update the MAC with the receive window
349        mac.update(recv_window.as_bytes());
350        // Update the MAC with the request body, if provided
351        if let Some(req) = request {
352            mac.update(req.as_bytes());
353        }
354
355        // Finalize the MAC and encode the result as a hex string
356        let hex_signature = hex_encode(mac.finalize().into_bytes());
357
358        hex_signature
359    }
360
361    /// Internal function to handle the response from a HTTP request.
362    ///
363    /// # Arguments
364    ///
365    /// * `response` - The HTTP response from the request.
366    ///
367    /// # Returns
368    ///
369    /// The result of deserializing the response body into a specific type.
370    /// Returns `Ok(T)` if the response status is `StatusCode::OK`,
371    /// returns `Err(BybitError::BybitError(BybitContentError))` if the response
372    /// status is `StatusCode::BAD_REQUEST`, returns `Err(BybitError::InternalServerError)`
373    /// if the response status is `StatusCode::INTERNAL_SERVER_ERROR`,
374    /// returns `Err(BybitError::ServiceUnavailable)` if the response status is
375    /// `StatusCode::SERVICE_UNAVAILABLE`, returns `Err(BybitError::Unauthorized)`
376    /// if the response status is `StatusCode::UNAUTHORIZED`, and returns
377    /// `Err(BybitError::StatusCode(status))` if the response status is any other
378    /// value.
379    async fn handler<T: DeserializeOwned + Send + 'static>(
380        &self,
381        response: ReqwestResponse,
382    ) -> Result<T, BybitError> {
383        // Match the status code of the response
384        match response.status() {
385            // If the status code is OK, deserialize the response body into T and return it
386            StatusCode::OK => {
387                let response = response.json::<T>().await?;
388                Ok(response)
389            }
390            // If the status code is BAD_REQUEST, deserialize the response body into BybitContentError and
391            // wrap it in BybitError and return it
392            StatusCode::BAD_REQUEST => {
393                let error: BybitContentError = response.json().await.map_err(BybitError::from)?;
394                Err(BybitError::BybitError(error).into())
395            }
396            // If the status code is INTERNAL_SERVER_ERROR, return BybitError::InternalServerError
397            StatusCode::INTERNAL_SERVER_ERROR => Err(BybitError::InternalServerError),
398            // If the status code is SERVICE_UNAVAILABLE, return BybitError::ServiceUnavailable
399            StatusCode::SERVICE_UNAVAILABLE => Err(BybitError::ServiceUnavailable),
400            // If the status code is UNAUTHORIZED, return BybitError::Unauthorized
401            StatusCode::UNAUTHORIZED => Err(BybitError::Unauthorized),
402            // If the status code is any other value, wrap it in BybitError::StatusCode and return it
403            status => Err(BybitError::StatusCode(status.as_u16())),
404        }
405    }
406
407    /// Connects to the Bybit WebSocket endpoint and sends an authentication message.
408    ///
409    /// # Arguments
410    ///
411    /// * `endpoint` - The WebSocket endpoint to connect to.
412    /// * `request_body` - An optional request body to send after authenticating.
413    /// * `private` - A boolean indicating whether to send the authentication message.
414    /// * `alive_dur` - An optional duration in seconds to set the `alive` field of the
415    ///   authentication message to.
416    ///
417    /// # Returns
418    ///
419    /// Returns a `Result` containing a `WebSocketStream` if the connection and authentication
420    /// are successful, or a `BybitError` if an error occurs.
421    pub async fn wss_connect(
422        &self,
423        endpoint: WebsocketAPI,
424        request_body: Option<String>,
425        private: bool,
426        alive_dur: Option<u16>,
427    ) -> Result<WebSocketStream<MaybeTlsStream<TcpStream>>, BybitError> {
428        // Construct the WebSocket URL
429        let unparsed_url = format!("{}{}", self.host, endpoint.as_ref()).to_string();
430        let url = WsUrl::parse(unparsed_url.as_str())?;
431
432        // Calculate the expiration time for the authentication message
433        let expiry_time = alive_dur.unwrap_or(9) as u64 * 1000 * 60;
434        let expires = get_timestamp() + expiry_time as u64;
435
436        // Calculate the signature for the authentication message
437        let mut mac = Hmac::<Sha256>::new_from_slice(self.secret_key.as_bytes()).unwrap();
438        mac.update(format!("GET/realtime{expires}").as_bytes());
439        let signature = hex_encode(mac.finalize().into_bytes());
440
441        // Generate a random UUID for the request ID
442        let uuid = generate_random_uid(5);
443
444        // Connect to the WebSocket endpoint
445        match connect_async(url.as_ref()).await {
446            // If the connection is successful, send the authentication message
447            Ok((mut ws_stream, _)) => {
448                let auth_msg = json!({
449                    "req_id": uuid,
450                    "op": "auth",
451                    "args": [self.api_key, expires, signature]
452                });
453                if private {
454                    // Send the authentication message if `private` is true
455                    ws_stream
456                        .send(WsMessage::Text(auth_msg.to_string()))
457                        .await?;
458                }
459                // Send the request body if it is not empty
460                let request = request_body.unwrap_or_else(String::new);
461                if !request.is_empty() {
462                    ws_stream.send(WsMessage::Text(request)).await?;
463                }
464                Ok(ws_stream)
465            }
466            // If the connection fails, return a BybitError
467            Err(err) => Err(BybitError::Tungstenite(err)),
468        }
469    }
470}