df_hyperliquid_core/account/
onboarding.rs1use crate::account::agent::{Agent, Role};
6use crate::account::mode::AbstractionMode;
7use crate::account::signers::ActionKind;
8use crate::account::state::{Abstraction, PerpState, SpotState};
9use crate::account::sub_account::AccountRef;
10use crate::client::Network;
11use crate::trade::builder_fee::BuilderFee;
12use crate::utils::{Address, Dec};
13
14pub const MIN_DEPOSIT: Dec = Dec::from_int(5);
16
17#[derive(Debug, Clone, Default, PartialEq, Eq)]
19pub struct Wants {
20 pub agent: Option<Address>,
22 pub builder: Option<(Address, BuilderFee)>,
24 pub mode: Option<AbstractionMode>,
25}
26
27#[derive(Debug, Clone, Copy)]
30pub struct Seen<'a> {
31 pub perp: &'a PerpState,
33 pub spot: &'a SpotState,
34 pub mode: &'a Abstraction,
35 pub agents: &'a [Agent],
37 pub agent_role: Option<&'a Role>,
39 pub approved: Option<BuilderFee>,
41}
42
43#[derive(Debug, Clone, PartialEq, Eq)]
45pub enum StepKind {
46 Deposit {
48 chain_id: u64,
49 min: Dec,
50 },
51 FundFromMaster,
53 ApproveAgent {
54 agent: Address,
55 },
56 ApproveBuilderFee {
57 builder: Address,
58 fee: BuilderFee,
59 },
60 SetMode {
61 mode: AbstractionMode,
62 },
63}
64
65impl StepKind {
66 #[must_use]
69 pub const fn id(&self) -> &'static str {
70 match self {
71 StepKind::Deposit { .. } | StepKind::FundFromMaster => "deposit",
72 StepKind::ApproveAgent { .. } => "approve-agent",
73 StepKind::ApproveBuilderFee { .. } => "builder-fee",
74 StepKind::SetMode { .. } => "account-mode",
75 }
76 }
77
78 #[must_use]
82 pub const fn action(&self) -> Option<ActionKind> {
83 match self {
84 StepKind::Deposit { .. } => None,
85 StepKind::FundFromMaster => Some(ActionKind::SubAccountTransfer),
86 StepKind::ApproveAgent { .. } => Some(ActionKind::ApproveAgent),
87 StepKind::ApproveBuilderFee { .. } => Some(ActionKind::ApproveBuilderFee),
88 StepKind::SetMode { .. } => Some(ActionKind::UserSetAbstraction),
89 }
90 }
91}
92
93#[derive(Debug, Clone, PartialEq, Eq)]
94pub enum Status {
95 Done,
96 Needed,
97 Blocked(Blocked),
98}
99
100#[derive(Debug, Clone, PartialEq, Eq)]
101pub enum Blocked {
102 DepositFirst,
104 Refused(String),
106}
107
108#[derive(Debug, Clone, PartialEq, Eq)]
109pub struct Step {
110 pub kind: StepKind,
111 pub status: Status,
112}
113
114#[must_use]
116pub fn deposited(perp: &PerpState, spot: &SpotState) -> bool {
117 perp.margin_summary.account_value.is_positive() || spot.balances.iter().any(|row| row.total.is_positive())
118}
119
120#[must_use]
124pub fn steps(account: &AccountRef, network: Network, seen: &Seen<'_>, wants: &Wants, now: u64) -> Vec<Step> {
125 let funded = deposited(seen.perp, seen.spot);
126 let after = |done: bool| match (done, funded) {
127 (true, _) => Status::Done,
128 (false, true) => Status::Needed,
129 (false, false) => Status::Blocked(Blocked::DepositFirst),
130 };
131 let deposit = match account.is_sub() {
132 true => StepKind::FundFromMaster,
133 false => StepKind::Deposit { chain_id: network.arbitrum_chain_id(), min: MIN_DEPOSIT },
134 };
135 let mut steps = vec![Step { kind: deposit, status: if funded { Status::Done } else { Status::Needed } }];
136 if let Some(agent) = wants.agent {
137 let listed = seen.agents.iter().any(|known| known.address == agent && known.is_live(now));
138 let signs = seen.agent_role.and_then(Role::agent_of) == Some(account.signs_for());
139 steps.push(Step { kind: StepKind::ApproveAgent { agent }, status: after(listed || signs) });
140 }
141 if let Some((builder, fee)) = wants.builder.filter(|_| !account.is_sub()) {
142 let covered = seen.approved.is_some_and(|approved| approved >= fee);
143 steps.push(Step { kind: StepKind::ApproveBuilderFee { builder, fee }, status: after(covered) });
144 }
145 if let Some(mode) = wants.mode {
146 let status = match (seen.mode.wire() == mode.wire(), mode.is_refused_from(seen.mode)) {
147 (true, _) => Status::Done,
148 (false, true) => {
149 Status::Blocked(Blocked::Refused("the venue refuses a unified account back to standard".to_owned()))
150 }
151 (false, false) => after(false),
152 };
153 steps.push(Step { kind: StepKind::SetMode { mode }, status });
154 }
155 steps
156}
157
158pub fn left(steps: &[Step]) -> impl Iterator<Item = &Step> {
160 steps.iter().filter(|step| step.status != Status::Done)
161}
162
163#[cfg(test)]
164#[path = "tests/onboarding.rs"]
165mod tests;