use crate::client::{ApiError, Transport, info, info_request};
use crate::markets::dex::Dex;
use crate::markets::perps::table::MarginTable;
use crate::utils::{Asset, AssetKind, Dec, Grid};
use serde::Deserialize;
const THOUSAND: Dec = Dec::from_int(1000);
#[must_use]
pub fn meta_request(dex: Dex<'_>) -> String {
info_request("meta", dex.field().as_slice())
}
#[must_use]
pub fn meta_and_asset_ctxs_request(dex: Dex<'_>) -> String {
info_request("metaAndAssetCtxs", dex.field().as_slice())
}
pub async fn meta(transport: &impl Transport, dex: Dex<'_>) -> Result<Option<Meta>, ApiError> {
info(transport, meta_request(dex)).await
}
pub async fn meta_and_asset_ctxs(
transport: &impl Transport,
dex: Dex<'_>,
) -> Result<Option<MetaAndAssetCtxs>, ApiError> {
info(transport, meta_and_asset_ctxs_request(dex)).await
}
#[must_use]
pub fn perps_at_open_interest_cap_request(dex: Dex<'_>) -> String {
info_request("perpsAtOpenInterestCap", dex.field().as_slice())
}
pub async fn perps_at_open_interest_cap(transport: &impl Transport, dex: Dex<'_>) -> Result<Vec<String>, ApiError> {
info(transport, perps_at_open_interest_cap_request(dex)).await
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Meta {
pub universe: Vec<PerpAsset>,
#[serde(default)]
pub margin_tables: Vec<(u32, MarginTable)>,
#[serde(default)]
pub collateral_token: Option<u32>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PerpAsset {
pub name: String,
pub sz_decimals: u32,
pub max_leverage: u32,
#[serde(default)]
pub margin_table_id: Option<u32>,
#[serde(default)]
pub only_isolated: bool,
#[serde(default)]
pub is_delisted: bool,
#[serde(default)]
pub margin_mode: Option<String>,
#[serde(default)]
pub growth_mode: Option<String>,
#[serde(default)]
pub deployer_fee_scale: Option<Dec>,
}
impl PerpAsset {
#[must_use]
pub const fn grid(&self) -> Grid {
Grid::perp(self.sz_decimals)
}
#[must_use]
pub fn allows_cross(&self) -> bool {
!self.only_isolated && !matches!(self.margin_mode.as_deref(), Some("strictIsolated" | "noCross"))
}
}
#[must_use]
pub fn underlying(coin: &str) -> (&str, Dec) {
let bare = coin.split_once(':').map_or(coin, |(_, symbol)| symbol);
match bare.strip_prefix('k') {
Some(rest) if rest.starts_with(|first: char| first.is_ascii_uppercase()) => (rest, THOUSAND),
_ => (bare, Dec::ONE),
}
}
const fn place_of(asset: Asset) -> Option<(u32, u32)> {
match asset.kind() {
AssetKind::Perp => Some((0, asset.id())),
_ => asset.hip3_parts(),
}
}
impl Meta {
pub fn perps(&self, place: u32) -> impl Iterator<Item = (Asset, &PerpAsset)> {
self.universe
.iter()
.enumerate()
.filter_map(move |(index, perp)| Some((Asset::perp_on(place, u32::try_from(index).ok()?)?, perp)))
}
#[must_use]
pub fn find(&self, place: u32, name: &str) -> Option<(Asset, &PerpAsset)> {
self.perps(place).find(|(_, perp)| perp.name == name)
}
#[must_use]
pub fn get(&self, place: u32, asset: Asset) -> Option<&PerpAsset> {
match place_of(asset)? {
(at, index) if at == place => self.universe.get(usize::try_from(index).ok()?),
_ => None,
}
}
}
pub use crate::markets::context::PerpCtx;
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(from = "(Meta, Vec<PerpCtx>)")]
pub struct MetaAndAssetCtxs {
pub meta: Meta,
pub ctxs: Vec<PerpCtx>,
}
impl From<(Meta, Vec<PerpCtx>)> for MetaAndAssetCtxs {
fn from((meta, ctxs): (Meta, Vec<PerpCtx>)) -> Self {
MetaAndAssetCtxs { meta, ctxs }
}
}
impl MetaAndAssetCtxs {
#[must_use]
pub fn ctx(&self, place: u32, asset: Asset) -> Option<&PerpCtx> {
self.meta.get(place, asset)?;
let (_, index) = place_of(asset)?;
self.ctxs.get(usize::try_from(index).ok()?)
}
#[must_use]
pub fn find(&self, place: u32, name: &str) -> Option<(Asset, &PerpAsset, &PerpCtx)> {
let (asset, perp) = self.meta.find(place, name)?;
Some((asset, perp, self.ctx(place, asset)?))
}
}
#[cfg(test)]
#[path = "tests/meta.rs"]
mod tests;