extern crate crypto;
extern crate curl;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
use crypto::hmac::Hmac;
use crypto::mac::Mac;
use crypto::sha2::Sha512;
use std::collections::HashMap;
use std::fmt;
use std::fmt::Write;
use std::io::Read;
use curl::easy::{Easy, List};
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Account {
pub key: String,
pub secret: String,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct TickPair {
pub high: f64,
pub low: f64,
pub avg: f64,
pub vol: f64,
pub vol_cur: f64,
pub last: f64,
pub buy: f64,
pub sell: f64,
pub updated: i64,
}
pub type Tick = HashMap<String, TickPair>;
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct Depth {
pub asks: Vec<Vec<f64>>,
pub bids: Vec<Vec<f64>>,
}
pub type Depths = HashMap<String, Depth>;
#[derive(Deserialize, Serialize, Clone, Debug)]
#[serde(rename_all = "lowercase")]
pub enum PublicTradeKind {
Ask,
Bid,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct PublicTrade {
#[serde(rename = "type")]
kind: PublicTradeKind,
price: f64,
amount: f64,
tid: u32,
timestamp: i64,
}
pub type PublicTrades = HashMap<String, Vec<PublicTrade>>;
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct InfoPair {
pub decimal_places: u32,
pub min_price: f64,
pub max_price: f64,
pub min_amount: f64,
pub hidden: u32,
pub fee: f64,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct Info {
pub server_time: i64,
pub pairs: HashMap<String, InfoPair>,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct WexResult<T> {
success: u8,
#[serde(rename = "return")]
result: Option<T>,
error: Option<String>,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct FundRights {
pub info: u8,
pub trade: u8,
pub withdraw: u8,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct FundInfo {
pub funds: HashMap<String, f64>,
pub rights: FundRights,
pub transaction_count: u32,
pub open_orders: u32,
pub server_time: i64,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct TradeResult {
pub received: f64,
pub remains: f64,
pub order_id: u64,
pub funds: HashMap<String, f64>,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
#[serde(rename_all = "lowercase")]
pub enum OrderType {
Sell,
Buy,
}
macro_rules! enum_number {
($name:ident { $($variant:ident = $value:expr, )* }) => {
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum $name {
$($variant = $value,)*
}
impl ::serde::Serialize for $name {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: ::serde::Serializer
{
serializer.serialize_u64(*self as u64)
}
}
impl<'de> ::serde::Deserialize<'de> for $name {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: ::serde::Deserializer<'de>
{
struct Visitor;
impl<'de> ::serde::de::Visitor<'de> for Visitor {
type Value = $name;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("positive integer")
}
fn visit_u64<E>(self, value: u64) -> Result<$name, E>
where E: ::serde::de::Error
{
match value {
$( $value => Ok($name::$variant), )*
_ => Err(E::custom(
format!("unknown {} value: {}",
stringify!($name), value))),
}
}
}
deserializer.deserialize_u64(Visitor)
}
}
}
}
enum_number!(OrderStatus {
Active = 0,
ExecutedOrder = 1,
Canceled = 2,
PartiallyExecuted = 3,
});
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct ActiveOrder {
pair: String,
#[serde(rename = "type")]
kind: OrderType,
amount: f64,
rate: f64,
timestamp_created: i64,
status: OrderStatus,
}
pub type ActiveOrders = HashMap<String, ActiveOrder>;
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct OrderInfo {
pair: String,
#[serde(rename = "type")]
kind: OrderType,
start_amount: f64,
amount: f64,
rate: f64,
timestamp_created: i64,
status: OrderStatus,
}
pub type OrderInfos = HashMap<String, ActiveOrder>;
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct CancelResult {
order_id: String,
funds: HashMap<String, f64>,
}
pub enum TradeHistorySorting {
Asc,
Desc,
}
pub struct HistoryQuery {
from: Option<u64>,
count: Option<u64>,
from_id: Option<u64>,
end_id: Option<u64>,
order: Option<TradeHistorySorting>,
since: Option<i64>,
end: Option<i64>,
pair: Option<String>,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct TradeHistoryEntry {
pair: String,
kind: OrderType,
amount: f64,
rate: f64,
order_id: u64,
is_your_order: u8,
timestamp: i64,
}
type TradeHistory = HashMap<String, TradeHistoryEntry>;
#[derive(Deserialize, Serialize, Clone, Debug)]
pub enum TransactionType {
Deposit = 1,
Withdrawl = 2,
Credit = 4,
Debit = 5,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub enum TransactionStatus {
Canceled = 0,
Waiting = 1,
Successful = 2,
NotConfirmed = 3,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct TransactionHistoryEntry {
#[serde(rename = "type")]
kind: TransactionType,
amount: f64,
currency: String,
desc: String,
status: TransactionStatus,
timestamp: i64,
}
pub type TransactionHistory = HashMap<String, TransactionHistoryEntry>;
fn public(url: &str) -> Result<Vec<u8>, String> {
let mut easy = Easy::new();
let mut dst = Vec::new();
easy.url(&format!("https://wex.nz/api/3/{}", url)).unwrap();
let result = {
let mut transfer = easy.transfer();
transfer
.write_function(|data| {
dst.extend_from_slice(data);
Ok(data.len())
})
.unwrap();
transfer.perform()
};
result.map_err(|e| format!("{:?}", e)).and_then(
|_x| Ok(dst),
)
}
pub fn info() -> Result<Info, String> {
public("info").and_then(|data| {
serde_json::from_slice(&data).map_err(|e| format!("{:?}", e))
})
}
pub fn ticker(pair: &str) -> Result<Tick, String> {
public(&format!("ticker/{}", pair)).and_then(|data| {
serde_json::from_slice(&data).map_err(|e| format!("{:?}", e))
})
}
pub fn depth(pair: &str, limit: Option<u32>) -> Result<Depths, String> {
let mut url = format!("depth/{}", pair);
if let Some(l) = limit {
url = format!("{}?limit={}", url, l);
}
public(&url).and_then(|data| {
serde_json::from_slice(&data).map_err(|e| format!("{:?}", e))
})
}
pub fn trades(pair: &str, limit: Option<u32>) -> Result<PublicTrades, String> {
let mut url = format!("trades/{}", pair);
if let Some(l) = limit {
url = format!("{}?limit={}", url, l);
}
public(&url).and_then(|data| {
serde_json::from_slice(&data).map_err(|e| format!("{:?}", e))
})
}
fn private(account: &Account, params: &mut HashMap<String, String>) -> Result<Vec<u8>, String> {
let mut dst = Vec::new();
let mut easy = Easy::new();
easy.url("https://wex.nz/tapi").unwrap();
easy.post(true).unwrap();
let timestamp = ::std::time::UNIX_EPOCH.elapsed().unwrap();
let nonce = format!("{}", timestamp.as_secs());
params.insert("nonce".to_owned(), nonce);
let mut body = params.iter().fold(
String::new(),
|data, item| data + item.0 + "=" + item.1 + "&",
);
body.pop();
let mut body_bytes = body.as_bytes();
let mut hmac = Hmac::new(Sha512::new(), account.secret.as_bytes());
hmac.input(body_bytes);
easy.post_field_size(body_bytes.len() as u64).unwrap();
let mut list = List::new();
let sign = hmac.result();
let mut hex = String::new();
for byte in sign.code() {
write!(&mut hex, "{:02x}", byte).expect("could not create hmac hex");
}
list.append("Content-Type: application/x-www-form-urlencoded")
.unwrap();
list.append(&format!("Key: {}", account.key)).unwrap();
list.append(&format!("Sign: {}", hex)).unwrap();
easy.http_headers(list).unwrap();
let result = {
let mut transfer = easy.transfer();
transfer
.read_function(|buf| Ok(body_bytes.read(buf).unwrap_or(0)))
.unwrap();
transfer
.write_function(|data| {
dst.extend_from_slice(data);
Ok(data.len())
})
.unwrap();
transfer.perform()
};
result.map_err(|e| format!("{:?}", e)).and_then(
|_x| Ok(dst),
)
}
pub fn get_info(account: &Account) -> Result<FundInfo, String> {
let mut params = HashMap::new();
params.insert("method".to_owned(), "getInfo".to_owned());
private(account, &mut params).and_then(|r| {
serde_json::from_slice(&r)
.map_err(|e| format!("{:?}", e))
.and_then(|result: WexResult<FundInfo>| if result.success == 1 {
Ok(result.result.unwrap())
} else {
Err(result.error.unwrap())
})
})
}
pub fn trade(
account: &Account,
pair: &str,
kind: OrderType,
rate: &str,
amount: &str,
) -> Result<TradeResult, String> {
let mut params = HashMap::new();
let kind_str = match kind {
OrderType::Buy => String::from("buy"),
OrderType::Sell => String::from("sell"),
};
params.insert("method".to_owned(), "Trade".to_owned());
params.insert("pair".to_owned(), String::from(pair));
params.insert("type".to_owned(), kind_str);
params.insert("rate".to_owned(), String::from(rate));
params.insert("amount".to_owned(), String::from(amount));
private(account, &mut params).and_then(|r| {
serde_json::from_slice(&r)
.map_err(|e| format!("{:?}", e))
.and_then(|result: WexResult<TradeResult>| if result.success == 1 {
Ok(result.result.unwrap())
} else {
Err(result.error.unwrap())
})
})
}
pub fn active_orders(account: &Account, pair: Option<&str>) -> Result<ActiveOrders, String> {
let mut params = HashMap::new();
params.insert("method".to_owned(), "ActiveOrders".to_owned());
if let Some(p) = pair {
params.insert("pair".to_owned(), String::from(p));
}
private(account, &mut params).and_then(|r| {
serde_json::from_slice(&r)
.map_err(|e| format!("{:?}\n{}", e, String::from_utf8(r).unwrap()))
.and_then(|result: WexResult<ActiveOrders>| if result.success == 1 {
Ok(result.result.unwrap())
} else {
Err(result.error.unwrap())
})
})
}
pub fn order_info(account: &Account, order_id: &str) -> Result<OrderInfos, String> {
let mut params = HashMap::new();
params.insert("method".to_owned(), "OrderInfo".to_owned());
params.insert("order_id".to_owned(), String::from(order_id));
private(account, &mut params).and_then(|r| {
serde_json::from_slice(&r)
.map_err(|e| format!("{:?}", e))
.and_then(|result: WexResult<OrderInfos>| if result.success == 1 {
Ok(result.result.unwrap())
} else {
Err(result.error.unwrap())
})
})
}
pub fn cancel_order(account: &Account, order_id: &str) -> Result<CancelResult, String> {
let mut params = HashMap::new();
params.insert("method".to_owned(), "CancelOrder".to_owned());
params.insert("order_id".to_owned(), String::from(order_id));
private(account, &mut params).and_then(|r| {
serde_json::from_slice(&r)
.map_err(|e| format!("{:?}", e))
.and_then(|result: WexResult<CancelResult>| if result.success == 1 {
Ok(result.result.unwrap())
} else {
Err(result.error.unwrap())
})
})
}
pub fn trade_history(account: &Account, cfg: Option<HistoryQuery>) -> Result<TradeHistory, String> {
let mut params = HashMap::new();
params.insert("method".to_owned(), "TradeHistory".to_owned());
if let Some(p) = cfg {
if let Some(from) = p.from {
params.insert("from".to_owned(), format!("{}", from));
}
if let Some(count) = p.count {
params.insert("count".to_owned(), format!("{}", count));
}
if let Some(from_id) = p.from_id {
params.insert("from_id".to_owned(), format!("{}", from_id));
}
if let Some(end_id) = p.end_id {
params.insert("end_id".to_owned(), format!("{}", end_id));
}
if let Some(order) = p.order {
match order {
TradeHistorySorting::Asc => params.insert("order".to_owned(), "asc".to_owned()),
TradeHistorySorting::Desc => params.insert("order".to_owned(), "desc".to_owned()),
};
}
if let Some(since) = p.since {
params.insert("since".to_owned(), format!("{}", since));
}
if let Some(end) = p.end {
params.insert("end".to_owned(), format!("{}", end));
}
if let Some(pair) = p.pair {
params.insert("pair".to_owned(), pair);
}
}
private(account, &mut params).and_then(|r| {
serde_json::from_slice(&r)
.map_err(|e| format!("{:?}", e))
.and_then(|result: WexResult<TradeHistory>| if result.success == 1 {
Ok(result.result.unwrap())
} else {
Err(result.error.unwrap())
})
})
}
pub fn trans_history(
account: &Account,
cfg: Option<HistoryQuery>,
) -> Result<TransactionHistory, String> {
let mut params = HashMap::new();
params.insert("method".to_owned(), "TransHistory".to_owned());
if let Some(p) = cfg {
if let Some(from) = p.from {
params.insert("from".to_owned(), format!("{}", from));
}
if let Some(count) = p.count {
params.insert("count".to_owned(), format!("{}", count));
}
if let Some(from_id) = p.from_id {
params.insert("from_id".to_owned(), format!("{}", from_id));
}
if let Some(end_id) = p.end_id {
params.insert("end_id".to_owned(), format!("{}", end_id));
}
if let Some(order) = p.order {
match order {
TradeHistorySorting::Asc => params.insert("order".to_owned(), "asc".to_owned()),
TradeHistorySorting::Desc => params.insert("order".to_owned(), "desc".to_owned()),
};
}
if let Some(since) = p.since {
params.insert("since".to_owned(), format!("{}", since));
}
if let Some(end) = p.end {
params.insert("end".to_owned(), format!("{}", end));
}
if let Some(pair) = p.pair {
params.insert("pair".to_owned(), pair);
}
}
private(account, &mut params).and_then(|r| {
serde_json::from_slice(&r)
.map_err(|e| format!("{:?}", e))
.and_then(|result: WexResult<TransactionHistory>| if result.success ==
1
{
Ok(result.result.unwrap())
} else {
Err(result.error.unwrap())
})
})
}
pub fn coin_deposit_address(account: &Account, coin_name: &str) -> Result<CancelResult, String> {
let mut params = HashMap::new();
params.insert("method".to_owned(), "CoinDepositAddress".to_owned());
params.insert("coinName".to_owned(), String::from(coin_name));
private(account, &mut params).and_then(|r| {
serde_json::from_slice(&r)
.map_err(|e| format!("{:?}", e))
.and_then(|result: WexResult<CancelResult>| if result.success == 1 {
Ok(result.result.unwrap())
} else {
Err(result.error.unwrap())
})
})
}