1pub(crate) mod generated;
2
3pub use crate::constants::NEXUS_PROGRAM_ID as ID;
4pub use generated::{accounts, errors, events, instructions, types};
5
6pub mod pda;
7
8use crate::math::fee_decay::{FeeDecayArgs, calculate_fee_decay_premium};
9
10impl types::LaunchpadFees {
11 #[must_use]
12 pub const fn total_fee_bps(&self) -> Option<u16> {
13 self.protocol_fee_bps.checked_add(self.creator_fee_bps)
14 }
15
16 #[must_use]
20 pub fn effective_fee_bps(&self, created_at: i64, now: i64) -> Option<u16> {
21 let standard_fee_bps = self.total_fee_bps()?;
22 add_decay_premium(
23 standard_fee_bps,
24 FeeDecayArgs {
25 current_timestamp: u64::try_from(now).ok()?,
26 created_at_timestamp: u64::try_from(created_at).ok()?,
27 decay_seconds: self.fee_decay_seconds,
28 decay_start_bps: self.fee_decay_start_bps,
29 standard_fee_bps: u64::from(standard_fee_bps),
30 },
31 )
32 }
33}
34
35impl types::DexFees {
36 #[must_use]
37 pub const fn total_fee_bps(&self) -> Option<u16> {
38 match self.protocol_fee_bps.checked_add(self.lp_fee_bps) {
39 Some(v) => v.checked_add(self.creator_fee_bps),
40 None => None,
41 }
42 }
43
44 #[must_use]
48 pub fn effective_fee_bps(&self, created_at: i64, now: i64) -> Option<u16> {
49 let standard_fee_bps = self.total_fee_bps()?;
50 add_decay_premium(
51 standard_fee_bps,
52 FeeDecayArgs {
53 current_timestamp: u64::try_from(now).ok()?,
54 created_at_timestamp: u64::try_from(created_at).ok()?,
55 decay_seconds: self.fee_decay_seconds,
56 decay_start_bps: self.fee_decay_start_bps,
57 standard_fee_bps: u64::from(standard_fee_bps),
58 },
59 )
60 }
61}
62
63fn add_decay_premium(standard_fee_bps: u16, args: FeeDecayArgs) -> Option<u16> {
64 let premium = calculate_fee_decay_premium(args)?;
65 standard_fee_bps.checked_add(u16::try_from(premium).ok()?)
66}
67
68#[cfg(test)]
69mod tests {
70 use super::types::{DexFees, LaunchpadFees};
71
72 const LAUNCHPAD: LaunchpadFees = LaunchpadFees {
73 creation_fee_cents: 0,
74 protocol_fee_bps: 100,
75 creator_fee_bps: 50,
76 fee_decay_seconds: 12,
77 fee_decay_start_bps: 5_000,
78 };
79
80 #[test]
81 fn charges_the_decay_start_rate_at_creation() {
82 assert_eq!(LAUNCHPAD.effective_fee_bps(100, 100), Some(5_000));
83 }
84
85 #[test]
86 fn decays_quadratically_rounding_the_premium_up() {
87 assert_eq!(LAUNCHPAD.effective_fee_bps(100, 106), Some(1_363));
89 }
90
91 #[test]
92 fn falls_to_the_standard_rate_when_the_window_closes() {
93 assert_eq!(LAUNCHPAD.effective_fee_bps(100, 112), Some(150));
94 }
95
96 #[test]
97 fn charges_the_full_premium_on_a_creation_time_in_the_future() {
98 assert_eq!(LAUNCHPAD.effective_fee_bps(200, 100), Some(5_000));
99 }
100
101 #[test]
102 fn rejects_a_negative_timestamp() {
103 assert_eq!(LAUNCHPAD.effective_fee_bps(-1, 100), None);
104 }
105
106 #[test]
107 fn sums_protocol_lp_and_creator_on_the_dex() {
108 let fees = DexFees {
109 creation_fee_cents: 0,
110 protocol_fee_bps: 80,
111 lp_fee_bps: 30,
112 creator_fee_bps: 40,
113 fee_decay_seconds: 0,
114 fee_decay_start_bps: 0,
115 };
116 assert_eq!(fees.effective_fee_bps(100, 100), Some(150));
117 }
118}