df_hyperliquid_core/account/keys/
builder.rs1use crate::account::holdings::perps::{ClearinghouseState, clearinghouse_state};
7use crate::account::settings::mode::{Abstraction, abstraction};
8use crate::client::{ApiError, Transport, info, info_request};
9use crate::markets::Dex;
10use crate::trade::builder::fee::BuilderFee;
11use crate::utils::sign::{Field, NonceKey, UserAction, UserSigned};
12use crate::utils::{Address, Dec, Json};
13
14pub const BUILDER_MINIMUM: Dec = Dec::from_int(100);
16
17pub const BUILDER_MODE: Abstraction = Abstraction::Disabled;
19
20#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct ApproveBuilderFee {
25 max_fee_rate: String,
26 builder: Address,
27 nonce: u64,
28}
29
30impl ApproveBuilderFee {
31 #[must_use]
32 pub fn new(builder: Address, max_fee: BuilderFee, nonce: u64) -> ApproveBuilderFee {
33 ApproveBuilderFee { max_fee_rate: max_fee.percent(), builder, nonce }
34 }
35
36 #[must_use]
38 pub fn max_fee_rate(&self) -> &str {
39 &self.max_fee_rate
40 }
41
42 #[must_use]
44 pub fn max_fee(&self) -> Option<BuilderFee> {
45 BuilderFee::from_percent(&self.max_fee_rate)
46 }
47
48 #[must_use]
49 pub const fn builder(&self) -> Address {
50 self.builder
51 }
52}
53
54impl UserAction for ApproveBuilderFee {
55 fn user_signed(&self) -> UserSigned<'_> {
56 UserSigned::new("approveBuilderFee", "HyperliquidTransaction:ApproveBuilderFee", NonceKey::Nonce, self.nonce)
57 .field("maxFeeRate", Field::String(&self.max_fee_rate))
58 .field("builder", Field::Address(self.builder))
59 }
60}
61
62#[must_use]
64pub fn max_builder_fee_request(user: Address, builder: Address) -> String {
65 info_request("maxBuilderFee", &[("user", Json::String(user.to_hex())), ("builder", Json::String(builder.to_hex()))])
66}
67
68pub async fn max_builder_fee(
71 transport: &impl Transport,
72 user: Address,
73 builder: Address,
74) -> Result<BuilderFee, ApiError> {
75 info(transport, max_builder_fee_request(user, builder)).await
76}
77
78#[derive(Debug, Clone, PartialEq, Eq)]
80pub enum Readiness {
81 Ready,
82 NotReady(Vec<NotReady>),
84}
85
86impl Readiness {
87 #[must_use]
88 pub const fn is_ready(&self) -> bool {
89 matches!(self, Readiness::Ready)
90 }
91}
92
93#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
95pub enum NotReady {
96 #[error("{value} USDC perps account value, under the {BUILDER_MINIMUM} a builder needs")]
97 TooLittle { value: Dec },
98 #[error("its account abstraction is {0}, and a builder needs standard")]
99 NotStandard(Abstraction),
100}
101
102#[must_use]
105pub fn readiness(perp: &ClearinghouseState, mode: &Abstraction) -> Readiness {
106 let value = perp.margin_summary.account_value;
107 let mut why = Vec::new();
108 if value < BUILDER_MINIMUM {
109 why.push(NotReady::TooLittle { value });
110 }
111 if *mode != BUILDER_MODE {
112 why.push(NotReady::NotStandard(mode.clone()));
113 }
114 match why.is_empty() {
115 true => Readiness::Ready,
116 false => Readiness::NotReady(why),
117 }
118}
119
120pub async fn builder_readiness(transport: &impl Transport, builder: Address) -> Result<Readiness, ApiError> {
124 let perp = clearinghouse_state(transport, builder, Dex::Main).await?;
125 let mode = abstraction(transport, builder).await?;
126 Ok(readiness(&perp, &mode))
127}
128
129#[cfg(test)]
130#[path = "tests/builder.rs"]
131mod tests;