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(×tamp, &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(×tamp.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}