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