fcoin-rust-sdk 0.2.2

FCoin Rust SDK
Documentation

pub mod order;
pub mod signature;

extern crate bigdecimal;
extern crate reqwest;
extern crate base64;
extern crate time;
extern crate hmac;
extern crate sha1;

#[macro_use]
extern crate hyper;

#[macro_use]
extern crate serde_derive;
extern crate serde;

use order::*;
use hyper::header::Headers;

header! { (FcAccessKey, "FC-ACCESS-KEY") => [String] }
header! { (FcAccessSignature, "FC-ACCESS-SIGNATURE") => [String] }
header! { (FcAccessTimestamp, "FC-ACCESS-TIMESTAMP") => [String] }

type Result<T> = std::result::Result<T, reqwest::Error>;

pub trait Function {
    fn symbols(&self) -> Result<ApiResponse<Vec<SymbolDescription>>>;

    fn coins(&self) -> Result<ApiResponse<Vec<String>>>;

    fn ordering(&self, order: &OrderRequest) -> Result<ApiResponse<String>>;

    fn cancel(&self, id: &str) -> Result<ApiResponse<bool>>;

    fn query(&self, condition: &OrderQuery) -> Result<ApiResponse<Vec<OrderInfo>>>;

    fn get(&self, id: &str) -> Result<ApiResponse<OrderInfo>>;

    fn get_match_result(&self, id: &str) -> Result<ApiResponse<Vec<MatchResult>>>;

    fn get_orderbook(&self, level: &str, coin: &str, currency: &str) -> Result<ApiResponse<Orderbook>>;
}

pub struct Fcoin {
    key: String,
    secret: String,
    uri: &'static str,
    http: reqwest::Client,
}

impl Fcoin {
    pub fn sandbox(key: &str, secret: &str) -> Fcoin {
        Fcoin::new(key, secret, "https://api-sandbox.fcoin.com/v2")
    }

    pub fn real(key: &str, secret: &str) -> Fcoin {
        Fcoin::new(key, secret, "https://api.fcoin.com/v2")
    }

    fn new(key: &str, secret: &str, host: &'static str) -> Fcoin {
        Fcoin {
            key: key.to_string(),
            secret: secret.to_string(),
            uri: host,
            http: reqwest::Client::new(),
        }
    }
}

#[derive(Deserialize, Debug, Eq, PartialEq)]
pub struct SymbolDescription {
    pub name: String,
    pub base_currency: String,
    pub quote_currency: String,
    pub price_decimal: u8,
    pub amount_decimal: u8,
}

#[derive(Deserialize)]
pub struct ApiResponse<T> {
    pub status: i16,
    pub data: Option<T>,
    pub msg: Option<String>,
}

impl Function for Fcoin {

    fn symbols(&self) -> Result<ApiResponse<Vec<SymbolDescription>>> {
        let mut url = self.uri.to_string();
        let suffix = "/public/symbols";
        url += &suffix;
        let symbols: ApiResponse<Vec<SymbolDescription>> = self.http.get(&url).send()?.json()?;
        Ok(symbols)
    }

    fn coins(&self) -> Result<ApiResponse<Vec<String>>> {
        let mut url = self.uri.to_string();
        let suffix = "/public/currencies";
        url += &suffix;
        let coins: ApiResponse<Vec<String>> = self.http.get(&url).send()?.json()?;
        Ok(coins)
    }

    fn ordering(&self, order: &OrderRequest) -> Result<ApiResponse<String>> {
        let mut url = self.uri.to_string();
        let suffix = "/orders";
        url += &suffix;
        let current_time = time::get_time();
        let mut keys = vec!["amount".to_string(), "price".to_string(), "side".to_string(), "symbol".to_string(), "type".to_string()];
        let mut values = Vec::<String>::with_capacity(5);
        values.push(order.amount.to_string());
        values.push(order.price.to_string());
        values.push(order.buy_or_sell.to_string());
        values.push(order.symbol.to_string());
        values.push(order.instruction.to_string());
        let mut body = std::collections::HashMap::<&str, String>::new();
        body.insert("amount", order.amount.to_string());
        body.insert("price", order.price.to_string());
        body.insert("side", order.buy_or_sell.to_string());
        body.insert("symbol", order.symbol.to_string());
        body.insert("type", order.instruction.to_string());
        let timestamp: u64 = (current_time.sec as u64 * 1000) + (current_time.nsec as u64 / 1000 / 1000);
        let sig = signature::sign("POST", "https://api.fcoin.com/v2/orders", timestamp, &mut keys, &mut values, &self.secret);
        let mut headers = Headers::new();
        headers.set(FcAccessKey(self.key.clone()));
        headers.set(FcAccessSignature(sig));
        headers.set(FcAccessTimestamp(timestamp.to_string()));
        let order_id: ApiResponse<String> = self.http.post(&url).headers(headers).json(&body).send()?.json()?;
        Ok(order_id)
    }

    fn cancel(&self, id: &str) -> Result<ApiResponse<bool>> {
        let mut url = self.uri.to_string();
        url += "/orders/";
        url += id;
        url += "/submit-cancel";
        let mut sig_prefix = "https://api.fcoin.com/v2".to_string();
        sig_prefix += "/orders/";
        sig_prefix += id;
        sig_prefix += "/submit-cancel";
        let current_time = time::get_time();
        let timestamp: u64 = (current_time.sec as u64 * 1000) + (current_time.nsec as u64 / 1000 / 1000);
        let mut keys = Vec::<String>::new();
        let mut values = Vec::<String>::new();
        let sig = signature::sign("POST", &sig_prefix, timestamp, &mut keys, &mut values, &self.secret);
        let mut headers = Headers::new();
        headers.set(FcAccessKey(self.key.clone()));
        headers.set(FcAccessSignature(sig));
        headers.set(FcAccessTimestamp(timestamp.to_string()));
        let cancel: ApiResponse<bool> = self.http.post(&url).headers(headers).send()?.json()?;
        Ok(cancel)
    }

    fn query(&self, condition: &OrderQuery) -> Result<ApiResponse<Vec<OrderInfo>>> {
        let mut url = self.uri.to_string();
        url += "/orders";
        let mut sig_prefix = "https://api.fcoin.com/v2/orders".to_string();

        let mut query_str = "?".to_string();
        if let Some(after) = condition.after {
            query_str += "after=";
            query_str += &after.to_string();
            query_str += "&";
        }
        if let Some(before) = condition.before {
            query_str += "before=";
            query_str += &before.to_string();
            query_str += "&";
        }
        if let Some(limit) = condition.limit {
            query_str += "limit=";
            query_str += &limit.to_string();
            query_str += "&"
        }
        query_str += "states=";
        query_str += &condition.states;
        query_str += "&";
        query_str += "symbol=";
        query_str += &condition.symbol;
        url += &query_str;
        sig_prefix += &query_str;

        let mut keys = vec![];
        let mut values = vec![];
        let current_time = time::get_time();
        let timestamp: u64 = (current_time.sec as u64 * 1000) + (current_time.nsec as u64 / 1000 / 1000);
        let sig = signature::sign("GET", &sig_prefix, timestamp, &mut keys, &mut values, &self.secret);
        let mut headers = Headers::new();
        headers.set(FcAccessKey(self.key.clone()));
        headers.set(FcAccessSignature(sig));
        headers.set(FcAccessTimestamp(timestamp.to_string()));
        let result: ApiResponse<Vec<OrderInfo>> = self.http.get(&url).headers(headers).send()?.json()?;
        Ok(result)
    }

    fn get(&self, id: &str) -> Result<ApiResponse<OrderInfo>> {
        let mut url = self.uri.to_string();
        url += "/orders/";
        url += id;
        let mut sig_prefix = "https://api.fcoin.com/v2/orders/".to_string();
        sig_prefix += id;
        let mut keys = vec![];
        let mut values = vec![];
        let current_time = time::get_time();
        let timestamp: u64 = (current_time.sec as u64 * 1000) + (current_time.nsec as u64 / 1000 / 1000);
        let sig = signature::sign("GET", &sig_prefix, timestamp, &mut keys, &mut values, &self.secret);
        let mut headers = Headers::new();
        headers.set(FcAccessKey(self.key.clone()));
        headers.set(FcAccessSignature(sig));
        headers.set(FcAccessTimestamp(timestamp.to_string()));
        let result: ApiResponse<OrderInfo> = self.http.get(&url).headers(headers).send()?.json()?;
        Ok(result)
    }

    fn get_match_result(&self, id: &str) -> Result<ApiResponse<Vec<MatchResult>>> {
        let mut url = self.uri.to_string();
        url += "/orders/";
        url += id;
        url += "/match-results";
        let mut sig_prefix = "https://api.fcoin.com/v2/orders/".to_string();
        sig_prefix += id;
        sig_prefix += "/match-results";
        let mut keys = vec![];
        let mut values = vec![];
        let current_time = time::get_time();
        let timestamp: u64 = (current_time.sec as u64 * 1000) + (current_time.nsec as u64 / 1000 / 1000);
        let sig = signature::sign("GET", &sig_prefix, timestamp, &mut keys, &mut values, &self.secret);
        let mut headers = Headers::new();
        headers.set(FcAccessKey(self.key.clone()));
        headers.set(FcAccessSignature(sig));
        headers.set(FcAccessTimestamp(timestamp.to_string()));
        let mut response = self.http.get(&url).headers(headers).send()?;
        let result: ApiResponse<Vec<MatchResult>> = response.json()?;
        Ok(result)
    }

    fn get_orderbook(&self, level: &str, coin: &str, currency: &str) -> Result<ApiResponse<Orderbook>> {
        let mut url = self.uri.to_string();
        url +=  "/market/depth/";
        url += level;
        url = url + "/" + coin + currency;
        let mut response = self.http.get(&url).send()?;
        let result: ApiResponse<Orderbook> = response.json()?;
        Ok(result)
    }
}

#[cfg(test)]
mod tests {

    use super::*;

    #[test]
    fn test_currencies() {
        let fcoin = Fcoin::real("sk", "sd");
        let v = fcoin.coins().unwrap();
        assert_eq!(v.status, 0);
        assert_ne!(v.data, None);
    }

    #[test]
    fn test_symbols() {
        let fcoin = Fcoin::real("sk", "sd");
        let v = fcoin.symbols().unwrap();
        assert_eq!(v.status, 0);
        assert_ne!(v.data, None);
    }

    #[test]
    fn test_orderbook() {
        let fcoin = Fcoin::real("sk", "sd");
        let v = fcoin.get_orderbook("L20", "btc", "usdt").unwrap();
        assert_eq!(v.status, 0);
        assert_ne!(v.data, None);
    }
}