df_hyperliquid_core/trade/
builder.rs1use crate::trade::builder_fee::BuilderFee;
6use crate::trade::order::{Builder, OverCap, PlaceOrder};
7use crate::utils::{Address, Asset, AssetKind};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum FeeRule {
12 ShareOfCap(u64),
15 Flat(BuilderFee),
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub struct BuilderSetting {
23 address: Address,
24 rule: FeeRule,
25}
26
27impl BuilderSetting {
28 #[must_use]
30 pub const fn share_of_cap(address: Address, percent: u64) -> Option<BuilderSetting> {
31 match percent <= 100 {
32 true => Some(BuilderSetting { address, rule: FeeRule::ShareOfCap(percent) }),
33 false => None,
34 }
35 }
36
37 #[must_use]
39 pub const fn flat(address: Address, fee: BuilderFee) -> BuilderSetting {
40 BuilderSetting { address, rule: FeeRule::Flat(fee) }
41 }
42
43 #[must_use]
44 pub const fn address(&self) -> Address {
45 self.address
46 }
47
48 #[must_use]
49 pub const fn rule(&self) -> FeeRule {
50 self.rule
51 }
52
53 #[must_use]
55 pub const fn fee_on(&self, kind: AssetKind) -> BuilderFee {
56 match self.rule {
57 FeeRule::ShareOfCap(percent) => {
58 BuilderFee::from_tenths(kind.builder_cap().tenths().saturating_mul(percent) / 100)
59 }
60 FeeRule::Flat(fee) => fee,
61 }
62 }
63
64 pub fn on(&self, kind: AssetKind) -> Result<Builder, OverCap> {
66 let fee = self.fee_on(kind);
67 let cap = kind.builder_cap();
68 match fee > cap {
69 true => Err(OverCap { fee, cap, kind }),
70 false => Ok(Builder::new(self.address, fee)),
71 }
72 }
73
74 pub fn on_assets(&self, assets: impl IntoIterator<Item = Asset>) -> Result<Option<Builder>, OverCap> {
78 AssetKind::strictest(assets).map(|kind| self.on(kind)).transpose()
79 }
80
81 #[must_use]
84 pub fn most(&self) -> BuilderFee {
85 AssetKind::ALL
86 .into_iter()
87 .filter_map(|kind| self.on(kind).ok())
88 .map(|builder| builder.fee())
89 .max()
90 .unwrap_or_default()
91 }
92
93 pub fn approval(&self, order: &PlaceOrder, approved: &Approved, by: SignedBy) -> Result<Approval, ApprovalRefused> {
96 let Some(builder) = order.builder().filter(|builder| builder.fee() > BuilderFee::ZERO) else {
97 return Ok(Approval::Goes);
98 };
99 if builder.address() != self.address {
100 return Err(ApprovalRefused::OtherBuilder { named: builder.address(), set: self.address });
101 }
102 let fee = builder.fee();
103 let approved = match approved {
104 Approved::Upto(approved) if *approved >= fee => return Ok(Approval::Goes),
105 Approved::Upto(approved) => *approved,
106 Approved::Unread => return Err(ApprovalRefused::NotRead),
107 Approved::Failed(why) => return Err(ApprovalRefused::Unreadable(why.clone())),
108 };
109 let rate = self.most().max(fee);
110 match by {
111 SignedBy::Account => Ok(Approval::ApproveFirst { approved, rate }),
112 SignedBy::Agent => Err(ApprovalRefused::AgentCannotApprove { approved, rate }),
113 }
114 }
115}
116
117#[derive(Debug, Clone, PartialEq, Eq, Default)]
120pub enum Approved {
121 #[default]
123 Unread,
124 Upto(BuilderFee),
126 Failed(String),
128}
129
130impl Approved {
131 #[must_use]
133 pub fn words(&self) -> String {
134 match self {
135 Approved::Unread => "not read yet".to_owned(),
136 Approved::Upto(fee) if *fee == BuilderFee::ZERO => "nothing yet".to_owned(),
137 Approved::Upto(fee) => format!("up to {fee}"),
138 Approved::Failed(why) => format!("would not read: {why}"),
139 }
140 }
141}
142
143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
146pub enum SignedBy {
147 Account,
148 Agent,
149}
150
151#[derive(Debug, Clone, Copy, PartialEq, Eq)]
153pub enum Approval {
154 Goes,
156 ApproveFirst { approved: BuilderFee, rate: BuilderFee },
160}
161
162impl Approval {
163 #[must_use]
165 pub fn max_fee_rate(&self) -> Option<String> {
166 match self {
167 Approval::Goes => None,
168 Approval::ApproveFirst { rate, .. } => Some(rate.percent()),
169 }
170 }
171}
172
173#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
176pub enum ApprovalRefused {
177 #[error("the builder fee this account has approved is not read yet; try again in a moment")]
178 NotRead,
179 #[error("the builder fee this account has approved would not read, so it is not guessed at: {0}")]
180 Unreadable(String),
181 #[error(
182 "the account's main key has to approve the builder fee up to {rate} once (approved now: {approved}); an agent key cannot sign approveBuilderFee"
183 )]
184 AgentCannotApprove { approved: BuilderFee, rate: BuilderFee },
185 #[error("the order names the builder {named}, and what the account approved was read for {set}")]
186 OtherBuilder { named: Address, set: Address },
187}
188
189#[cfg(test)]
190#[path = "tests/builder.rs"]
191mod tests;