use hyper_native_tls::NativeTlsClient;
use hyper::Client;
use hyper::header::{ContentType,UserAgent};
use hyper::net::HttpsConnector;
use serde_json::Value;
use serde_json::value::Map;
use std::collections::HashMap;
use std::io::Read;
use std::thread;
use std::time::Duration;
use coinnect::Credentials;
use exchange::Exchange;
use error::*;
use helpers;
use types::Pair;
use gdax::utils;
use types::*;
header! {
#[doc(hidden)]
(KeyHeader, "Key") => [String]
}
header! {
#[doc(hidden)]
(SignHeader, "Sign") => [String]
}
header! {
#[doc(hidden)]
(ContentHeader, "Content-Type") => [String]
}
#[derive(Debug)]
pub struct GdaxApi {
last_request: i64, api_key: String,
api_secret: String,
customer_id: String,
http_client: Client,
burst: bool,
}
impl GdaxApi {
pub fn new<C: Credentials>(creds: C) -> Result<GdaxApi> {
if creds.exchange() != Exchange::Gdax {
return Err(ErrorKind::InvalidConfigType(Exchange::Gdax, creds.exchange()).into());
}
let ssl = match NativeTlsClient::new() {
Ok(res) => res,
Err(_) => return Err(ErrorKind::TlsError.into()),
};
let connector = HttpsConnector::new(ssl);
Ok(GdaxApi {
last_request: 0,
api_key: creds.get("api_key").unwrap_or_default(),
api_secret: creds.get("api_secret").unwrap_or_default(),
customer_id: creds.get("customer_id").unwrap_or_default(),
http_client: Client::with_connector(connector),
burst: false, })
}
pub fn set_burst(&mut self, burst: bool) {
self.burst = burst
}
fn block_or_continue(&self) {
if ! self.burst {
let threshold: u64 = 334; let offset: u64 = helpers::get_unix_timestamp_ms() as u64 - self.last_request as u64;
if offset < threshold {
let wait_ms = Duration::from_millis(threshold - offset);
thread::sleep(wait_ms);
}
}
}
fn public_query(&mut self, params: &HashMap<&str, &str>) -> Result<Map<String, Value>> {
let method: &str = params
.get("method")
.ok_or_else(|| "Missing \"method\" field.")?;
let pair: &str = params.get("pair").ok_or_else(|| "Missing \"pair\" field.")?;
let url: String = utils::build_url(method, pair);
self.block_or_continue();
let mut response = self.http_client
.get(&url)
.header(UserAgent("coinnect".to_string()))
.send()?;
self.last_request = helpers::get_unix_timestamp_ms();
let mut buffer = String::new();
response.read_to_string(&mut buffer)?;
utils::deserialize_json(&buffer)
}
fn private_query(&mut self, params: &HashMap<&str, &str>) -> Result<Map<String, Value>> {
let method: &str = params
.get("method")
.ok_or_else(|| "Missing \"method\" field.")?;
let pair: &str = params.get("pair").ok_or_else(|| "Missing \"pair\" field.")?;
let url: String = utils::build_url(method, pair);
let nonce = utils::generate_nonce(None);
let signature =
utils::build_signature(&nonce, &self.customer_id, &self.api_key, &self.api_secret)?;
let copy_api_key = self.api_key.clone();
let mut post_params: &mut HashMap<&str, &str> = &mut HashMap::new();
post_params.insert("key", ©_api_key);
post_params.insert("signature", &signature);
post_params.insert("nonce", &nonce);
params.iter().for_each(|(k,v)| {
post_params.insert(k,v);
});
helpers::strip_empties(&mut post_params);
let post_data = helpers::url_encode_hashmap(post_params);
let mut response = self.http_client
.post(&url)
.header(ContentType::form_url_encoded())
.body(&post_data)
.send()?;
let mut buffer = String::new();
response.read_to_string(&mut buffer)?;
utils::deserialize_json(&buffer)
}
pub fn return_ticker(&mut self, pair: Pair) -> Result<Map<String, Value>> {
let pair_name = match utils::get_pair_string(&pair) {
Some(name) => name,
None => return Err(ErrorKind::PairUnsupported.into()),
};
let mut params: HashMap<&str, &str> = HashMap::new();
params.insert("pair", pair_name);
params.insert("method", "ticker");
self.public_query(¶ms)
}
pub fn return_order_book(&mut self, pair: Pair) -> Result<Map<String, Value>> {
let pair_name = match utils::get_pair_string(&pair) {
Some(name) => name,
None => return Err(ErrorKind::PairUnsupported.into()),
};
let mut params: HashMap<&str, &str> = HashMap::new();
params.insert("method", "order_book");
params.insert("pair", pair_name);
self.public_query(¶ms)
}
pub fn return_trade_history(&mut self, pair: Pair) -> Result<Map<String, Value>> {
let pair_name = match utils::get_pair_string(&pair) {
Some(name) => name,
None => return Err(ErrorKind::PairUnsupported.into()),
};
let mut params: HashMap<&str, &str> = HashMap::new();
params.insert("pair", pair_name);
params.insert("method", "transactions");
self.public_query(¶ms)
}
pub fn return_balances(&mut self) -> Result<Map<String, Value>> {
let mut params = HashMap::new();
params.insert("method", "balance");
params.insert("pair", "");
self.private_query(¶ms)
}
pub fn buy_limit(&mut self,
pair: Pair,
amount: Volume,
price: Price,
price_limit: Option<Price>,
daily_order: Option<bool>)
-> Result<Map<String, Value>> {
let pair_name = match utils::get_pair_string(&pair) {
Some(name) => name,
None => return Err(ErrorKind::PairUnsupported.into()),
};
let amount_string = amount.to_string();
let price_string = price.to_string();
let price_limit_string = match price_limit {
Some(limit) => limit.to_string(),
None => "".to_string(),
};
let mut params = HashMap::new();
params.insert("method", "buy");
params.insert("pair", pair_name);
params.insert("amount", &amount_string);
params.insert("price", &price_string);
params.insert("limit_price", &price_limit_string);
if let Some(order) = daily_order {
let daily_order_str = if order { "True" } else { "" }; params.insert("daily_order", daily_order_str);
}
self.private_query(¶ms)
}
pub fn sell_limit(&mut self,
pair: Pair,
amount: Volume,
price: Price,
price_limit: Option<Price>,
daily_order: Option<bool>)
-> Result<Map<String, Value>> {
let pair_name = match utils::get_pair_string(&pair) {
Some(name) => name,
None => return Err(ErrorKind::PairUnsupported.into()),
};
let amount_string = amount.to_string();
let price_string = price.to_string();
let price_limit_string = match price_limit {
Some(limit) => limit.to_string(),
None => "".to_string(),
};
let mut params = HashMap::new();
params.insert("method", "sell");
params.insert("pair", pair_name);
params.insert("amount", &amount_string);
params.insert("price", &price_string);
params.insert("limit_price", &price_limit_string);
if let Some(order) = daily_order {
let daily_order_str = if order { "True" } else { "" }; params.insert("daily_order", daily_order_str);
}
self.private_query(¶ms)
}
pub fn buy_market(&mut self, pair: Pair, amount: Volume) -> Result<Map<String, Value>> {
let pair_name = match utils::get_pair_string(&pair) {
Some(name) => name,
None => return Err(ErrorKind::PairUnsupported.into()),
};
let amount_string = amount.to_string();
let mut params = HashMap::new();
params.insert("method", "buy/market");
params.insert("pair", pair_name);
params.insert("amount", &amount_string);
self.private_query(¶ms)
}
pub fn sell_market(&mut self, pair: Pair, amount: Volume) -> Result<Map<String, Value>> {
let pair_name = match utils::get_pair_string(&pair) {
Some(name) => name,
None => return Err(ErrorKind::PairUnsupported.into()),
};
let amount_string = amount.to_string();
let mut params = HashMap::new();
params.insert("method", "sell/market");
params.insert("pair", pair_name);
params.insert("amount", &amount_string);
self.private_query(¶ms)
}
}
#[cfg(test)]
mod gdax_api_tests {
use super::*;
#[test]
fn should_block_or_not_block_when_enabled_or_disabled() {
let mut api = GdaxApi {
last_request: helpers::get_unix_timestamp_ms(),
api_key: "".to_string(),
api_secret: "".to_string(),
customer_id: "".to_string(),
http_client: Client::new(),
burst: false,
};
let mut counter = 0;
loop {
api.set_burst(false);
let start = helpers::get_unix_timestamp_ms();
api.block_or_continue();
api.last_request = helpers::get_unix_timestamp_ms();
let difference = api.last_request - start;
assert!(difference >= 334);
assert!(difference < 10000);
api.set_burst(true);
let start = helpers::get_unix_timestamp_ms();
api.block_or_continue();
api.last_request = helpers::get_unix_timestamp_ms();
let difference = api.last_request - start;
assert!(difference < 10);
counter = counter + 1;
if counter >= 3 { break; }
}
}
}