kraken_rest_api/api/
api_impl.rs

1/// Kraken API implementation
2use crate::api::utils::create_signature;
3use chrono::Utc;
4use data_encoding::BASE64;
5use log::trace;
6use reqwest::blocking::Response;
7use reqwest::header::USER_AGENT;
8use serde::de::DeserializeOwned;
9use std::{collections::HashMap, error::Error};
10
11use reqwest::header::HeaderMap;
12
13use super::{api::KrakenAPI, error::KrakenError, methods::Method, types::KrakenResponse};
14
15const API_URL: &str = "https://api.kraken.com";
16const API_VERSION: &str = "0";
17const API_USER_AGENT: &str = "Kraken Rust API Agent";
18pub const BTCUSD: &str = "XXBTZUSD";
19
20/// Kraken API
21impl KrakenAPI {
22    pub fn new(api_key: String, secret: String) -> KrakenAPI {
23        KrakenAPI {
24            api_key,
25            secret,
26            client: reqwest::blocking::Client::new(),
27        }
28    }
29
30    /// query private endpoints
31    pub async fn query_private<T>(
32        &self,
33        method: Method,
34        params: &mut HashMap<String, String>,
35    ) -> Result<KrakenResponse<T>, Box<dyn Error>>
36    where
37        T: DeserializeOwned + 'static,
38    {
39        let method: &str = method.into();
40        let url_path = format!("/{}/private/{}", API_VERSION, method);
41        let url = format!("{}{}", API_URL, url_path);
42        let secret_bytes = BASE64
43            .decode(&self.secret.as_bytes())
44            .expect("Not able to decode Kraken api secret");
45        let nonce = format!("{}", Utc::now().timestamp_millis() * 1000);
46        params.insert("nonce".to_owned(), nonce.to_owned());
47
48        let sig = create_signature(&url_path, params, &secret_bytes)?;
49
50        let mut header_map = HeaderMap::new();
51        header_map.insert(
52            "API-Key",
53            self.api_key.parse().expect("fail to parse api key"),
54        );
55
56        header_map.insert(
57            "API-Sign",
58            sig.parse().expect("fail to parse request signature"),
59        );
60
61        trace!("Query request url: {}", url);
62        trace!("Query request method: {}", method);
63        trace!("Query with nonce: {}", nonce);
64        trace!("Query request params: {:?}", params);
65
66        let res: KrakenResponse<T> = self.do_request(&url, params, &header_map).await?.json()?;
67        Ok(res)
68    }
69
70    /// query public endpoints
71    pub async fn query_public<T>(
72        &self,
73        method: Method,
74        params: &HashMap<String, String>,
75    ) -> Result<KrakenResponse<T>, Box<dyn Error>>
76    where
77        T: DeserializeOwned + 'static,
78    {
79        let method: &str = method.into();
80        let url = format!("{}/{}/public/{}", API_URL, API_VERSION, method);
81
82        trace!("Query request url: {}", url);
83        trace!("Query request method: {}", method);
84
85        let res: KrakenResponse<T> = self
86            .do_request(&url, params, &HeaderMap::new())
87            .await?
88            .json()?;
89        Ok(res)
90    }
91
92    /// Send Http reqeust.
93    async fn do_request(
94        &self,
95        url: &str,
96        params: &HashMap<String, String>,
97        headers: &HeaderMap,
98    ) -> Result<Response, Box<dyn Error>> {
99        let mut header_map = HeaderMap::new();
100        header_map.insert(USER_AGENT, API_USER_AGENT.parse().unwrap());
101
102        // Additional headers.
103        for (n, v) in headers {
104            header_map.insert(n, v.to_owned());
105        }
106
107        let res = self
108            .client
109            .post(url)
110            .form(params)
111            .headers(header_map)
112            .send()?;
113
114        let ok = res.status().is_success();
115
116        if !ok {
117            let status_code = res.status();
118            let res: KrakenResponse<serde_json::Value> = res.json()?;
119            return Err(Box::new(KrakenError::new(Some(status_code), res.error)));
120        }
121
122        Ok(res)
123    }
124}