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 let client = reqwest::Client::new();
31 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 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 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 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 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 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}