df_hyperliquid_core/trade/builder/
setting.rs1use crate::account::Key;
6use crate::trade::builder::fee::{Builder, BuilderFee, OverCap};
7use crate::trade::orders::PlaceOrder;
8use crate::utils::{Address, Asset, AssetKind};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum FeeRule {
13 ShareOfCap(u64),
16 Flat(BuilderFee),
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub struct BuilderSetting {
24 address: Address,
25 rule: FeeRule,
26}
27
28impl BuilderSetting {
29 #[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 #[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 #[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 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 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 #[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 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#[derive(Debug, Clone, PartialEq, Eq, Default)]
121pub enum Approved {
122 #[default]
124 Unread,
125 Upto(BuilderFee),
127 Failed(String),
129}
130
131impl Approved {
132 #[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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
146pub enum Approval {
147 Goes,
149 ApproveFirst { approved: BuilderFee, rate: BuilderFee },
153}
154
155impl Approval {
156 #[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#[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;