zcash_client_backend: Add exchanges we know how to query for USD/ZEC
Currently only unauthenticated connections are supported (i.e. no API keys can be configured). However, AFAICT none of these exchanges provide non-IP-based rate limits for authenticated connections to public APIs, so authentication wouldn't help to make connections over Tor more reliable.
This commit is contained in:
parent
739e878c8b
commit
ac5bac9969
|
@ -8,6 +8,23 @@ use tracing::{error, trace};
|
|||
|
||||
use crate::tor::{Client, Error};
|
||||
|
||||
mod binance;
|
||||
mod coinbase;
|
||||
mod gate_io;
|
||||
mod gemini;
|
||||
mod ku_coin;
|
||||
mod mexc;
|
||||
|
||||
/// Exchanges for which we know how to query data over Tor.
|
||||
pub mod exchanges {
|
||||
pub use super::binance::Binance;
|
||||
pub use super::coinbase::Coinbase;
|
||||
pub use super::gate_io::GateIo;
|
||||
pub use super::gemini::Gemini;
|
||||
pub use super::ku_coin::KuCoin;
|
||||
pub use super::mexc::Mexc;
|
||||
}
|
||||
|
||||
/// An exchange that can be queried for ZEC data.
|
||||
#[async_trait]
|
||||
pub trait Exchange: 'static {
|
||||
|
@ -43,6 +60,20 @@ pub struct Exchanges {
|
|||
}
|
||||
|
||||
impl Exchanges {
|
||||
/// Unauthenticated connections to all known exchanges with USD/ZEC pairs.
|
||||
///
|
||||
/// Gemini is treated as a "trusted" data source due to being a NYDFS-regulated
|
||||
/// exchange.
|
||||
pub fn unauthenticated_known_with_gemini_trusted() -> Self {
|
||||
Self::builder(exchanges::Gemini::unauthenticated())
|
||||
.with(exchanges::Binance::unauthenticated())
|
||||
.with(exchanges::Coinbase::unauthenticated())
|
||||
.with(exchanges::GateIo::unauthenticated())
|
||||
.with(exchanges::KuCoin::unauthenticated())
|
||||
.with(exchanges::Mexc::unauthenticated())
|
||||
.build()
|
||||
}
|
||||
|
||||
/// Returns an `Exchanges` builder.
|
||||
///
|
||||
/// The `trusted` exchange will always have its data used, _if_ data is successfully
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
use async_trait::async_trait;
|
||||
use rust_decimal::Decimal;
|
||||
use serde::Deserialize;
|
||||
|
||||
use super::{Exchange, ExchangeData};
|
||||
use crate::tor::{Client, Error};
|
||||
|
||||
/// Querier for the Binance exchange.
|
||||
pub struct Binance {
|
||||
_private: (),
|
||||
}
|
||||
|
||||
impl Binance {
|
||||
/// Prepares for unauthenticated connections to Binance.
|
||||
pub fn unauthenticated() -> Self {
|
||||
Self { _private: () }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[allow(dead_code)]
|
||||
#[allow(non_snake_case)]
|
||||
struct BinanceData {
|
||||
symbol: String,
|
||||
priceChange: Decimal,
|
||||
priceChangePercent: Decimal,
|
||||
weightedAvgPrice: Decimal,
|
||||
prevClosePrice: Decimal,
|
||||
lastPrice: Decimal,
|
||||
lastQty: Decimal,
|
||||
bidPrice: Decimal,
|
||||
bidQty: Decimal,
|
||||
askPrice: Decimal,
|
||||
askQty: Decimal,
|
||||
openPrice: Decimal,
|
||||
highPrice: Decimal,
|
||||
lowPrice: Decimal,
|
||||
volume: Decimal,
|
||||
quoteVolume: Decimal,
|
||||
openTime: u64,
|
||||
closeTime: u64,
|
||||
firstId: u32,
|
||||
lastId: u32,
|
||||
count: u32,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Exchange for Binance {
|
||||
async fn query_zec_to_usd(&self, client: &Client) -> Result<ExchangeData, Error> {
|
||||
// API documentation:
|
||||
// https://binance-docs.github.io/apidocs/spot/en/#24hr-ticker-price-change-statistics
|
||||
let res = client
|
||||
.get_json::<BinanceData>(
|
||||
"https://api.binance.com/api/v3/ticker/24hr?symbol=ZECUSDT"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
)
|
||||
.await?;
|
||||
let data = res.into_body();
|
||||
Ok(ExchangeData {
|
||||
bid: data.bidPrice,
|
||||
ask: data.askPrice,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
use async_trait::async_trait;
|
||||
use rust_decimal::Decimal;
|
||||
use serde::Deserialize;
|
||||
|
||||
use super::{Exchange, ExchangeData};
|
||||
use crate::tor::{Client, Error};
|
||||
|
||||
/// Querier for the Coinbase exchange.
|
||||
pub struct Coinbase {
|
||||
_private: (),
|
||||
}
|
||||
|
||||
impl Coinbase {
|
||||
/// Prepares for unauthenticated connections to Coinbase.
|
||||
pub fn unauthenticated() -> Self {
|
||||
Self { _private: () }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[allow(dead_code)]
|
||||
struct CoinbaseData {
|
||||
ask: Decimal,
|
||||
bid: Decimal,
|
||||
volume: Decimal,
|
||||
trade_id: u32,
|
||||
price: Decimal,
|
||||
size: Decimal,
|
||||
time: String,
|
||||
rfq_volume: Option<Decimal>,
|
||||
conversions_volume: Option<Decimal>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Exchange for Coinbase {
|
||||
#[allow(dead_code)]
|
||||
async fn query_zec_to_usd(&self, client: &Client) -> Result<ExchangeData, Error> {
|
||||
// API documentation:
|
||||
// https://docs.cdp.coinbase.com/exchange/reference/exchangerestapi_getproductticker
|
||||
let res = client
|
||||
.get_json::<CoinbaseData>(
|
||||
"https://api.exchange.coinbase.com/products/ZEC-USD/ticker"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
)
|
||||
.await?;
|
||||
let data = res.into_body();
|
||||
Ok(ExchangeData {
|
||||
bid: data.bid,
|
||||
ask: data.ask,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
use async_trait::async_trait;
|
||||
use hyper::StatusCode;
|
||||
use rust_decimal::Decimal;
|
||||
use serde::Deserialize;
|
||||
|
||||
use super::{Exchange, ExchangeData};
|
||||
use crate::tor::{Client, Error};
|
||||
|
||||
/// Querier for the Gate.io exchange.
|
||||
pub struct GateIo {
|
||||
_private: (),
|
||||
}
|
||||
|
||||
impl GateIo {
|
||||
/// Prepares for unauthenticated connections to Gate.io.
|
||||
pub fn unauthenticated() -> Self {
|
||||
Self { _private: () }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[allow(dead_code)]
|
||||
struct GateIoData {
|
||||
currency_pair: String,
|
||||
last: Decimal,
|
||||
lowest_ask: Decimal,
|
||||
highest_bid: Decimal,
|
||||
change_percentage: Decimal,
|
||||
base_volume: Decimal,
|
||||
quote_volume: Decimal,
|
||||
high_24h: Decimal,
|
||||
low_24h: Decimal,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Exchange for GateIo {
|
||||
async fn query_zec_to_usd(&self, client: &Client) -> Result<ExchangeData, Error> {
|
||||
// API documentation:
|
||||
// https://www.gate.io/docs/developers/apiv4/#retrieve-ticker-information
|
||||
let res = client
|
||||
.get_json::<Vec<GateIoData>>(
|
||||
"https://api.gateio.ws/api/v4/spot/tickers?currency_pair=ZEC_USDT"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
)
|
||||
.await?;
|
||||
let data = res.into_body().into_iter().next().ok_or(Error::Http(
|
||||
super::super::HttpError::Unsuccessful(StatusCode::GONE),
|
||||
))?;
|
||||
|
||||
Ok(ExchangeData {
|
||||
bid: data.highest_bid,
|
||||
ask: data.lowest_ask,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
use async_trait::async_trait;
|
||||
use rust_decimal::Decimal;
|
||||
use serde::Deserialize;
|
||||
|
||||
use super::{Exchange, ExchangeData};
|
||||
use crate::tor::{Client, Error};
|
||||
|
||||
/// Querier for the Gemini exchange.
|
||||
pub struct Gemini {
|
||||
_private: (),
|
||||
}
|
||||
|
||||
impl Gemini {
|
||||
/// Prepares for unauthenticated connections to Gemini.
|
||||
pub fn unauthenticated() -> Self {
|
||||
Self { _private: () }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[allow(dead_code)]
|
||||
struct GeminiData {
|
||||
symbol: String,
|
||||
open: Decimal,
|
||||
high: Decimal,
|
||||
low: Decimal,
|
||||
close: Decimal,
|
||||
changes: Vec<Decimal>,
|
||||
bid: Decimal,
|
||||
ask: Decimal,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Exchange for Gemini {
|
||||
async fn query_zec_to_usd(&self, client: &Client) -> Result<ExchangeData, Error> {
|
||||
// API documentation:
|
||||
// https://docs.gemini.com/rest-api/#ticker-v2
|
||||
let res = client
|
||||
.get_json::<GeminiData>("https://api.gemini.com/v2/ticker/zecusd".parse().unwrap())
|
||||
.await?;
|
||||
let data = res.into_body();
|
||||
Ok(ExchangeData {
|
||||
bid: data.bid,
|
||||
ask: data.ask,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
use async_trait::async_trait;
|
||||
use rust_decimal::Decimal;
|
||||
use serde::Deserialize;
|
||||
|
||||
use super::{Exchange, ExchangeData};
|
||||
use crate::tor::{Client, Error};
|
||||
|
||||
/// Querier for the KuCoin exchange.
|
||||
pub struct KuCoin {
|
||||
_private: (),
|
||||
}
|
||||
|
||||
impl KuCoin {
|
||||
/// Prepares for unauthenticated connections to KuCoin.
|
||||
pub fn unauthenticated() -> Self {
|
||||
Self { _private: () }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[allow(dead_code)]
|
||||
#[allow(non_snake_case)]
|
||||
struct KuCoinData {
|
||||
time: u64,
|
||||
symbol: String,
|
||||
buy: Decimal,
|
||||
sell: Decimal,
|
||||
changeRate: Decimal,
|
||||
changePrice: Decimal,
|
||||
high: Decimal,
|
||||
low: Decimal,
|
||||
vol: Decimal,
|
||||
volValue: Decimal,
|
||||
last: Decimal,
|
||||
averagePrice: Decimal,
|
||||
takerFeeRate: Decimal,
|
||||
makerFeeRate: Decimal,
|
||||
takerCoefficient: Decimal,
|
||||
makerCoefficient: Decimal,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[allow(dead_code)]
|
||||
struct KuCoinResponse {
|
||||
code: String,
|
||||
data: KuCoinData,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Exchange for KuCoin {
|
||||
async fn query_zec_to_usd(&self, client: &Client) -> Result<ExchangeData, Error> {
|
||||
// API documentation:
|
||||
// https://www.kucoin.com/docs/rest/spot-trading/market-data/get-24hr-stats
|
||||
let res = client
|
||||
.get_json::<KuCoinResponse>(
|
||||
"https://api.kucoin.com/api/v1/market/stats?symbol=ZEC-USDT"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
)
|
||||
.await?;
|
||||
let data = res.into_body().data;
|
||||
Ok(ExchangeData {
|
||||
bid: data.buy,
|
||||
ask: data.sell,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
use async_trait::async_trait;
|
||||
use rust_decimal::Decimal;
|
||||
use serde::Deserialize;
|
||||
|
||||
use super::{Exchange, ExchangeData};
|
||||
use crate::tor::{Client, Error};
|
||||
|
||||
/// Querier for the MEXC exchange.
|
||||
pub struct Mexc {
|
||||
_private: (),
|
||||
}
|
||||
|
||||
impl Mexc {
|
||||
/// Prepares for unauthenticated connections to MEXC.
|
||||
pub fn unauthenticated() -> Self {
|
||||
Self { _private: () }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[allow(dead_code)]
|
||||
#[allow(non_snake_case)]
|
||||
struct MexcData {
|
||||
symbol: String,
|
||||
priceChange: Decimal,
|
||||
priceChangePercent: Decimal,
|
||||
prevClosePrice: Decimal,
|
||||
lastPrice: Decimal,
|
||||
bidPrice: Decimal,
|
||||
bidQty: Decimal,
|
||||
askPrice: Decimal,
|
||||
askQty: Decimal,
|
||||
openPrice: Decimal,
|
||||
highPrice: Decimal,
|
||||
lowPrice: Decimal,
|
||||
volume: Decimal,
|
||||
quoteVolume: Decimal,
|
||||
openTime: u64,
|
||||
closeTime: u64,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Exchange for Mexc {
|
||||
async fn query_zec_to_usd(&self, client: &Client) -> Result<ExchangeData, Error> {
|
||||
// API documentation:
|
||||
// https://mexcdevelop.github.io/apidocs/spot_v3_en/#24hr-ticker-price-change-statistics
|
||||
let res = client
|
||||
.get_json::<MexcData>(
|
||||
"https://api.mexc.com/api/v3/ticker/24hr?symbol=ZECUSDT"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
)
|
||||
.await?;
|
||||
let data = res.into_body();
|
||||
Ok(ExchangeData {
|
||||
bid: data.bidPrice,
|
||||
ask: data.askPrice,
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue