1use std::{
5 str::FromStr,
6 marker::PhantomData,
7 time::{SystemTime, Duration},
8};
9use hmac::{Hmac, Mac};
10use sha2::Sha256;
11use serde::{de::DeserializeOwned, Deserialize, Serialize};
12use crypto_botters_api::*;
13use generic_api_client::{http::*, websocket::*};
14
15pub type BinanceRequestResult<T> = Result<T, BinanceRequestError>;
17pub type BinanceRequestError = RequestError<&'static str, BinanceHandlerError>;
18
19pub enum BinanceOption {
21 Default,
23 Key(String),
25 Secret(String),
27 HttpUrl(BinanceHttpUrl),
29 HttpAuth(BinanceAuth),
31 RequestConfig(RequestConfig),
34 WebSocketUrl(BinanceWebSocketUrl),
36 WebSocketConfig(WebSocketConfig),
40}
41
42#[derive(Clone, Debug)]
44pub struct BinanceOptions {
45 pub key: Option<String>,
47 pub secret: Option<String>,
49 pub http_url: BinanceHttpUrl,
51 pub http_auth: BinanceAuth,
53 pub request_config: RequestConfig,
55 pub websocket_url: BinanceWebSocketUrl,
57 pub websocket_config: WebSocketConfig,
59}
60
61#[derive(Debug, Eq, PartialEq, Copy, Clone)]
63#[non_exhaustive]
64pub enum BinanceHttpUrl {
65 Spot,
67 Spot1,
69 Spot2,
71 Spot3,
73 Spot4,
75 SpotTest,
77 SpotData,
79 FuturesUsdM,
81 FuturesCoinM,
83 FuturesTest,
85 EuropeanOptions,
87 None,
89}
90
91#[derive(Debug, Eq, PartialEq, Copy, Clone)]
93#[non_exhaustive]
94pub enum BinanceWebSocketUrl {
95 Spot9443,
97 Spot443,
99 SpotTest,
101 SpotData,
103 WebSocket443,
105 WebSocket9443,
107 FuturesUsdM,
109 FuturesUsdMAuth,
111 FuturesCoinM,
113 FuturesUsdMTest,
115 FuturesCoinMTest,
117 EuropeanOptions,
119 None,
121}
122
123#[derive(Debug, Eq, PartialEq, Copy, Clone)]
124pub enum BinanceAuth {
125 Sign,
126 Key,
127 None,
128}
129
130#[derive(Debug)]
131pub enum BinanceHandlerError {
132 ApiError(BinanceError),
133 RateLimitError { retry_after: Option<u32> },
134 ParseError,
135}
136
137#[derive(Deserialize, Debug)]
138pub struct BinanceError {
139 pub code: i32,
140 pub msg: String,
141}
142
143pub struct BinanceRequestHandler<'a, R: DeserializeOwned> {
145 options: BinanceOptions,
146 _phantom: PhantomData<&'a R>,
147}
148
149pub struct BinanceWebSocketHandler<H: FnMut(serde_json::Value) + Send + 'static> {
151 message_handler: H,
152 options: BinanceOptions,
153}
154
155impl<'a, B, R> RequestHandler<B> for BinanceRequestHandler<'a, R>
157where
158 B: Serialize,
159 R: DeserializeOwned,
160{
161 type Successful = R;
162 type Unsuccessful = BinanceHandlerError;
163 type BuildError = &'static str;
164
165 fn request_config(&self) -> RequestConfig {
166 let mut config = self.options.request_config.clone();
167 if self.options.http_url != BinanceHttpUrl::None {
168 config.url_prefix = self.options.http_url.as_str().to_owned();
169 }
170 config
171 }
172
173 fn build_request(&self, mut builder: RequestBuilder, request_body: &Option<B>, _: u8) -> Result<Request, Self::BuildError> {
174 if let Some(body) = request_body {
175 let encoded = serde_urlencoded::to_string(body).or(
176 Err("could not serialize body as application/x-www-form-urlencoded"),
177 )?;
178 builder = builder
179 .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
180 .body(encoded);
181 }
182
183 if self.options.http_auth != BinanceAuth::None {
184 let key = self.options.key.as_deref().ok_or("API key not set")?;
186 builder = builder.header("X-MBX-APIKEY", key);
187
188 if self.options.http_auth == BinanceAuth::Sign {
189 let time = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); let timestamp = time.as_millis();
191
192 builder = builder.query(&[("timestamp", timestamp)]);
193
194 let secret = self.options.secret.as_deref().ok_or("API secret not set")?;
195 let mut hmac = Hmac::<Sha256>::new_from_slice(secret.as_bytes()).unwrap(); let mut request = builder.build().or(Err("Failed to build request"))?;
198 let query = request.url().query().unwrap(); let body = request.body().and_then(|body| body.as_bytes()).unwrap_or_default();
200
201 hmac.update(&[query.as_bytes(), body].concat());
202 let signature = hex::encode(hmac.finalize().into_bytes());
203
204 request.url_mut().query_pairs_mut().append_pair("signature", &signature);
205
206 return Ok(request);
207 }
208 }
209 builder.build().or(Err("failed to build request"))
210 }
211
212 fn handle_response(&self, status: StatusCode, headers: HeaderMap, response_body: Bytes) -> Result<Self::Successful, Self::Unsuccessful> {
213 if status.is_success() {
214 serde_json::from_slice(&response_body).map_err(|error| {
215 log::error!("Failed to parse response due to an error: {}", error);
216 BinanceHandlerError::ParseError
217 })
218 } else {
219 if status == 429 || status == 418 {
221 let retry_after = if let Some(value) = headers.get("Retry-After") {
222 if let Ok(string) = value.to_str() {
223 if let Ok(retry_after) = u32::from_str(string) {
224 Some(retry_after)
225 } else {
226 log::warn!("Invalid number in Retry-After header");
227 None
228 }
229 } else {
230 log::warn!("Non-ASCII character in Retry-After header");
231 None
232 }
233 } else {
234 None
235 };
236 return Err(BinanceHandlerError::RateLimitError { retry_after });
237 }
238
239 let error = match serde_json::from_slice(&response_body) {
240 Ok(parsed_error) => BinanceHandlerError::ApiError(parsed_error),
241 Err(error) => {
242 log::error!("Failed to parse error response due to an error: {}", error);
243 BinanceHandlerError::ParseError
244 }
245 };
246 Err(error)
247 }
248 }
249}
250
251impl<H> WebSocketHandler for BinanceWebSocketHandler<H> where H: FnMut(serde_json::Value) + Send + 'static, {
252 fn websocket_config(&self) -> WebSocketConfig {
253 let mut config = self.options.websocket_config.clone();
254 if self.options.websocket_url != BinanceWebSocketUrl::None {
255 config.url_prefix = self.options.websocket_url.as_str().to_owned();
256 }
257 config
258 }
259
260 fn handle_message(&mut self, message: WebSocketMessage) -> Vec<WebSocketMessage> {
261 match message {
262 WebSocketMessage::Text(message) => {
263 if let Ok(message) = serde_json::from_str(&message) {
264 (self.message_handler)(message);
265 } else {
266 log::error!("Invalid JSON message received");
267 }
268 },
269 WebSocketMessage::Binary(_) => log::warn!("Unexpected binary message received"),
270 WebSocketMessage::Ping(_) | WebSocketMessage::Pong(_) => (),
271 }
272 vec![]
273 }
274}
275
276impl BinanceHttpUrl {
277 #[inline(always)]
279 fn as_str(&self) -> &'static str {
280 match self {
281 Self::Spot => "https://api.binance.com",
282 Self::Spot1 => "https://api1.binance.com",
283 Self::Spot2 => "https://api2.binance.com",
284 Self::Spot3 => "https://api3.binance.com",
285 Self::Spot4 => "https://api4.binance.com",
286 Self::SpotTest => "https://testnet.binance.vision",
287 Self::SpotData => "https://data.binance.com",
288 Self::FuturesUsdM => "https://fapi.binance.com",
289 Self::FuturesCoinM => "https://dapi.binance.com",
290 Self::FuturesTest => "https://testnet.binancefuture.com",
291 Self::EuropeanOptions => "https://eapi.binance.com",
292 Self::None => "",
293 }
294 }
295}
296
297impl BinanceWebSocketUrl {
298 #[inline(always)]
300 pub fn as_str(&self) -> &'static str {
301 match self {
302 Self::Spot9443 => "wss://stream.binance.com:9443",
303 Self::Spot443 => "wss://stream.binance.com:443",
304 Self::SpotTest => "wss://testnet.binance.vision",
305 Self::SpotData => "wss://data-stream.binance.com",
306 Self::WebSocket443 => "wss://ws-api.binance.com:443",
307 Self::WebSocket9443 => "wss://ws-api.binance.com:9443",
308 Self::FuturesUsdM => "wss://fstream.binance.com",
309 Self::FuturesUsdMAuth => "wss://fstream-auth.binance.com",
310 Self::FuturesCoinM => "wss://dstream.binance.com",
311 Self::FuturesUsdMTest => "wss://stream.binancefuture.com",
312 Self::FuturesCoinMTest => "wss://dstream.binancefuture.com",
313 Self::EuropeanOptions => "wss://nbstream.binance.com",
314 Self::None => "",
315 }
316 }
317}
318
319impl HandlerOptions for BinanceOptions {
320 type OptionItem = BinanceOption;
321
322 fn update(&mut self, option: Self::OptionItem) {
323 match option {
324 BinanceOption::Default => (),
325 BinanceOption::Key(v) => self.key = Some(v),
326 BinanceOption::Secret(v) => self.secret = Some(v),
327 BinanceOption::HttpUrl(v) => self.http_url = v,
328 BinanceOption::HttpAuth(v) => self.http_auth = v,
329 BinanceOption::RequestConfig(v) => self.request_config = v,
330 BinanceOption::WebSocketUrl(v) => self.websocket_url = v,
331 BinanceOption::WebSocketConfig(v) => self.websocket_config = v,
332 }
333 }
334}
335
336impl Default for BinanceOptions {
337 fn default() -> Self {
338 let mut websocket_config = WebSocketConfig::new();
339 websocket_config.refresh_after = Duration::from_secs(60 * 60 * 12);
340 websocket_config.ignore_duplicate_during_reconnection = true;
341 Self {
342 key: None,
343 secret: None,
344 http_url: BinanceHttpUrl::None,
345 http_auth: BinanceAuth::None,
346 request_config: RequestConfig::default(),
347 websocket_url: BinanceWebSocketUrl::None,
348 websocket_config,
349 }
350 }
351}
352
353impl<'a, R: DeserializeOwned + 'a> HttpOption<'a, R> for BinanceOption {
354 type RequestHandler = BinanceRequestHandler<'a, R>;
355
356 #[inline(always)]
357 fn request_handler(options: Self::Options) -> Self::RequestHandler {
358 BinanceRequestHandler::<'a, R> {
359 options,
360 _phantom: PhantomData,
361 }
362 }
363}
364
365impl<H: FnMut(serde_json::Value) + Send + 'static> WebSocketOption<H> for BinanceOption {
366 type WebSocketHandler = BinanceWebSocketHandler<H>;
367
368 #[inline(always)]
369 fn websocket_handler(handler: H, options: Self::Options) -> Self::WebSocketHandler {
370 BinanceWebSocketHandler {
371 message_handler: handler,
372 options,
373 }
374 }
375}
376
377impl HandlerOption for BinanceOption {
378 type Options = BinanceOptions;
379}
380
381impl Default for BinanceOption {
382 fn default() -> Self {
383 Self::Default
384 }
385}