Skip to content

Commit

Permalink
zcash_client_backend: Add exchanges we know how to query for USD/ZEC
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
str4d committed Jul 18, 2024
1 parent 3d055df commit 0ccfdb2
Show file tree
Hide file tree
Showing 7 changed files with 379 additions and 0 deletions.
31 changes: 31 additions & 0 deletions zcash_client_backend/src/tor/http/cryptex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
65 changes: 65 additions & 0 deletions zcash_client_backend/src/tor/http/cryptex/binance.rs
Original file line number Diff line number Diff line change
@@ -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,
})
}
}
53 changes: 53 additions & 0 deletions zcash_client_backend/src/tor/http/cryptex/coinbase.rs
Original file line number Diff line number Diff line change
@@ -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,
})
}
}
56 changes: 56 additions & 0 deletions zcash_client_backend/src/tor/http/cryptex/gate_io.rs
Original file line number Diff line number Diff line change
@@ -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,
})
}
}
47 changes: 47 additions & 0 deletions zcash_client_backend/src/tor/http/cryptex/gemini.rs
Original file line number Diff line number Diff line change
@@ -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,
})
}
}
67 changes: 67 additions & 0 deletions zcash_client_backend/src/tor/http/cryptex/ku_coin.rs
Original file line number Diff line number Diff line change
@@ -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,
})
}
}
60 changes: 60 additions & 0 deletions zcash_client_backend/src/tor/http/cryptex/mexc.rs
Original file line number Diff line number Diff line change
@@ -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,
})
}
}

0 comments on commit 0ccfdb2

Please sign in to comment.