use crate::client::{ApiError, Transport, info, info_request};
use crate::markets::dexes::ids::asset_parts;
use crate::trade::dexes::MarginTable;
use crate::utils::Grid;
use crate::utils::{Address, Asset, Dec, Json};
use serde::Deserialize;
pub(crate) fn request(kind: &str, dex: Option<&str>) -> String {
match dex {
Some(dex) => info_request(kind, &[("dex", Json::String(dex.to_owned()))]),
None => info_request(kind, &[]),
}
}
#[must_use]
pub fn perp_dexs_request() -> String {
request("perpDexs", None)
}
#[must_use]
pub fn dex_meta_request(dex: &str) -> String {
request("meta", Some(dex))
}
#[must_use]
pub fn dex_meta_and_asset_ctxs_request(dex: &str) -> String {
request("metaAndAssetCtxs", Some(dex))
}
pub async fn perp_dexs(transport: &impl Transport) -> Result<PerpDexs, ApiError> {
info(transport, perp_dexs_request()).await
}
pub async fn dex_meta(transport: &impl Transport, dex: &str) -> Result<Option<DexMeta>, ApiError> {
info(transport, dex_meta_request(dex)).await
}
pub async fn dex_meta_and_asset_ctxs(
transport: &impl Transport,
dex: &str,
) -> Result<Option<DexMetaAndAssetCtxs>, ApiError> {
info(transport, dex_meta_and_asset_ctxs_request(dex)).await
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(transparent)]
pub struct PerpDexs(pub Vec<Option<PerpDex>>);
impl PerpDexs {
pub fn dexes(&self) -> impl Iterator<Item = (u32, &PerpDex)> {
self.0.iter().enumerate().filter_map(|(at, dex)| Some((u32::try_from(at).ok()?, dex.as_ref()?)))
}
#[must_use]
pub fn index_of(&self, name: &str) -> Option<u32> {
self.dexes().find(|(_, dex)| dex.name == name).map(|(at, _)| at)
}
#[must_use]
pub fn get(&self, name: &str) -> Option<&PerpDex> {
self.dexes().find(|(_, dex)| dex.name == name).map(|(_, dex)| dex)
}
#[must_use]
pub fn name_of(&self, place: u32) -> Option<&str> {
self.0.get(usize::try_from(place).ok()?)?.as_ref().map(|dex| dex.name.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PerpDex {
pub name: String,
pub full_name: String,
#[serde(default)]
pub deployer: Option<Address>,
#[serde(default)]
pub oracle_updater: Option<Address>,
#[serde(default)]
pub fee_recipient: Option<Address>,
#[serde(default)]
pub sub_deployers: Vec<(String, Vec<Address>)>,
#[serde(default)]
pub asset_to_streaming_oi_cap: Vec<(String, Dec)>,
#[serde(default)]
pub asset_to_funding_multiplier: Vec<(String, Dec)>,
#[serde(default)]
pub asset_to_funding_interest_rate: Vec<(String, Dec)>,
#[serde(default)]
pub asset_to_funding_clamp: Vec<(String, Dec)>,
}
impl PerpDex {
#[must_use]
pub fn streaming_oi_cap(&self, coin: &str) -> Option<Dec> {
lookup(&self.asset_to_streaming_oi_cap, coin)
}
#[must_use]
pub fn funding_multiplier(&self, coin: &str) -> Option<Dec> {
lookup(&self.asset_to_funding_multiplier, coin)
}
#[must_use]
pub fn funding_interest_rate(&self, coin: &str) -> Option<Dec> {
lookup(&self.asset_to_funding_interest_rate, coin)
}
#[must_use]
pub fn funding_clamp(&self, coin: &str) -> Option<Dec> {
lookup(&self.asset_to_funding_clamp, coin)
}
#[must_use]
pub fn sub_deployers_for(&self, action: &str) -> &[Address] {
self.sub_deployers.iter().find(|(listed, _)| listed == action).map_or(&[], |(_, users)| users.as_slice())
}
}
fn lookup(pairs: &[(String, Dec)], coin: &str) -> Option<Dec> {
pairs.iter().find(|(listed, _)| listed == coin).map(|(_, value)| *value)
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DexMeta {
pub universe: Vec<DexAsset>,
#[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 DexAsset {
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 DexAsset {
#[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"))
}
}
impl DexMeta {
pub fn perps(&self, dex: u32) -> impl Iterator<Item = (Asset, &DexAsset)> {
self.universe
.iter()
.enumerate()
.filter_map(move |(index, perp)| Some((Asset::hip3(dex, u32::try_from(index).ok()?)?, perp)))
}
#[must_use]
pub fn find(&self, dex: u32, coin: &str) -> Option<(Asset, &DexAsset)> {
self.perps(dex).find(|(_, perp)| perp.name == coin)
}
#[must_use]
pub fn get(&self, dex: u32, asset: Asset) -> Option<&DexAsset> {
match asset_parts(asset)? {
(place, index) if place == dex => self.universe.get(usize::try_from(index).ok()?),
_ => None,
}
}
}
pub type DexCtx = crate::markets::context::PerpCtx;
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(from = "(DexMeta, Vec<DexCtx>)")]
pub struct DexMetaAndAssetCtxs {
pub meta: DexMeta,
pub ctxs: Vec<DexCtx>,
}
impl From<(DexMeta, Vec<DexCtx>)> for DexMetaAndAssetCtxs {
fn from((meta, ctxs): (DexMeta, Vec<DexCtx>)) -> Self {
DexMetaAndAssetCtxs { meta, ctxs }
}
}
impl DexMetaAndAssetCtxs {
#[must_use]
pub fn find(&self, dex: u32, coin: &str) -> Option<(Asset, &DexAsset, &DexCtx)> {
let index = self.meta.universe.iter().position(|perp| perp.name == coin)?;
let asset = Asset::hip3(dex, u32::try_from(index).ok()?)?;
Some((asset, self.meta.universe.get(index)?, self.ctxs.get(index)?))
}
}
#[cfg(test)]
#[path = "tests/dex.rs"]
mod tests;