use std::fmt;
use crate::account::builder::ApproveBuilderFee;
use crate::account::state::{Abstraction, PerpState};
use crate::account::{abstraction, perp_state};
use crate::client::{ApiError, Transport};
use crate::trade::builder_fee::BuilderFee;
use crate::utils::{Address, Dec};
pub const BUILDER_MINIMUM: Dec = Dec::from_int(100);
pub const BUILDER_MODE: Abstraction = Abstraction::Disabled;
#[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: &PerpState, 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 = perp_state(transport, builder).await?;
let mode = abstraction(transport, builder).await?;
Ok(readiness(&perp, &mode))
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub enum Approved {
#[default]
Unread,
Upto(BuilderFee),
Failed(String),
}
impl Approved {
#[must_use]
pub fn after(read: Result<BuilderFee, ApiError>) -> Approved {
match read {
Ok(fee) => Approved::Upto(fee),
Err(error) => Approved::Failed(error.to_string()),
}
}
#[must_use]
pub fn taken(self, builder: Address, approval: &ApproveBuilderFee) -> Approved {
match (approval.builder() == builder, approval.max_fee()) {
(true, Some(fee)) => Approved::Upto(fee),
_ => self,
}
}
#[must_use]
pub fn words(&self) -> String {
match self {
Approved::Unread => "not read yet".to_owned(),
Approved::Upto(fee) if *fee == BuilderFee::ZERO => "nothing yet".to_owned(),
Approved::Upto(fee) => format!("up to {} ({} bps)", fee.percent(), bps(*fee)),
Approved::Failed(why) => format!("would not read: {why}"),
}
}
}
fn bps(fee: BuilderFee) -> String {
Dec::lit::<1>(i128::from(fee.tenths())).to_wire()
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Clearance {
Clear,
Approve { approved: BuilderFee },
Refused(String),
}
impl fmt::Display for Clearance {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Clearance::Clear => f.write_str("clear"),
Clearance::Approve { approved } => write!(f, "needs an approval over the {approved} approved"),
Clearance::Refused(why) => f.write_str(why),
}
}
}
#[must_use]
pub fn clearance(approved: &Approved, fee: Option<BuilderFee>) -> Clearance {
let Some(fee) = fee.filter(|fee| *fee > BuilderFee::ZERO) else {
return Clearance::Clear;
};
match approved {
Approved::Upto(approved) if *approved >= fee => Clearance::Clear,
Approved::Upto(approved) => Clearance::Approve { approved: *approved },
Approved::Unread => Clearance::Refused(
"the builder fee this account has approved is not read yet; try again in a moment".to_owned(),
),
Approved::Failed(why) => Clearance::Refused(format!(
"the builder fee this account has approved would not read, so it is not guessed at: {why}"
)),
}
}
#[cfg(test)]
#[path = "tests/readiness.rs"]
mod tests;