use std::fmt;
use crate::client::{info_request, user_request};
use crate::utils::{Address, Dec, Json};
use serde::{Deserialize, Deserializer, de};
#[must_use]
pub fn spot_state_request(user: Address) -> String {
user_request("spotClearinghouseState", user)
}
#[must_use]
pub fn perp_state_request(user: Address) -> String {
user_request("clearinghouseState", user)
}
#[must_use]
pub fn perp_state_on_request(user: Address, dex: &str) -> String {
if dex.is_empty() {
return perp_state_request(user);
}
info_request("clearinghouseState", &[("user", Json::String(user.to_hex())), ("dex", Json::String(dex.to_owned()))])
}
#[must_use]
pub fn abstraction_request(user: Address) -> String {
user_request("userAbstraction", user)
}
pub use crate::account::clearinghouse::{SpotBalance, SpotState};
pub trait SpotRisk {
fn risk(&self) -> Risk;
}
impl SpotRisk for SpotState {
fn risk(&self) -> Risk {
Risk::of(self.portfolio_margin_ratio)
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub enum Risk {
#[default]
Safe,
Near,
Liquidatable,
}
impl Risk {
#[must_use]
pub fn of(ratio: Option<Dec>) -> Risk {
let (near, liquidatable) = (Dec::lit::<2>(85), Dec::lit::<2>(95));
match ratio {
Some(ratio) if ratio > liquidatable => Risk::Liquidatable,
Some(ratio) if ratio >= near => Risk::Near,
_ => Risk::Safe,
}
}
}
pub type PerpState = crate::account::clearinghouse::Clearinghouse;
pub use crate::account::clearinghouse::{AssetPosition, CumFunding, Leverage, MarginMode, MarginSummary, Position};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Abstraction {
Default,
Disabled,
UnifiedAccount,
PortfolioMargin,
DexAbstraction,
Unknown(String),
}
impl Abstraction {
#[must_use]
pub fn parse(text: &str) -> Abstraction {
match text {
"default" => Abstraction::Default,
"disabled" => Abstraction::Disabled,
"unifiedAccount" => Abstraction::UnifiedAccount,
"portfolioMargin" => Abstraction::PortfolioMargin,
"dexAbstraction" => Abstraction::DexAbstraction,
other => Abstraction::Unknown(other.to_owned()),
}
}
#[must_use]
pub fn wire(&self) -> &str {
match self {
Abstraction::Default => "default",
Abstraction::Disabled => "disabled",
Abstraction::UnifiedAccount => "unifiedAccount",
Abstraction::PortfolioMargin => "portfolioMargin",
Abstraction::DexAbstraction => "dexAbstraction",
Abstraction::Unknown(other) => other,
}
}
#[must_use]
pub const fn is_pooled(&self) -> bool {
matches!(self, Abstraction::UnifiedAccount | Abstraction::PortfolioMargin)
}
#[must_use]
pub const fn is_standard(&self) -> bool {
!self.is_pooled()
}
}
impl fmt::Display for Abstraction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.wire())
}
}
impl<'de> Deserialize<'de> for Abstraction {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct Visitor;
impl de::Visitor<'_> for Visitor {
type Value = Abstraction;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("an account mode as a string")
}
fn visit_str<E: de::Error>(self, text: &str) -> Result<Abstraction, E> {
Ok(Abstraction::parse(text))
}
}
deserializer.deserialize_str(Visitor)
}
}
#[cfg(test)]
#[path = "tests/state.rs"]
mod tests;