kraken_api_client/futures/rest/
client.rs1use std::sync::Arc;
4
5use reqwest::header::{CONTENT_TYPE, HeaderMap, HeaderValue, USER_AGENT};
6use reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
7use reqwest_retry::{RetryTransientMiddleware, policies::ExponentialBackoff};
8use reqwest_tracing::TracingMiddleware;
9
10use crate::auth::{CredentialsProvider, IncreasingNonce, NonceProvider};
11use crate::error::KrakenError;
12use crate::futures::auth::sign_futures_request;
13use crate::futures::rest::endpoints::{FUTURES_BASE_URL, private, public};
14use crate::futures::rest::types::*;
15use crate::futures::types::*;
16
17#[derive(Clone)]
63pub struct FuturesRestClient {
64 http_client: ClientWithMiddleware,
65 base_url: String,
66 credentials: Option<Arc<dyn CredentialsProvider>>,
67 nonce_provider: Arc<dyn NonceProvider>,
68}
69
70impl FuturesRestClient {
71 pub fn new() -> Self {
76 Self::builder().build()
77 }
78
79 pub fn builder() -> FuturesRestClientBuilder {
81 FuturesRestClientBuilder::new()
82 }
83
84 pub(crate) async fn public_get<T>(&self, endpoint: &str) -> Result<T, KrakenError>
88 where
89 T: serde::de::DeserializeOwned,
90 {
91 let url = format!("{}{}", self.base_url, endpoint);
92 let response = self.http_client.get(&url).send().await?;
93 self.parse_futures_response(response).await
94 }
95
96 pub(crate) async fn public_get_with_params<T, Q>(
98 &self,
99 endpoint: &str,
100 params: &Q,
101 ) -> Result<T, KrakenError>
102 where
103 T: serde::de::DeserializeOwned,
104 Q: serde::Serialize + ?Sized,
105 {
106 let query_string = serde_urlencoded::to_string(params)
107 .map_err(|e| KrakenError::InvalidResponse(e.to_string()))?;
108 let url = if query_string.is_empty() {
109 format!("{}{}", self.base_url, endpoint)
110 } else {
111 format!("{}{}?{}", self.base_url, endpoint, query_string)
112 };
113 let response = self.http_client.get(&url).send().await?;
114 self.parse_futures_response(response).await
115 }
116
117 pub(crate) async fn private_get<T>(&self, endpoint: &str) -> Result<T, KrakenError>
119 where
120 T: serde::de::DeserializeOwned,
121 {
122 let credentials = self
123 .credentials
124 .as_ref()
125 .ok_or(KrakenError::MissingCredentials)?;
126
127 let nonce = self.nonce_provider.next_nonce();
128 let creds = credentials.get_credentials();
129
130 let signature = sign_futures_request(creds, endpoint, nonce, "")?;
132
133 let url = format!("{}{}", self.base_url, endpoint);
134 let response = self
135 .http_client
136 .get(&url)
137 .header("APIKey", &creds.api_key)
138 .header("Authent", signature)
139 .header("Nonce", nonce.to_string())
140 .send()
141 .await?;
142
143 self.parse_futures_response(response).await
144 }
145
146 pub(crate) async fn private_post<T, P>(
148 &self,
149 endpoint: &str,
150 params: &P,
151 ) -> Result<T, KrakenError>
152 where
153 T: serde::de::DeserializeOwned,
154 P: serde::Serialize,
155 {
156 let credentials = self
157 .credentials
158 .as_ref()
159 .ok_or(KrakenError::MissingCredentials)?;
160
161 let nonce = self.nonce_provider.next_nonce();
162 let creds = credentials.get_credentials();
163
164 let form_data = serde_urlencoded::to_string(params)
166 .map_err(|e| KrakenError::InvalidResponse(e.to_string()))?;
167
168 let signature = sign_futures_request(creds, endpoint, nonce, &form_data)?;
170
171 let url = format!("{}{}", self.base_url, endpoint);
172 let response = self
173 .http_client
174 .post(&url)
175 .header("APIKey", &creds.api_key)
176 .header("Authent", signature)
177 .header("Nonce", nonce.to_string())
178 .header(CONTENT_TYPE, "application/x-www-form-urlencoded")
179 .body(form_data)
180 .send()
181 .await?;
182
183 self.parse_futures_response(response).await
184 }
185
186 async fn parse_futures_response<T>(&self, response: reqwest::Response) -> Result<T, KrakenError>
192 where
193 T: serde::de::DeserializeOwned,
194 {
195 let status = response.status();
196 let body = response.text().await?;
197
198 if let Ok(error_response) = serde_json::from_str::<FuturesErrorResponse>(&body) {
200 if error_response.result == "error" {
201 return Err(KrakenError::Api(crate::error::ApiError::new(
202 "EFutures",
203 error_response
204 .error
205 .unwrap_or_else(|| "Unknown error".to_string()),
206 )));
207 }
208 }
209
210 serde_json::from_str::<T>(&body).map_err(|e| {
212 if !status.is_success() {
213 KrakenError::InvalidResponse(format!("HTTP {}: {}", status, body))
214 } else {
215 KrakenError::InvalidResponse(format!(
216 "Failed to parse response: {}. Body: {}",
217 e, body
218 ))
219 }
220 })
221 }
222
223 pub async fn get_tickers(&self) -> Result<Vec<FuturesTicker>, KrakenError> {
229 let response: TickersResponse = self.public_get(public::TICKERS).await?;
230 Ok(response.tickers)
231 }
232
233 pub async fn get_ticker(&self, symbol: &str) -> Result<Option<FuturesTicker>, KrakenError> {
237 let tickers = self.get_tickers().await?;
238 Ok(tickers.into_iter().find(|t| t.symbol == symbol))
239 }
240
241 pub async fn get_orderbook(&self, symbol: &str) -> Result<FuturesOrderBook, KrakenError> {
247 #[derive(serde::Serialize)]
248 struct Params<'a> {
249 symbol: &'a str,
250 }
251 let response: OrderBookResponse = self
252 .public_get_with_params(public::ORDERBOOK, &Params { symbol })
253 .await?;
254 Ok(response.order_book)
255 }
256
257 pub async fn get_trade_history(
264 &self,
265 symbol: &str,
266 last_time: Option<&str>,
267 ) -> Result<Vec<FuturesTrade>, KrakenError> {
268 #[derive(serde::Serialize)]
269 struct Params<'a> {
270 symbol: &'a str,
271 #[serde(skip_serializing_if = "Option::is_none")]
272 #[serde(rename = "lastTime")]
273 last_time: Option<&'a str>,
274 }
275 let response: TradeHistoryResponse = self
276 .public_get_with_params(public::HISTORY, &Params { symbol, last_time })
277 .await?;
278 Ok(response.history)
279 }
280
281 pub async fn get_instruments(&self) -> Result<Vec<FuturesInstrument>, KrakenError> {
285 let response: InstrumentsResponse = self.public_get(public::INSTRUMENTS).await?;
286 Ok(response.instruments)
287 }
288
289 pub async fn get_accounts(&self) -> Result<AccountsResponse, KrakenError> {
295 self.private_get(private::ACCOUNTS).await
296 }
297
298 pub async fn get_open_positions(&self) -> Result<Vec<FuturesPosition>, KrakenError> {
302 let response: OpenPositionsResponse = self.private_get(private::OPEN_POSITIONS).await?;
303 Ok(response.open_positions)
304 }
305
306 pub async fn get_open_orders(&self) -> Result<Vec<FuturesOrder>, KrakenError> {
310 let response: OpenOrdersResponse = self.private_get(private::OPEN_ORDERS).await?;
311 Ok(response.open_orders)
312 }
313
314 pub async fn get_fills(
322 &self,
323 request: Option<&FillsRequest>,
324 ) -> Result<Vec<FuturesFill>, KrakenError> {
325 let response: FillsResponse = match request {
326 Some(req) => self.public_get_with_params(private::FILLS, req).await?,
327 None => self.private_get(private::FILLS).await?,
328 };
329 Ok(response.fills)
330 }
331
332 pub async fn send_order(
340 &self,
341 request: &SendOrderRequest,
342 ) -> Result<SendOrderResponse, KrakenError> {
343 self.private_post(private::SEND_ORDER, request).await
344 }
345
346 pub async fn edit_order(
352 &self,
353 request: &EditOrderRequest,
354 ) -> Result<EditOrderResponse, KrakenError> {
355 self.private_post(private::EDIT_ORDER, request).await
356 }
357
358 pub async fn cancel_order(&self, order_id: &str) -> Result<CancelOrderResponse, KrakenError> {
364 #[derive(serde::Serialize)]
365 struct Params<'a> {
366 order_id: &'a str,
367 }
368 self.private_post(private::CANCEL_ORDER, &Params { order_id })
369 .await
370 }
371
372 pub async fn cancel_order_by_cli_ord_id(
378 &self,
379 cli_ord_id: &str,
380 ) -> Result<CancelOrderResponse, KrakenError> {
381 #[derive(serde::Serialize)]
382 struct Params<'a> {
383 #[serde(rename = "cliOrdId")]
384 cli_ord_id: &'a str,
385 }
386 self.private_post(private::CANCEL_ORDER, &Params { cli_ord_id })
387 .await
388 }
389
390 pub async fn cancel_all_orders(&self) -> Result<CancelAllOrdersResponse, KrakenError> {
394 #[derive(serde::Serialize)]
395 struct Empty {}
396 self.private_post(private::CANCEL_ALL_ORDERS, &Empty {})
397 .await
398 }
399
400 pub async fn cancel_all_orders_for_symbol(
406 &self,
407 symbol: &str,
408 ) -> Result<CancelAllOrdersResponse, KrakenError> {
409 #[derive(serde::Serialize)]
410 struct Params<'a> {
411 symbol: &'a str,
412 }
413 self.private_post(private::CANCEL_ALL_ORDERS, &Params { symbol })
414 .await
415 }
416
417 pub async fn cancel_all_orders_after(
423 &self,
424 timeout_seconds: u32,
425 ) -> Result<CancelAllOrdersAfterResponse, KrakenError> {
426 #[derive(serde::Serialize)]
427 struct Params {
428 timeout: u32,
429 }
430 self.private_post(
431 private::CANCEL_ALL_ORDERS_AFTER,
432 &Params {
433 timeout: timeout_seconds,
434 },
435 )
436 .await
437 }
438
439 pub async fn batch_order(
447 &self,
448 request: &BatchOrderRequest,
449 ) -> Result<BatchOrderResponse, KrakenError> {
450 self.private_post(private::BATCH_ORDER, request).await
451 }
452}
453
454impl Default for FuturesRestClient {
455 fn default() -> Self {
456 Self::new()
457 }
458}
459
460impl std::fmt::Debug for FuturesRestClient {
461 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
462 f.debug_struct("FuturesRestClient")
463 .field("base_url", &self.base_url)
464 .field("has_credentials", &self.credentials.is_some())
465 .finish()
466 }
467}
468
469pub struct FuturesRestClientBuilder {
471 base_url: String,
472 credentials: Option<Arc<dyn CredentialsProvider>>,
473 nonce_provider: Option<Arc<dyn NonceProvider>>,
474 user_agent: Option<String>,
475 max_retries: u32,
476}
477
478impl FuturesRestClientBuilder {
479 pub fn new() -> Self {
481 Self {
482 base_url: FUTURES_BASE_URL.to_string(),
483 credentials: None,
484 nonce_provider: None,
485 user_agent: None,
486 max_retries: 3,
487 }
488 }
489
490 pub fn base_url(mut self, url: impl Into<String>) -> Self {
492 self.base_url = url.into();
493 self
494 }
495
496 pub fn use_demo(mut self) -> Self {
498 self.base_url = crate::futures::rest::endpoints::FUTURES_DEMO_URL.to_string();
499 self
500 }
501
502 pub fn credentials(mut self, credentials: Arc<dyn CredentialsProvider>) -> Self {
504 self.credentials = Some(credentials);
505 self
506 }
507
508 pub fn nonce_provider(mut self, provider: Arc<dyn NonceProvider>) -> Self {
510 self.nonce_provider = Some(provider);
511 self
512 }
513
514 pub fn user_agent(mut self, user_agent: impl Into<String>) -> Self {
516 self.user_agent = Some(user_agent.into());
517 self
518 }
519
520 pub fn max_retries(mut self, retries: u32) -> Self {
522 self.max_retries = retries;
523 self
524 }
525
526 pub fn build(self) -> FuturesRestClient {
528 let mut headers = HeaderMap::new();
530 let user_agent = self
531 .user_agent
532 .unwrap_or_else(|| format!("kraken-api-client/{}", env!("CARGO_PKG_VERSION")));
533 let header_value = HeaderValue::from_str(&user_agent)
534 .unwrap_or_else(|_| HeaderValue::from_static("kraken-api-client"));
535 headers.insert(USER_AGENT, header_value);
536
537 let reqwest_client = reqwest::Client::builder()
539 .default_headers(headers)
540 .build()
541 .unwrap_or_else(|_| reqwest::Client::new());
542
543 let retry_policy = ExponentialBackoff::builder().build_with_max_retries(self.max_retries);
544
545 let client = ClientBuilder::new(reqwest_client)
546 .with(TracingMiddleware::default())
547 .with(RetryTransientMiddleware::new_with_policy(retry_policy))
548 .build();
549
550 let nonce_provider = self
551 .nonce_provider
552 .unwrap_or_else(|| Arc::new(IncreasingNonce::new()));
553
554 FuturesRestClient {
555 http_client: client,
556 base_url: self.base_url,
557 credentials: self.credentials,
558 nonce_provider,
559 }
560 }
561}
562
563impl Default for FuturesRestClientBuilder {
564 fn default() -> Self {
565 Self::new()
566 }
567}
568
569#[derive(Debug, serde::Deserialize)]
571struct FuturesErrorResponse {
572 result: String,
573 error: Option<String>,
574 #[serde(rename = "serverTime")]
575 #[allow(dead_code)]
576 server_time: Option<String>,
577}