pub mod data;
pub mod response;
use chrono::prelude::Utc;
use hmac::Hmac;
use hmac::Mac;
use reqwest::Method;
use reqwest::Url;
use sha2::Sha256;
use self::data::account::get::request::Query as AccountGetQuery;
use self::data::account::get::response::Response as AccountGetResponse;
use self::data::depth::get::request::Query as DepthGetQuery;
use self::data::depth::get::response::Response as DepthGetResponse;
use self::data::exchange_info::get::response::Response as ExchangeInfoGetResponse;
use self::data::klines::get::request::Query as KlinesGetQuery;
use self::data::klines::get::response::Response as KlinesGetResponse;
use self::data::open_orders::delete::request::Query as OpenOrdersDeleteQuery;
use self::data::open_orders::delete::response::Response as OpenOrdersDeleteResponse;
use self::data::open_orders::get::request::Query as OpenOrdersGetQuery;
use self::data::open_orders::get::response::Response as OpenOrdersGetResponse;
use self::data::order::delete::request::Query as OrderDeleteQuery;
use self::data::order::delete::response::Response as OrderDeleteResponse;
use self::data::order::get::request::Query as OrderGetQuery;
use self::data::order::get::response::Response as OrderGetResponse;
use self::data::order::post::request::Query as OrderPostQuery;
use self::data::order::post::response::Response as OrderPostResponse;
use self::data::time::get::response::Response as TimeGetResponse;
use crate::error::Error;
use self::response::Response;
#[derive(Debug, Clone)]
pub struct Client {
inner: reqwest::Client,
api_key: Option<String>,
secret_key: Option<String>,
timestamp_offset: i64,
}
impl Default for Client {
fn default() -> Self {
Self::new()
}
}
type Result<T> = ::std::result::Result<T, Error>;
impl Client {
const BASE_URL: &'static str = "https://api.binance.com";
const REQUEST_TIMESTAMP_OFFSET: i64 = 1000;
pub fn new() -> Self {
let mut client = Self {
inner: reqwest::Client::new(),
api_key: None,
secret_key: None,
timestamp_offset: 0,
};
client.timestamp_offset = client.timestamp_offset();
client
}
pub fn new_with_auth(api_key: String, secret_key: String) -> Self {
let mut client = Self {
inner: reqwest::Client::new(),
api_key: Some(api_key),
secret_key: Some(secret_key),
timestamp_offset: 0,
};
client.timestamp_offset = client.timestamp_offset();
client
}
pub fn ping(&self) -> Result<()> {
self.execute::<()>(Method::GET, "/api/v3/ping".to_owned())
}
pub fn time(&self) -> Result<TimeGetResponse> {
self.execute::<TimeGetResponse>(Method::GET, "/api/v3/time".to_owned())
}
pub fn exchange_info(&self) -> Result<ExchangeInfoGetResponse> {
self.execute::<ExchangeInfoGetResponse>(Method::GET, "/api/v3/exchangeInfo".to_owned())
}
pub fn klines(&self, request: KlinesGetQuery) -> Result<KlinesGetResponse> {
self.execute::<KlinesGetResponse>(
Method::GET,
format!("/api/v3/klines?{}", request.to_string()),
)
}
pub fn depth(&self, request: DepthGetQuery) -> Result<DepthGetResponse> {
self.execute::<DepthGetResponse>(
Method::GET,
format!("/api/v3/depth?{}", request.to_string()),
)
}
pub fn account_get(&self, mut request: AccountGetQuery) -> Result<AccountGetResponse> {
let secret_key = self
.secret_key
.as_ref()
.ok_or(Error::AuthorizationKeysMissing)?;
request.timestamp -= self.timestamp_offset;
let mut params = request.to_string();
params += &format!("&signature={}", Self::signature(¶ms, secret_key));
self.execute_signed::<AccountGetResponse>(
Method::GET,
format!("/api/v3/account?{}", params),
)
}
pub fn open_orders_get(
&self,
mut request: OpenOrdersGetQuery,
) -> Result<OpenOrdersGetResponse> {
let secret_key = self
.secret_key
.as_ref()
.ok_or(Error::AuthorizationKeysMissing)?;
request.timestamp -= self.timestamp_offset;
let mut params = request.to_string();
params += &format!("&signature={}", Self::signature(¶ms, secret_key));
self.execute_signed::<OpenOrdersGetResponse>(
Method::GET,
format!("/api/v3/openOrders?{}", params),
)
}
pub fn open_orders_delete(
&self,
mut request: OpenOrdersDeleteQuery,
) -> Result<OpenOrdersDeleteResponse> {
let secret_key = self
.secret_key
.as_ref()
.ok_or(Error::AuthorizationKeysMissing)?;
request.timestamp -= self.timestamp_offset;
let mut params = request.to_string();
params += &format!("&signature={}", Self::signature(¶ms, secret_key));
self.execute_signed::<OpenOrdersDeleteResponse>(
Method::DELETE,
format!("/api/v3/openOrders?{}", params),
)
}
pub fn order_get(&self, mut request: OrderGetQuery) -> Result<OrderGetResponse> {
let secret_key = self
.secret_key
.as_ref()
.ok_or(Error::AuthorizationKeysMissing)?;
request.timestamp -= self.timestamp_offset;
let mut params = request.to_string();
params += &format!("&signature={}", Self::signature(¶ms, secret_key));
self.execute_signed::<OrderGetResponse>(Method::GET, format!("/api/v3/order?{}", params))
}
pub fn order_post(&self, mut request: OrderPostQuery) -> Result<OrderPostResponse> {
let secret_key = self
.secret_key
.as_ref()
.ok_or(Error::AuthorizationKeysMissing)?;
request.timestamp -= self.timestamp_offset;
let mut params = request.to_string();
params += &format!("&signature={}", Self::signature(¶ms, secret_key));
self.execute_signed::<OrderPostResponse>(Method::POST, format!("/api/v3/order?{}", params))
}
pub fn order_delete(&self, mut request: OrderDeleteQuery) -> Result<OrderDeleteResponse> {
let secret_key = self
.secret_key
.as_ref()
.ok_or(Error::AuthorizationKeysMissing)?;
request.timestamp -= self.timestamp_offset;
let mut params = request.to_string();
params += &format!("&signature={}", Self::signature(¶ms, secret_key));
self.execute_signed::<OrderDeleteResponse>(
Method::DELETE,
format!("/api/v3/order?{}", params),
)
}
pub fn order_post_test(&self, mut request: OrderPostQuery) -> Result<OrderPostResponse> {
let secret_key = self
.secret_key
.as_ref()
.ok_or(Error::AuthorizationKeysMissing)?;
request.timestamp -= self.timestamp_offset;
let mut params = request.to_string();
params += &format!("&signature={}", Self::signature(¶ms, secret_key));
self.execute_signed::<OrderPostResponse>(
Method::POST,
format!("/api/v3/order/test?{}", params),
)
}
fn execute<T>(&self, method: Method, url: String) -> Result<T>
where
T: serde::de::DeserializeOwned,
{
let url = Self::BASE_URL.to_owned() + url.as_str();
let response = self
.inner
.execute(
self.inner
.request(
method,
Url::parse(&url).map_err(|error| Error::UrlParsing(error, url))?,
)
.build()
.map_err(Error::RequestBuilding)?,
)
.map_err(Error::RequestExecution)?
.text()
.map_err(Error::ResponseReading)?;
let response: Response<T> = serde_json::from_str(response.as_str())
.map_err(|error| Error::ResponseParsing(error, response))?;
match response {
Response::Ok(response) => Ok(response),
Response::Error(error) => Err(Error::ResponseError(error)),
}
}
fn execute_signed<T>(&self, method: Method, url: String) -> Result<T>
where
T: serde::de::DeserializeOwned,
{
let api_key = self
.api_key
.as_ref()
.ok_or(Error::AuthorizationKeysMissing)?;
let url = Self::BASE_URL.to_owned() + url.as_str();
let response = self
.inner
.execute(
self.inner
.request(
method,
Url::parse(&url).map_err(|error| Error::UrlParsing(error, url))?,
)
.header("X-MBX-APIKEY", api_key.to_owned())
.build()
.map_err(Error::RequestBuilding)?,
)
.map_err(Error::RequestExecution)?
.text()
.map_err(Error::ResponseReading)?;
let response: Response<T> = serde_json::from_str(response.as_str())
.map_err(|error| Error::ResponseParsing(error, response))?;
match response {
Response::Ok(response) => Ok(response),
Response::Error(error) => Err(Error::ResponseError(error)),
}
}
fn signature(params: &str, secret_key: &str) -> String {
hex::encode({
let mut hmac: Hmac<Sha256> =
Hmac::new_from_slice(secret_key.as_bytes()).expect("HMAC is valid");
hmac.update(params.as_bytes());
hmac.finalize().into_bytes()
})
}
fn timestamp_offset(&self) -> i64 {
let system_time = Utc::now().timestamp_millis();
let request_time = std::time::Instant::now();
let binance_time = self.time().expect("Time request").server_time
- (request_time.elapsed().as_millis() as i64) / 2;
(system_time - binance_time) + Self::REQUEST_TIMESTAMP_OFFSET
}
}