Skip to main content

kraken_http/kraken/
client.rs

1use crate::kraken::env::KrakenCredentials;
2use crate::kraken::payload::{
3    self, AssetInfoInput, AssetInfoResponse, AssetPairsInfo, AssetPairsInput, AssetPairsResponse,
4    RawRecentSpreadsResponse, RawTickerResponse, RecentSpreadsInput, RecentSpreadsResponse,
5    SerializableAssetPairsInput, TickerInput, TickerResponse,
6};
7use crate::kraken::ratelimiter::LeakyBucket;
8use crate::kraken::request_builder::{ParamEncoding, PrivacyLevel, RequestBuilder};
9use crate::kraken::signature::get_kraken_signature;
10use crate::kraken::AccountTier;
11use crate::kraken::{
12    endpoint, AssetPair, ACCOUNT_BALANCE, ASSET_INFO, ASSET_PAIRS, OPEN_ORDERS, RECENT_SPREADS,
13    SYSTEM_STATUS, SYSTEM_TIME, TICKER, TRADE_BALANCE,
14};
15use chrono::prelude::*;
16use reqwest::header::{HeaderValue, CONTENT_TYPE};
17use reqwest::Method;
18use std::error::Error;
19
20pub struct Client {
21    http: reqwest::Client,
22    api_key: String,
23    private_key: String,
24    rate_limiter: LeakyBucket,
25}
26
27impl Client {
28    pub fn new(creds: KrakenCredentials, tier: AccountTier) -> Self {
29        // First, create a new reqwest client.
30        let client = reqwest::Client::new();
31        // Set the API Key and the Private Key.
32        Self {
33            http: client,
34            api_key: creds.api_key().to_string(),
35            private_key: creds.private_key().to_string(),
36            rate_limiter: LeakyBucket::new(tier),
37        }
38    }
39
40    async fn use_rate_limit(&self, count: usize) {
41        self.rate_limiter.use_rate_limit(count).await
42    }
43
44    fn nonce(&self) -> String {
45        let utc: DateTime<Utc> = Utc::now();
46        let ms = utc.timestamp_millis();
47        ms.to_string()
48    }
49
50    pub async fn server_time(&self) -> Result<payload::ServerTimeResponse, reqwest::Error> {
51        self.use_rate_limit(1).await;
52        let client = &self.http;
53        let req = RequestBuilder::<()> {
54            method: Method::GET,
55            url: endpoint(SYSTEM_TIME),
56            params: None,
57            param_encoding: ParamEncoding::FormEncoded,
58            privacy_level: PrivacyLevel::Public,
59        };
60        let resp = req.execute(client).await?;
61        Ok(resp)
62    }
63
64    pub async fn system_status(&self) -> Result<payload::SystemStatusResponse, reqwest::Error> {
65        self.use_rate_limit(1).await;
66        let client = &self.http;
67        let req = RequestBuilder::<()> {
68            method: Method::GET,
69            url: endpoint(SYSTEM_STATUS),
70            params: None,
71            param_encoding: ParamEncoding::QueryEncoded,
72            privacy_level: PrivacyLevel::Public,
73        };
74        let resp = req.execute(client).await?;
75        Ok(resp)
76    }
77
78    pub async fn account_balance(&self) -> Result<payload::AccountBalanceResponse, reqwest::Error> {
79        self.use_rate_limit(1).await;
80        let nonce = self.nonce();
81        let client = &self.http;
82        let req = RequestBuilder {
83            method: Method::POST,
84            url: endpoint(ACCOUNT_BALANCE),
85            param_encoding: ParamEncoding::FormEncoded,
86            params: Some(payload::AccountBalanceInput {
87                nonce: nonce.clone(),
88            }),
89            privacy_level: PrivacyLevel::Private {
90                nonce,
91                api_key: self.api_key.clone(),
92                private_key: self.private_key.clone(),
93            },
94        };
95        let resp = req.execute(client).await?;
96        Ok(resp)
97    }
98
99    pub async fn asset_info(
100        &self,
101        asset: Option<String>,
102        asset_class: Option<String>,
103    ) -> Result<AssetInfoResponse, reqwest::Error> {
104        self.use_rate_limit(1).await;
105        let client = &self.http;
106        let req = RequestBuilder {
107            method: Method::GET,
108            url: endpoint(ASSET_INFO),
109            param_encoding: ParamEncoding::FormEncoded,
110            params: Some(AssetInfoInput { asset, asset_class }),
111            privacy_level: PrivacyLevel::Public,
112        };
113        let resp: AssetInfoResponse = req.execute(client).await?;
114        Ok(resp)
115    }
116
117    pub async fn recent_spreads(
118        &self,
119        pair: String,
120        since: Option<u64>,
121    ) -> Result<RecentSpreadsResponse, reqwest::Error> {
122        self.use_rate_limit(1).await;
123        let client = &self.http;
124        let req = RequestBuilder {
125            method: Method::GET,
126            url: endpoint(RECENT_SPREADS),
127            param_encoding: ParamEncoding::QueryEncoded,
128            params: Some(RecentSpreadsInput { pair, since }),
129            privacy_level: PrivacyLevel::Public,
130        };
131        let resp: RawRecentSpreadsResponse = req.execute(client).await?;
132        Ok(RecentSpreadsResponse::from(resp))
133    }
134
135    pub async fn asset_pairs(
136        &self,
137        pairs: Vec<String>,
138        info: Option<AssetPairsInfo>,
139    ) -> Result<AssetPairsResponse, reqwest::Error> {
140        self.use_rate_limit(1).await;
141        let client = &self.http;
142        let user_input = AssetPairsInput { pairs, info };
143        let serializable_input = SerializableAssetPairsInput::from(user_input);
144        let req = RequestBuilder {
145            method: Method::GET,
146            url: endpoint(ASSET_PAIRS),
147            param_encoding: ParamEncoding::QueryEncoded,
148            params: Some(serializable_input),
149            privacy_level: PrivacyLevel::Public,
150        };
151        let resp = req.execute(client).await?;
152        Ok(resp)
153    }
154
155    pub async fn ticker(&self, asset_pair: AssetPair) -> Result<TickerResponse, Box<dyn Error>> {
156        self.use_rate_limit(1).await;
157        let pair = asset_pair.to_string();
158        let client = &self.http;
159        let req = RequestBuilder {
160            method: Method::GET,
161            url: endpoint(TICKER),
162            param_encoding: ParamEncoding::QueryEncoded,
163            params: Some(TickerInput { pair }),
164            privacy_level: PrivacyLevel::Public,
165        };
166        let resp: RawTickerResponse = req.execute(client).await?;
167        let ticker = TickerResponse::try_from(resp)?;
168        Ok(ticker)
169    }
170
171    ///////////////////////////////////////////////////////////////////////////
172    // Everything under this line does not strongly type their responses. /////
173    ///////////////////////////////////////////////////////////////////////////
174
175    pub async fn open_orders(
176        &self,
177        trades: Option<bool>,
178        user_ref: Option<u32>,
179    ) -> Result<String, reqwest::Error> {
180        self.use_rate_limit(1).await;
181        let nonce = self.nonce();
182        let method = Method::POST;
183        let api_key = &self.api_key;
184        let content_type = "application/x-www-form-urlencoded; charset=utf-8";
185        let url = endpoint(OPEN_ORDERS);
186        let private_key = self.private_key.clone();
187        let form_param = payload::OpenOrdersInput {
188            nonce: nonce.clone(),
189            trades,
190            user_ref,
191        };
192        // Next, we have to attach the API Key header.
193        let mut req = self
194            .http
195            .request(method, url)
196            .form(&form_param)
197            .header("API-Key", api_key)
198            .header(CONTENT_TYPE, content_type)
199            .build()?;
200        let signature = get_kraken_signature(nonce, private_key, &req);
201        // We also need to attach the API-Sign header.
202        let api_sign = HeaderValue::from_str(&signature).unwrap();
203        req.headers_mut().insert("API-Sign", api_sign);
204        let resp = self.http.execute(req).await?.text().await?;
205        Ok(resp)
206    }
207
208    pub async fn trade_balance(&self, asset: Option<String>) -> Result<String, reqwest::Error> {
209        self.use_rate_limit(1).await;
210        let nonce = self.nonce();
211        let method = Method::POST;
212        let api_key = &self.api_key;
213        let content_type = "application/x-www-form-urlencoded; charset=utf-8";
214        let url = endpoint(TRADE_BALANCE);
215        let private_key = self.private_key.clone();
216        let form_param = payload::TradeBalanceInput {
217            nonce: nonce.clone(),
218            asset,
219        };
220        // Next, we have to attach the API Key header.
221        let mut req = self
222            .http
223            .request(method, url)
224            .form(&form_param)
225            .header("API-Key", api_key)
226            .header(CONTENT_TYPE, content_type)
227            .build()?;
228        let signature = get_kraken_signature(nonce, private_key, &req);
229        // We also need to attach the API-Sign header.
230        let api_sign = HeaderValue::from_str(&signature).unwrap();
231        req.headers_mut().insert("API-Sign", api_sign);
232        let resp = self.http.execute(req).await?.text().await?;
233        Ok(resp)
234    }
235
236    pub async fn debug_recent_spreads(
237        &self,
238        pair: String,
239        since: Option<u64>,
240    ) -> Result<String, reqwest::Error> {
241        self.use_rate_limit(1).await;
242        let client = &self.http;
243        let req = RequestBuilder {
244            method: Method::GET,
245            url: endpoint(RECENT_SPREADS),
246            param_encoding: ParamEncoding::QueryEncoded,
247            params: Some(RecentSpreadsInput { pair, since }),
248            privacy_level: PrivacyLevel::Public,
249        };
250        let resp = req.debug(client).await?;
251        Ok(resp)
252    }
253}