use ring::{digest, hmac};
use serde_json::{json, to_value, Value};
use std::{
error::Error,
time::{SystemTime, UNIX_EPOCH},
};
use surf::http::mime;
use crate::{config, KrakenResult};
pub struct KrakenClient {
api_key: Option<String>,
api_secret: Option<String>,
}
impl KrakenClient {
pub fn new() -> Self {
Self {
api_key: None,
api_secret: None,
}
}
pub fn with_credentials(api_key: String, api_secret: String) -> Self {
Self {
api_key: Some(api_key),
api_secret: Some(api_secret),
}
}
pub async fn api_request(&self, method: &str, payload: Value) -> Result<Value, Box<dyn Error>> {
let method_type = config::method_type(method);
let endpoint = format!("/{}/{}/{}", crate::config::API_VER, method_type, method);
let uri = format!("{}{}", crate::config::API_URL, endpoint);
let request = match method_type {
"public" => surf::get(uri).query(&payload)?,
"private" => {
if self.api_key.is_none() || self.api_secret.is_none() {
panic!("Trying to use private method without credentials")
}
let mut payload = payload.clone();
let nonce = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis() as u64;
let m = payload.as_object_mut().unwrap();
m.insert(String::from("nonce"), json!(nonce));
let payload = to_value(m)?;
let sha =
digest::digest(&digest::SHA256, format!("{}{}", nonce, payload).as_bytes());
let data = [endpoint.as_bytes(), sha.as_ref()].concat();
let secret_key = match &self.api_secret {
Some(x) => base64::decode(x.as_bytes())?,
None => vec![],
};
let mac_key = hmac::Key::new(hmac::HMAC_SHA512, &secret_key);
let mac = hmac::sign(&mac_key, &data);
let sig = base64::encode(mac.as_ref());
let api_key = match &self.api_key {
Some(x) => x,
None => "",
};
surf::post(uri)
.header("API-Key", api_key)
.header("API-Sign", sig)
.content_type(mime::JSON)
.body(payload)
}
_ => panic!("Unsupported method: {}", method),
};
let result = request.recv_json::<KrakenResult>().await?;
if result.error.len() > 0 {
Err(std::boxed::Box::new(crate::KrakenError {
er_list: result.error,
}))
} else {
Ok(result.result)
}
}
}