Skip to main content

df_hyperliquid_core/trade/builder/
fee.rs

1//! A builder's fee in tenths of a basis point, as an order's `f` and `maxBuilderFee` count it (10
2//! is 1 basis point). `approveBuilderFee` names the same figure as a percent.
3
4use crate::utils::dec::{Dec, Round};
5use crate::utils::sign::Map;
6use crate::utils::{Address, AssetKind};
7use serde::Deserialize;
8use std::fmt;
9
10/// Tenths of a basis point: 10 is 0.01%, 1000 is 1%.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Deserialize)]
12#[serde(transparent)]
13pub struct BuilderFee(u64);
14
15/// The most a builder may take on a perp order, main dex or HIP-3: 0.1%.
16pub const PERP_BUILDER_CAP: BuilderFee = BuilderFee(100);
17
18/// The most a builder may take on a spot order: 1%.
19pub const SPOT_BUILDER_CAP: BuilderFee = BuilderFee(1_000);
20
21/// HIP-4 takes builder codes as spot does.
22pub const OUTCOME_BUILDER_CAP: BuilderFee = SPOT_BUILDER_CAP;
23
24impl BuilderFee {
25    pub const ZERO: BuilderFee = BuilderFee(0);
26
27    #[must_use]
28    pub const fn from_tenths(tenths: u64) -> BuilderFee {
29        BuilderFee(tenths)
30    }
31
32    #[must_use]
33    pub const fn tenths(self) -> u64 {
34        self.0
35    }
36
37    /// The percent string `approveBuilderFee` takes as `maxFeeRate`: 880 is `0.88%`, 100 is `0.1%`,
38    /// 1000 is `1%`. It always matches `^[0-9]+(\.[0-9]+)?%$`.
39    #[must_use]
40    pub fn percent(self) -> String {
41        let figure = Dec::lit::<3>(i128::from(self.0)).to_wire();
42        format!("{figure}%")
43    }
44
45    /// A percent string back in tenths. `None` for anything that is not a
46    /// whole number of tenths of a basis point.
47    #[must_use]
48    pub fn from_percent(text: &str) -> Option<BuilderFee> {
49        let digits = text.trim().strip_suffix('%')?;
50        if !digits.bytes().all(|byte| byte.is_ascii_digit() || byte == b'.') {
51            return None;
52        }
53        let figure = Dec::parse(digits).ok()?;
54        if figure.places() > 3 {
55            return None;
56        }
57        u64::try_from(figure.units_at(3, Round::Down)?).ok().map(BuilderFee)
58    }
59}
60
61impl fmt::Display for BuilderFee {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        f.write_str(&self.percent())
64    }
65}
66
67/// Who routed the order and what it takes: `{b, f}`, the address lowercase,
68/// the fee in tenths of a basis point.
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub struct Builder {
71    address: Address,
72    fee: BuilderFee,
73}
74
75impl Builder {
76    /// The account must have approved at least `fee` for this builder
77    /// (`approveBuilderFee`), or the venue refuses the order.
78    #[must_use]
79    pub const fn new(address: Address, fee: BuilderFee) -> Builder {
80        Builder { address, fee }
81    }
82
83    #[must_use]
84    pub const fn address(&self) -> Address {
85        self.address
86    }
87
88    #[must_use]
89    pub const fn fee(&self) -> BuilderFee {
90        self.fee
91    }
92
93    #[must_use]
94    pub fn wire(&self) -> Map {
95        Map::new().put("b", self.address.to_hex()).put("f", self.fee.tenths())
96    }
97}
98
99/// A builder fee over what the venue lets a builder take on these orders.
100#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
101#[error("the builder fee of {} is over the {} {} order allows ({})", .fee.tenths(), .cap.tenths(), .kind.one(), .cap)]
102pub struct OverCap {
103    pub fee: BuilderFee,
104    pub cap: BuilderFee,
105    pub kind: AssetKind,
106}
107
108#[cfg(test)]
109#[path = "tests/fee.rs"]
110mod tests;