1use hypercall_types::{WalletAddress, CONTRACT_UNIT_MULTIPLIER};
10use rust_decimal::Decimal;
11
12#[derive(Debug, Clone)]
14pub struct FeeConfig {
15 pub maker_fee_bps: f64,
17 pub taker_fee_bps: f64,
19 pub builder_code_share_pct: f64,
21}
22
23impl Default for FeeConfig {
24 fn default() -> Self {
25 Self {
26 maker_fee_bps: 0.0,
27 taker_fee_bps: 0.0,
28 builder_code_share_pct: 0.0,
29 }
30 }
31}
32
33#[derive(Debug, Clone)]
35pub struct FeeCalculation {
36 pub maker_fee: f64,
38 pub taker_fee: f64,
40 pub builder_code_fee: Option<Decimal>,
42}
43
44#[derive(Debug, Clone)]
46pub struct FeeService {
47 config: FeeConfig,
48}
49
50impl FeeService {
51 pub fn new(config: FeeConfig) -> Self {
53 Self { config }
54 }
55
56 pub fn get_fees(
68 &self,
69 _maker_account: &str,
70 _taker_account: &str,
71 price: f64,
72 size: u64,
73 builder_code_address: Option<&WalletAddress>,
74 ) -> FeeCalculation {
75 let size_human = size as f64 / CONTRACT_UNIT_MULTIPLIER;
77
78 let notional = price * size_human;
80
81 let maker_fee = notional * self.config.maker_fee_bps / 10_000.0;
83 let taker_fee = notional * self.config.taker_fee_bps / 10_000.0;
84
85 let builder_code_fee = if builder_code_address.is_some()
88 && taker_fee > 0.0
89 && self.config.builder_code_share_pct > 0.0
90 {
91 Decimal::from_f64_retain(taker_fee * self.config.builder_code_share_pct)
92 } else {
93 None
94 };
95
96 FeeCalculation {
97 maker_fee,
98 taker_fee,
99 builder_code_fee,
100 }
101 }
102
103 pub fn config(&self) -> &FeeConfig {
105 &self.config
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112 use std::str::FromStr;
113
114 #[test]
115 fn test_fee_calculation_basic() {
116 let service = FeeService::new(FeeConfig {
117 maker_fee_bps: 0.5,
118 taker_fee_bps: 2.0,
119 builder_code_share_pct: 0.10,
120 });
121
122 let result = service.get_fees("maker", "taker", 100.0, 1_000_000, None);
127
128 assert!((result.maker_fee - 0.005).abs() < 1e-10);
129 assert!((result.taker_fee - 0.02).abs() < 1e-10);
130 assert!(result.builder_code_fee.is_none());
131 }
132
133 #[test]
134 fn test_fee_calculation_with_builder_code() {
135 let service = FeeService::new(FeeConfig {
136 maker_fee_bps: 0.5,
137 taker_fee_bps: 2.0,
138 builder_code_share_pct: 0.10,
139 });
140
141 let builder_code =
145 WalletAddress::from_str("0x1234567890123456789012345678901234567890").unwrap();
146 let result = service.get_fees("maker", "taker", 100.0, 1_000_000, Some(&builder_code));
147
148 assert!((result.maker_fee - 0.005).abs() < 1e-10);
149 assert!((result.taker_fee - 0.02).abs() < 1e-10);
150 assert!(result.builder_code_fee.is_some());
151 assert_eq!(
152 result.builder_code_fee.unwrap(),
153 Decimal::from_f64_retain(0.002).unwrap()
154 );
155 }
156
157 #[test]
158 fn test_fee_calculation_zero_size() {
159 let service = FeeService::new(FeeConfig {
160 maker_fee_bps: 0.5,
161 taker_fee_bps: 2.0,
162 builder_code_share_pct: 0.10,
163 });
164
165 let result = service.get_fees("maker", "taker", 100.0, 0, None);
166
167 assert_eq!(result.maker_fee, 0.0);
168 assert_eq!(result.taker_fee, 0.0);
169 assert!(result.builder_code_fee.is_none());
170 }
171
172 #[test]
173 fn test_fee_calculation_large_notional() {
174 let service = FeeService::new(FeeConfig {
175 maker_fee_bps: 0.5,
176 taker_fee_bps: 2.0,
177 builder_code_share_pct: 0.10,
178 });
179
180 let result = service.get_fees("maker", "taker", 50000.0, 10_000_000, None);
185
186 assert!((result.maker_fee - 25.0).abs() < 1e-10);
187 assert!((result.taker_fee - 100.0).abs() < 1e-10);
188 }
189
190 #[test]
191 fn test_default_config() {
192 let config = FeeConfig::default();
193
194 assert_eq!(config.maker_fee_bps, 0.0);
195 assert_eq!(config.taker_fee_bps, 0.0);
196 assert_eq!(config.builder_code_share_pct, 0.0);
197 }
198
199 #[test]
200 fn test_default_config_disables_builder_code_fee() {
201 let service = FeeService::new(FeeConfig::default());
202 let builder_code =
203 WalletAddress::from_str("0x1234567890123456789012345678901234567890").unwrap();
204
205 let result = service.get_fees("maker", "taker", 100.0, 1_000_000, Some(&builder_code));
206
207 assert_eq!(result.maker_fee, 0.0);
208 assert_eq!(result.taker_fee, 0.0);
209 assert!(result.builder_code_fee.is_none());
210 }
211}