use crate::account::holdings::perps::{ClearinghouseState, clearinghouse_state};
use crate::account::settings::mode::{Abstraction, abstraction};
use crate::client::{ApiError, Transport, info, info_request};
use crate::markets::Dex;
use crate::trade::builder::fee::BuilderFee;
use crate::utils::sign::{Field, NonceKey, UserAction, UserSigned};
use crate::utils::{Address, Dec, Json};
pub const BUILDER_MINIMUM: Dec = Dec::from_int(100);
pub const BUILDER_MODE: Abstraction = Abstraction::Disabled;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ApproveBuilderFee {
max_fee_rate: String,
builder: Address,
nonce: u64,
}
impl ApproveBuilderFee {
#[must_use]
pub fn new(builder: Address, max_fee: BuilderFee, nonce: u64) -> ApproveBuilderFee {
ApproveBuilderFee { max_fee_rate: max_fee.percent(), builder, nonce }
}
#[must_use]
pub fn max_fee_rate(&self) -> &str {
&self.max_fee_rate
}
#[must_use]
pub fn max_fee(&self) -> Option<BuilderFee> {
BuilderFee::from_percent(&self.max_fee_rate)
}
#[must_use]
pub const fn builder(&self) -> Address {
self.builder
}
}
impl UserAction for ApproveBuilderFee {
fn user_signed(&self) -> UserSigned<'_> {
UserSigned::new("approveBuilderFee", "HyperliquidTransaction:ApproveBuilderFee", NonceKey::Nonce, self.nonce)
.field("maxFeeRate", Field::String(&self.max_fee_rate))
.field("builder", Field::Address(self.builder))
}
}
#[must_use]
pub fn max_builder_fee_request(user: Address, builder: Address) -> String {
info_request("maxBuilderFee", &[("user", Json::String(user.to_hex())), ("builder", Json::String(builder.to_hex()))])
}
pub async fn max_builder_fee(
transport: &impl Transport,
user: Address,
builder: Address,
) -> Result<BuilderFee, ApiError> {
info(transport, max_builder_fee_request(user, builder)).await
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Readiness {
Ready,
NotReady(Vec<NotReady>),
}
impl Readiness {
#[must_use]
pub const fn is_ready(&self) -> bool {
matches!(self, Readiness::Ready)
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum NotReady {
#[error("{value} USDC perps account value, under the {BUILDER_MINIMUM} a builder needs")]
TooLittle { value: Dec },
#[error("its account abstraction is {0}, and a builder needs standard")]
NotStandard(Abstraction),
}
#[must_use]
pub fn readiness(perp: &ClearinghouseState, mode: &Abstraction) -> Readiness {
let value = perp.margin_summary.account_value;
let mut why = Vec::new();
if value < BUILDER_MINIMUM {
why.push(NotReady::TooLittle { value });
}
if *mode != BUILDER_MODE {
why.push(NotReady::NotStandard(mode.clone()));
}
match why.is_empty() {
true => Readiness::Ready,
false => Readiness::NotReady(why),
}
}
pub async fn builder_readiness(transport: &impl Transport, builder: Address) -> Result<Readiness, ApiError> {
let perp = clearinghouse_state(transport, builder, Dex::Main).await?;
let mode = abstraction(transport, builder).await?;
Ok(readiness(&perp, &mode))
}
#[cfg(test)]
#[path = "tests/builder.rs"]
mod tests;