Skip to main content

sendfun_sdk/
nexus.rs

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	/// `total_fee_bps` plus the fee decay premium at `now`. `created_at` is the
17	/// market's creation time. Both times are Unix seconds. `None` on overflow
18	/// or on a negative time.
19	#[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	/// `total_fee_bps` plus the fee decay premium at `now`. `created_at` is the
45	/// market's creation time. Both times are Unix seconds. `None` on overflow
46	/// or on a negative time.
47	#[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		// 150 standard + ceil(6^2 * 4_850 / 12^2) = 150 + 1_213.
88		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}