diff --git a/zcash_client_backend/src/tor/http/cryptex.rs b/zcash_client_backend/src/tor/http/cryptex.rs index cf090268f5..596e9b1bf5 100644 --- a/zcash_client_backend/src/tor/http/cryptex.rs +++ b/zcash_client_backend/src/tor/http/cryptex.rs @@ -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 diff --git a/zcash_client_backend/src/tor/http/cryptex/binance.rs b/zcash_client_backend/src/tor/http/cryptex/binance.rs new file mode 100644 index 0000000000..49f3001d6b --- /dev/null +++ b/zcash_client_backend/src/tor/http/cryptex/binance.rs @@ -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 { + // API documentation: + // https://binance-docs.github.io/apidocs/spot/en/#24hr-ticker-price-change-statistics + let res = client + .get_json::( + "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, + }) + } +} diff --git a/zcash_client_backend/src/tor/http/cryptex/coinbase.rs b/zcash_client_backend/src/tor/http/cryptex/coinbase.rs new file mode 100644 index 0000000000..61a355f269 --- /dev/null +++ b/zcash_client_backend/src/tor/http/cryptex/coinbase.rs @@ -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, + conversions_volume: Option, +} + +#[async_trait] +impl Exchange for Coinbase { + #[allow(dead_code)] + async fn query_zec_to_usd(&self, client: &Client) -> Result { + // API documentation: + // https://docs.cdp.coinbase.com/exchange/reference/exchangerestapi_getproductticker + let res = client + .get_json::( + "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, + }) + } +} diff --git a/zcash_client_backend/src/tor/http/cryptex/gate_io.rs b/zcash_client_backend/src/tor/http/cryptex/gate_io.rs new file mode 100644 index 0000000000..80a81b7dce --- /dev/null +++ b/zcash_client_backend/src/tor/http/cryptex/gate_io.rs @@ -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 { + // API documentation: + // https://www.gate.io/docs/developers/apiv4/#retrieve-ticker-information + let res = client + .get_json::>( + "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, + }) + } +} diff --git a/zcash_client_backend/src/tor/http/cryptex/gemini.rs b/zcash_client_backend/src/tor/http/cryptex/gemini.rs new file mode 100644 index 0000000000..dbb596e245 --- /dev/null +++ b/zcash_client_backend/src/tor/http/cryptex/gemini.rs @@ -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, + bid: Decimal, + ask: Decimal, +} + +#[async_trait] +impl Exchange for Gemini { + async fn query_zec_to_usd(&self, client: &Client) -> Result { + // API documentation: + // https://docs.gemini.com/rest-api/#ticker-v2 + let res = client + .get_json::("https://api.gemini.com/v2/ticker/zecusd".parse().unwrap()) + .await?; + let data = res.into_body(); + Ok(ExchangeData { + bid: data.bid, + ask: data.ask, + }) + } +} diff --git a/zcash_client_backend/src/tor/http/cryptex/ku_coin.rs b/zcash_client_backend/src/tor/http/cryptex/ku_coin.rs new file mode 100644 index 0000000000..30b4ac629c --- /dev/null +++ b/zcash_client_backend/src/tor/http/cryptex/ku_coin.rs @@ -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 { + // API documentation: + // https://www.kucoin.com/docs/rest/spot-trading/market-data/get-24hr-stats + let res = client + .get_json::( + "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, + }) + } +} diff --git a/zcash_client_backend/src/tor/http/cryptex/mexc.rs b/zcash_client_backend/src/tor/http/cryptex/mexc.rs new file mode 100644 index 0000000000..7dcb46d7b6 --- /dev/null +++ b/zcash_client_backend/src/tor/http/cryptex/mexc.rs @@ -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 { + // API documentation: + // https://mexcdevelop.github.io/apidocs/spot_v3_en/#24hr-ticker-price-change-statistics + let res = client + .get_json::( + "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, + }) + } +}