use std::fmt;
use serde::{Deserialize, Deserializer, de};
use crate::client::{ApiError, Transport, info, user_request};
use crate::utils::Address;
use crate::utils::sign::{Field, L1Action, Map, NonceKey, UserAction, UserSigned};
#[must_use]
pub fn abstraction_request(user: Address) -> String {
user_request("userAbstraction", user)
}
pub async fn abstraction(transport: &impl Transport, user: Address) -> Result<Abstraction, ApiError> {
info(transport, abstraction_request(user)).await
}
#[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)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AbstractionMode {
Disabled,
UnifiedAccount,
PortfolioMargin,
}
impl AbstractionMode {
pub const ALL: [AbstractionMode; 3] =
[AbstractionMode::Disabled, AbstractionMode::UnifiedAccount, AbstractionMode::PortfolioMargin];
#[must_use]
pub const fn wire(self) -> &'static str {
match self {
AbstractionMode::Disabled => "disabled",
AbstractionMode::UnifiedAccount => "unifiedAccount",
AbstractionMode::PortfolioMargin => "portfolioMargin",
}
}
#[must_use]
pub const fn code(self) -> &'static str {
match self {
AbstractionMode::Disabled => "i",
AbstractionMode::UnifiedAccount => "u",
AbstractionMode::PortfolioMargin => "p",
}
}
#[must_use]
pub fn from_wire(text: &str) -> Option<AbstractionMode> {
AbstractionMode::ALL.into_iter().find(|mode| mode.wire() == text)
}
#[must_use]
pub fn from_code(text: &str) -> Option<AbstractionMode> {
AbstractionMode::ALL.into_iter().find(|mode| mode.code() == text)
}
#[must_use]
pub const fn is_refused_from(self, current: &Abstraction) -> bool {
matches!((current, self), (Abstraction::UnifiedAccount, AbstractionMode::Disabled))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct UserSetAbstraction {
user: Address,
mode: AbstractionMode,
nonce: u64,
}
impl UserSetAbstraction {
#[must_use]
pub const fn new(user: Address, mode: AbstractionMode, nonce: u64) -> UserSetAbstraction {
UserSetAbstraction { user, mode, nonce }
}
}
impl UserAction for UserSetAbstraction {
fn user_signed(&self) -> UserSigned<'_> {
UserSigned::new("userSetAbstraction", "HyperliquidTransaction:UserSetAbstraction", NonceKey::Nonce, self.nonce)
.field("user", Field::Address(self.user))
.field("abstraction", Field::String(self.mode.wire()))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AgentSetAbstraction {
mode: AbstractionMode,
}
impl AgentSetAbstraction {
#[must_use]
pub const fn new(mode: AbstractionMode) -> AgentSetAbstraction {
AgentSetAbstraction { mode }
}
}
impl L1Action for AgentSetAbstraction {
fn action(&self) -> Map {
Map::new().put("type", "agentSetAbstraction").put("abstraction", self.mode.code())
}
}
#[cfg(test)]
#[path = "tests/mode.rs"]
mod tests;