use crate::client::{ApiError, Transport, USDC_INDEX, info, info_request};
use crate::utils::Grid;
use crate::utils::{Address, Asset, AssetKind, Dec, SPOT_ASSET_BASE};
use serde::Deserialize;
#[must_use]
pub fn spot_meta_request() -> String {
info_request("spotMeta", &[])
}
#[must_use]
pub fn spot_meta_and_asset_ctxs_request() -> String {
info_request("spotMetaAndAssetCtxs", &[])
}
pub async fn spot_meta(transport: &impl Transport) -> Result<SpotMeta, ApiError> {
info(transport, spot_meta_request()).await
}
pub async fn spot_meta_and_asset_ctxs(transport: &impl Transport) -> Result<SpotMetaAndAssetCtxs, ApiError> {
info(transport, spot_meta_and_asset_ctxs_request()).await
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct SpotMeta {
pub tokens: Vec<SpotToken>,
pub universe: Vec<SpotPair>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SpotToken {
pub name: String,
pub index: u32,
pub token_id: String,
pub sz_decimals: u32,
pub wei_decimals: u32,
#[serde(default)]
pub is_canonical: bool,
#[serde(default)]
pub evm_contract: Option<EvmContract>,
#[serde(default)]
pub full_name: Option<String>,
#[serde(default)]
pub deployer_trading_fee_share: Option<Dec>,
#[serde(default)]
pub deployer_label: Option<String>,
}
impl SpotToken {
#[must_use]
pub fn wire(&self) -> String {
format!("{}:{}", self.name, self.token_id)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct EvmContract {
pub address: Address,
#[serde(rename = "evm_extra_wei_decimals")]
pub extra_wei_decimals: i32,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SpotPair {
pub name: String,
pub tokens: [u32; 2],
pub index: u32,
#[serde(default)]
pub is_canonical: bool,
}
impl SpotPair {
#[must_use]
pub const fn base(&self) -> u32 {
self.tokens[0]
}
#[must_use]
pub const fn quote(&self) -> u32 {
self.tokens[1]
}
#[must_use]
pub const fn asset(&self) -> Option<Asset> {
Asset::spot(self.index)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SpotMarket<'a> {
pub pair: &'a SpotPair,
pub base: &'a SpotToken,
pub quote: &'a SpotToken,
}
impl SpotMarket<'_> {
#[must_use]
pub const fn asset(&self) -> Option<Asset> {
self.pair.asset()
}
#[must_use]
pub const fn grid(&self) -> Grid {
Grid::spot(self.base.sz_decimals)
}
#[must_use]
pub fn display_name(&self) -> String {
format!("{}/{}", self.base.name, self.quote.name)
}
}
impl SpotMeta {
#[must_use]
pub fn token(&self, index: u32) -> Option<&SpotToken> {
self.tokens.iter().find(|token| token.index == index)
}
#[must_use]
pub fn usdc(&self) -> Option<&SpotToken> {
self.token(USDC_INDEX)
}
#[must_use]
pub fn token_by_id(&self, token_id: &str) -> Option<&SpotToken> {
self.tokens.iter().find(|token| token.token_id.eq_ignore_ascii_case(token_id))
}
#[must_use]
pub fn token_named(&self, name: &str) -> Option<&SpotToken> {
self.tokens.iter().find(|token| token.name == name)
}
#[must_use]
pub fn token_by_wire(&self, wire: &str) -> Option<&SpotToken> {
let (name, id) = wire.split_once(':')?;
self.tokens.iter().find(|token| token.name == name && token.token_id.eq_ignore_ascii_case(id))
}
#[must_use]
pub fn pair(&self, index: u32) -> Option<&SpotPair> {
self.universe.iter().find(|pair| pair.index == index)
}
#[must_use]
pub fn pair_named(&self, name: &str) -> Option<&SpotPair> {
self.universe.iter().find(|pair| pair.name == name)
}
#[must_use]
pub fn pair_of(&self, base: u32, quote: u32) -> Option<&SpotPair> {
self.universe.iter().find(|pair| pair.tokens == [base, quote])
}
#[must_use]
pub fn market(&self, index: u32) -> Option<SpotMarket<'_>> {
let pair = self.pair(index)?;
Some(SpotMarket { pair, base: self.token(pair.base())?, quote: self.token(pair.quote())? })
}
#[must_use]
pub fn market_of(&self, base: &str, quote: &str) -> Option<SpotMarket<'_>> {
let pair = self.pair_of(self.token_named(base)?.index, self.token_named(quote)?.index)?;
self.market(pair.index)
}
#[must_use]
pub fn market_named(&self, name: &str) -> Option<SpotMarket<'_>> {
self.market(self.pair_named(name)?.index)
}
#[must_use]
pub fn market_at(&self, asset: Asset) -> Option<SpotMarket<'_>> {
match asset.kind() {
AssetKind::Spot => self.market(asset.id().checked_sub(SPOT_ASSET_BASE)?),
_ => None,
}
}
}
pub use crate::markets::context::SpotCtx;
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(from = "(SpotMeta, Vec<SpotCtx>)")]
pub struct SpotMetaAndAssetCtxs {
pub meta: SpotMeta,
pub ctxs: Vec<SpotCtx>,
}
impl From<(SpotMeta, Vec<SpotCtx>)> for SpotMetaAndAssetCtxs {
fn from((meta, ctxs): (SpotMeta, Vec<SpotCtx>)) -> Self {
SpotMetaAndAssetCtxs { meta, ctxs }
}
}
impl SpotMetaAndAssetCtxs {
#[must_use]
pub fn ctx(&self, coin: &str) -> Option<&SpotCtx> {
self.ctxs.iter().find(|ctx| ctx.coin == coin)
}
#[must_use]
pub fn market_ctx(&self, index: u32) -> Option<(SpotMarket<'_>, &SpotCtx)> {
let market = self.meta.market(index)?;
Some((market, self.ctx(&market.pair.name)?))
}
#[must_use]
pub fn usdc_price(&self, token: u32) -> Option<Dec> {
if token == USDC_INDEX {
return Some(Dec::ONE);
}
let pair = self.meta.pair_of(token, USDC_INDEX)?;
Some(self.market_ctx(pair.index)?.1.mark_px)
}
}
#[cfg(test)]
#[path = "tests/meta.rs"]
mod tests;