Skip to main content

df_hyperliquid_core/trade/builder/
setting.rs

1//! Builder fees as a setting: what each market kind pays the caller's builder, held to that kind's
2//! cap, and what an order paying it needs first, by what the account has approved and which key
3//! signs. Whether the builder can be paid at all is [`crate::account::builder_readiness`].
4
5use crate::account::Key;
6use crate::trade::builder::fee::{Builder, BuilderFee, OverCap};
7use crate::trade::orders::PlaceOrder;
8use crate::utils::{Address, Asset, AssetKind};
9
10/// What each order pays the builder.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum FeeRule {
13    /// This many percent of each market kind's cap, cut down to a whole tenth of a basis point: 88 is
14    /// 88 of a perp's 100 and 880 of spot's 1000. At most 100.
15    ShareOfCap(u64),
16    /// One fee on every market; an order whose market allows less is refused,
17    /// never lowered.
18    Flat(BuilderFee),
19}
20
21/// The builder orders are routed through, and what they pay it.
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub struct BuilderSetting {
24    address: Address,
25    rule: FeeRule,
26}
27
28impl BuilderSetting {
29    /// `percent` of each market's cap; `None` over 100.
30    #[must_use]
31    pub const fn share_of_cap(address: Address, percent: u64) -> Option<BuilderSetting> {
32        match percent <= 100 {
33            true => Some(BuilderSetting { address, rule: FeeRule::ShareOfCap(percent) }),
34            false => None,
35        }
36    }
37
38    /// `fee` on every market.
39    #[must_use]
40    pub const fn flat(address: Address, fee: BuilderFee) -> BuilderSetting {
41        BuilderSetting { address, rule: FeeRule::Flat(fee) }
42    }
43
44    #[must_use]
45    pub const fn address(&self) -> Address {
46        self.address
47    }
48
49    #[must_use]
50    pub const fn rule(&self) -> FeeRule {
51        self.rule
52    }
53
54    /// What an order on `kind` pays, whether the market allows it or not.
55    #[must_use]
56    pub const fn fee_on(&self, kind: AssetKind) -> BuilderFee {
57        match self.rule {
58            FeeRule::ShareOfCap(percent) => {
59                BuilderFee::from_tenths(kind.builder_cap().tenths().saturating_mul(percent) / 100)
60            }
61            FeeRule::Flat(fee) => fee,
62        }
63    }
64
65    /// The builder an order on `kind` names, or why it cannot.
66    pub fn on(&self, kind: AssetKind) -> Result<Builder, OverCap> {
67        let fee = self.fee_on(kind);
68        let cap = kind.builder_cap();
69        match fee > cap {
70            true => Err(OverCap { fee, cap, kind }),
71            false => Ok(Builder::new(self.address, fee)),
72        }
73    }
74
75    /// The builder one action over these assets names: held to the
76    /// strictest kind among them, so one perp order holds the whole action
77    /// to the perp cap. `None` for no assets.
78    pub fn on_assets(&self, assets: impl IntoIterator<Item = Asset>) -> Result<Option<Builder>, OverCap> {
79        AssetKind::strictest(assets).map(|kind| self.on(kind)).transpose()
80    }
81
82    /// The most an order on any market it may trade pays, which is what one
83    /// approval has to cover: 880 at 88% of each cap.
84    #[must_use]
85    pub fn most(&self) -> BuilderFee {
86        AssetKind::ALL
87            .into_iter()
88            .filter_map(|kind| self.on(kind).ok())
89            .map(|builder| builder.fee())
90            .max()
91            .unwrap_or_default()
92    }
93
94    /// What `order` needs before it is sent, by what the account has approved this builder to charge
95    /// and by which key signs. Only the account's own key may sign `approveBuilderFee`.
96    pub fn approval(&self, order: &PlaceOrder, approved: &Approved, by: Key) -> Result<Approval, ApprovalRefused> {
97        let Some(builder) = order.builder().filter(|builder| builder.fee() > BuilderFee::ZERO) else {
98            return Ok(Approval::Goes);
99        };
100        if builder.address() != self.address {
101            return Err(ApprovalRefused::OtherBuilder { named: builder.address(), set: self.address });
102        }
103        let fee = builder.fee();
104        let approved = match approved {
105            Approved::Upto(approved) if *approved >= fee => return Ok(Approval::Goes),
106            Approved::Upto(approved) => *approved,
107            Approved::Unread => return Err(ApprovalRefused::NotRead),
108            Approved::Failed(why) => return Err(ApprovalRefused::Unreadable(why.clone())),
109        };
110        let rate = self.most().max(fee);
111        match by {
112            Key::Main => Ok(Approval::ApproveFirst { approved, rate }),
113            Key::Agent => Err(ApprovalRefused::AgentCannotApprove { approved, rate }),
114        }
115    }
116}
117
118/// What the account has approved its builder to charge, as `maxBuilderFee`
119/// last answered or as the last approval the venue took.
120#[derive(Debug, Clone, PartialEq, Eq, Default)]
121pub enum Approved {
122    /// Nothing read yet.
123    #[default]
124    Unread,
125    /// Up to this.
126    Upto(BuilderFee),
127    /// The read failed, and why.
128    Failed(String),
129}
130
131impl Approved {
132    /// What the account has approved, in words.
133    #[must_use]
134    pub fn words(&self) -> String {
135        match self {
136            Approved::Unread => "not read yet".to_owned(),
137            Approved::Upto(fee) if *fee == BuilderFee::ZERO => "nothing yet".to_owned(),
138            Approved::Upto(fee) => format!("up to {fee}"),
139            Approved::Failed(why) => format!("would not read: {why}"),
140        }
141    }
142}
143
144/// What an order paying a builder needs before it is sent.
145#[derive(Debug, Clone, Copy, PartialEq, Eq)]
146pub enum Approval {
147    /// Nothing: no builder, a fee of 0, or a fee already approved.
148    Goes,
149    /// `approveBuilderFee` at `rate` first, signed by the account: it has
150    /// approved `approved`, under the order's fee. `rate` is the most any
151    /// market pays, so one approval covers them all.
152    ApproveFirst { approved: BuilderFee, rate: BuilderFee },
153}
154
155impl Approval {
156    /// The `maxFeeRate` `approveBuilderFee` names: `0.88%` for 880.
157    #[must_use]
158    pub fn max_fee_rate(&self) -> Option<String> {
159        match self {
160            Approval::Goes => None,
161            Approval::ApproveFirst { rate, .. } => Some(rate.percent()),
162        }
163    }
164}
165
166/// Why an order cannot go for its builder fee: what is not known is refused
167/// rather than guessed at.
168#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
169pub enum ApprovalRefused {
170    #[error("the builder fee this account has approved is not read yet; try again in a moment")]
171    NotRead,
172    #[error("the builder fee this account has approved would not read, so it is not guessed at: {0}")]
173    Unreadable(String),
174    #[error(
175        "the account's main key has to approve the builder fee up to {rate} once (approved now: {approved}); an agent key cannot sign approveBuilderFee"
176    )]
177    AgentCannotApprove { approved: BuilderFee, rate: BuilderFee },
178    #[error("the order names the builder {named}, and what the account approved was read for {set}")]
179    OtherBuilder { named: Address, set: Address },
180}
181
182#[cfg(test)]
183#[path = "tests/setting.rs"]
184mod tests;