use crate::account::agent::{Agent, Role};
use crate::account::mode::AbstractionMode;
use crate::account::signers::ActionKind;
use crate::account::state::{Abstraction, PerpState, SpotState};
use crate::account::sub_account::AccountRef;
use crate::client::Network;
use crate::trade::builder_fee::BuilderFee;
use crate::utils::{Address, Dec};
pub const MIN_DEPOSIT: Dec = Dec::from_int(5);
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Wants {
pub agent: Option<Address>,
pub builder: Option<(Address, BuilderFee)>,
pub mode: Option<AbstractionMode>,
}
#[derive(Debug, Clone, Copy)]
pub struct Seen<'a> {
pub perp: &'a PerpState,
pub spot: &'a SpotState,
pub mode: &'a Abstraction,
pub agents: &'a [Agent],
pub agent_role: Option<&'a Role>,
pub approved: Option<BuilderFee>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StepKind {
Deposit {
chain_id: u64,
min: Dec,
},
FundFromMaster,
ApproveAgent {
agent: Address,
},
ApproveBuilderFee {
builder: Address,
fee: BuilderFee,
},
SetMode {
mode: AbstractionMode,
},
}
impl StepKind {
#[must_use]
pub const fn id(&self) -> &'static str {
match self {
StepKind::Deposit { .. } | StepKind::FundFromMaster => "deposit",
StepKind::ApproveAgent { .. } => "approve-agent",
StepKind::ApproveBuilderFee { .. } => "builder-fee",
StepKind::SetMode { .. } => "account-mode",
}
}
#[must_use]
pub const fn action(&self) -> Option<ActionKind> {
match self {
StepKind::Deposit { .. } => None,
StepKind::FundFromMaster => Some(ActionKind::SubAccountTransfer),
StepKind::ApproveAgent { .. } => Some(ActionKind::ApproveAgent),
StepKind::ApproveBuilderFee { .. } => Some(ActionKind::ApproveBuilderFee),
StepKind::SetMode { .. } => Some(ActionKind::UserSetAbstraction),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Status {
Done,
Needed,
Blocked(Blocked),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Blocked {
DepositFirst,
Refused(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Step {
pub kind: StepKind,
pub status: Status,
}
#[must_use]
pub fn deposited(perp: &PerpState, spot: &SpotState) -> bool {
perp.margin_summary.account_value.is_positive() || spot.balances.iter().any(|row| row.total.is_positive())
}
#[must_use]
pub fn steps(account: &AccountRef, network: Network, seen: &Seen<'_>, wants: &Wants, now: u64) -> Vec<Step> {
let funded = deposited(seen.perp, seen.spot);
let after = |done: bool| match (done, funded) {
(true, _) => Status::Done,
(false, true) => Status::Needed,
(false, false) => Status::Blocked(Blocked::DepositFirst),
};
let deposit = match account.is_sub() {
true => StepKind::FundFromMaster,
false => StepKind::Deposit { chain_id: network.arbitrum_chain_id(), min: MIN_DEPOSIT },
};
let mut steps = vec![Step { kind: deposit, status: if funded { Status::Done } else { Status::Needed } }];
if let Some(agent) = wants.agent {
let listed = seen.agents.iter().any(|known| known.address == agent && known.is_live(now));
let signs = seen.agent_role.and_then(Role::agent_of) == Some(account.signs_for());
steps.push(Step { kind: StepKind::ApproveAgent { agent }, status: after(listed || signs) });
}
if let Some((builder, fee)) = wants.builder.filter(|_| !account.is_sub()) {
let covered = seen.approved.is_some_and(|approved| approved >= fee);
steps.push(Step { kind: StepKind::ApproveBuilderFee { builder, fee }, status: after(covered) });
}
if let Some(mode) = wants.mode {
let status = match (seen.mode.wire() == mode.wire(), mode.is_refused_from(seen.mode)) {
(true, _) => Status::Done,
(false, true) => {
Status::Blocked(Blocked::Refused("the venue refuses a unified account back to standard".to_owned()))
}
(false, false) => after(false),
};
steps.push(Step { kind: StepKind::SetMode { mode }, status });
}
steps
}
pub fn left(steps: &[Step]) -> impl Iterator<Item = &Step> {
steps.iter().filter(|step| step.status != Status::Done)
}
#[cfg(test)]
#[path = "tests/onboarding.rs"]
mod tests;