Skip to main content

sendfun_sdk/
lib.rs

1#![doc = include_str!("../README.md")]
2
3pub mod dex;
4pub mod launchpad;
5pub mod nexus;
6
7pub mod constants;
8pub mod math;
9pub mod transfer_fee;
10
11pub mod utils;
12
13#[cfg(test)]
14mod tests {
15
16	mod pda_tests {
17		use solana_address::address;
18
19		#[test]
20		fn launchpad_bonding_curve_pda() {
21			let base = address!("So11111111111111111111111111111111111111112");
22			let quote = address!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
23			let (addr1, _) =
24				crate::launchpad::pda::find_bonding_curve_pda(&base, &quote);
25			let (addr2, _) =
26				crate::launchpad::pda::find_bonding_curve_pda(&base, &quote);
27			assert_eq!(addr1, addr2);
28			let (addr3, _) =
29				crate::launchpad::pda::find_bonding_curve_pda(&quote, &base);
30			assert_ne!(addr1, addr3);
31		}
32
33		#[test]
34		fn dex_pool_and_lp_mint_pdas_differ() {
35			let base = address!("So11111111111111111111111111111111111111112");
36			let quote = address!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
37			let (pool, _) = crate::dex::pda::find_pool_pda(&base, &quote);
38			let (lp, _) = crate::dex::pda::find_lp_mint_pda(&base, &quote);
39			assert_ne!(pool, lp);
40		}
41
42		#[test]
43		fn nexus_staking_pdas() {
44			let user = address!("11111111111111111111111111111112");
45			let staking_mint =
46				address!("So11111111111111111111111111111111111111112");
47			let reward_mint =
48				address!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
49
50			let (pos, _) = crate::nexus::pda::find_user_stake_position_pda(
51				&user,
52				&staking_mint,
53			);
54			let (debt, _) = crate::nexus::pda::find_user_reward_debt_pda(
55				&user,
56				&staking_mint,
57				&reward_mint,
58			);
59			assert_ne!(pos, debt);
60		}
61
62		#[test]
63		fn nexus_creator_fee_config_pda() {
64			let quote = address!("So11111111111111111111111111111111111111112");
65			let (addr1, _) =
66				crate::nexus::pda::find_creator_fee_config_pda_from_id(
67					"wallet",
68					"SomeCreator",
69					&quote,
70				);
71			let (addr2, _) =
72				crate::nexus::pda::find_creator_fee_config_pda_from_id(
73					"twitter",
74					"SomeCreator",
75					&quote,
76				);
77			assert_ne!(addr1, addr2);
78		}
79
80		#[test]
81		fn event_authority_pdas_differ_per_program() {
82			let (lp, _) = crate::launchpad::pda::find_event_authority_pda();
83			let (dex, _) = crate::dex::pda::find_event_authority_pda();
84			let (nexus, _) = crate::nexus::pda::find_event_authority_pda();
85			assert_ne!(lp, dex);
86			assert_ne!(dex, nexus);
87			assert_ne!(lp, nexus);
88		}
89	}
90
91	mod generated_constants {
92		use solana_address::Address;
93
94		struct PdaCheck<'a> {
95			label: &'a str,
96			address: Address,
97			bump: u8,
98			seeds: &'a [&'a [u8]],
99			program_id: Address,
100		}
101
102		#[track_caller]
103		fn assert_pda(check: PdaCheck<'_>) {
104			let (derived, derived_bump) =
105				Address::find_program_address(check.seeds, &check.program_id);
106			assert_eq!(check.address, derived, "{}: address", check.label);
107			assert_eq!(check.bump, derived_bump, "{}: bump", check.label);
108		}
109
110		#[test]
111		fn launchpad_pda_constants_match_derivation() {
112			use crate::launchpad::generated::SEND_LAUNCHPAD_ID as ID;
113			use crate::launchpad::pda::{
114				EVENT_AUTHORITY_ADDRESS, EVENT_AUTHORITY_BUMP,
115				EVENT_AUTHORITY_SEED, GLOBAL_CONFIG_ADDRESS,
116				GLOBAL_CONFIG_BUMP, GLOBAL_CONFIG_SEED,
117				MIGRATION_AUTHORITY_ADDRESS, MIGRATION_AUTHORITY_BUMP,
118				MIGRATION_AUTHORITY_SEED,
119			};
120
121			assert_pda(PdaCheck {
122				label: "launchpad global_config",
123				address: GLOBAL_CONFIG_ADDRESS,
124				bump: GLOBAL_CONFIG_BUMP,
125				seeds: &[GLOBAL_CONFIG_SEED],
126				program_id: ID,
127			});
128			assert_pda(PdaCheck {
129				label: "launchpad event_authority",
130				address: EVENT_AUTHORITY_ADDRESS,
131				bump: EVENT_AUTHORITY_BUMP,
132				seeds: &[EVENT_AUTHORITY_SEED],
133				program_id: ID,
134			});
135			assert_pda(PdaCheck {
136				label: "launchpad migration_authority",
137				address: MIGRATION_AUTHORITY_ADDRESS,
138				bump: MIGRATION_AUTHORITY_BUMP,
139				seeds: &[MIGRATION_AUTHORITY_SEED],
140				program_id: ID,
141			});
142		}
143
144		#[test]
145		fn dex_pda_constants_match_derivation() {
146			use crate::dex::generated::SEND_DEX_ID as ID;
147			use crate::dex::pda::{
148				EVENT_AUTHORITY_ADDRESS, EVENT_AUTHORITY_BUMP,
149				EVENT_AUTHORITY_SEED, GLOBAL_CONFIG_ADDRESS,
150				GLOBAL_CONFIG_BUMP, GLOBAL_CONFIG_SEED,
151			};
152
153			assert_pda(PdaCheck {
154				label: "dex global_config",
155				address: GLOBAL_CONFIG_ADDRESS,
156				bump: GLOBAL_CONFIG_BUMP,
157				seeds: &[GLOBAL_CONFIG_SEED],
158				program_id: ID,
159			});
160			assert_pda(PdaCheck {
161				label: "dex event_authority",
162				address: EVENT_AUTHORITY_ADDRESS,
163				bump: EVENT_AUTHORITY_BUMP,
164				seeds: &[EVENT_AUTHORITY_SEED],
165				program_id: ID,
166			});
167		}
168
169		#[test]
170		fn nexus_pda_constants_match_derivation() {
171			use crate::nexus::generated::SEND_NEXUS_ID as ID;
172			use crate::nexus::pda::{
173				ALT_REGISTRY_ADDRESS, ALT_REGISTRY_BUMP, ALT_REGISTRY_SEED,
174				EVENT_AUTHORITY_ADDRESS, EVENT_AUTHORITY_BUMP,
175				EVENT_AUTHORITY_SEED, GLOBAL_CONFIG_ADDRESS,
176				GLOBAL_CONFIG_BUMP, GLOBAL_CONFIG_SEED, STAKING_CONFIG_ADDRESS,
177				STAKING_CONFIG_BUMP, STAKING_CONFIG_SEED,
178			};
179
180			assert_pda(PdaCheck {
181				label: "nexus global_config",
182				address: GLOBAL_CONFIG_ADDRESS,
183				bump: GLOBAL_CONFIG_BUMP,
184				seeds: &[GLOBAL_CONFIG_SEED],
185				program_id: ID,
186			});
187			assert_pda(PdaCheck {
188				label: "nexus staking_config",
189				address: STAKING_CONFIG_ADDRESS,
190				bump: STAKING_CONFIG_BUMP,
191				seeds: &[STAKING_CONFIG_SEED],
192				program_id: ID,
193			});
194			assert_pda(PdaCheck {
195				label: "nexus event_authority",
196				address: EVENT_AUTHORITY_ADDRESS,
197				bump: EVENT_AUTHORITY_BUMP,
198				seeds: &[EVENT_AUTHORITY_SEED],
199				program_id: ID,
200			});
201			assert_pda(PdaCheck {
202				label: "nexus alt_registry",
203				address: ALT_REGISTRY_ADDRESS,
204				bump: ALT_REGISTRY_BUMP,
205				seeds: &[ALT_REGISTRY_SEED],
206				program_id: ID,
207			});
208			// Platform-seeded finders can't be folded constants; pin them
209			// against the general finders.
210			let platform =
211				crate::constants::find_platform_address(b"sendfun").0;
212			assert_eq!(
213				crate::nexus::pda::find_default_partner_pda(&platform),
214				crate::nexus::pda::find_partner_config_pda(
215					&platform,
216					&crate::constants::DEFAULT_PARTNER,
217				),
218			);
219			assert_ne!(
220				crate::nexus::pda::find_default_partner_pda(&platform).0,
221				crate::nexus::pda::find_default_partner_pda(
222					&crate::constants::find_platform_address(b"acme").0
223				)
224				.0,
225			);
226			assert_eq!(
227				crate::nexus::pda::find_default_fee_preset_pda(&platform),
228				crate::nexus::pda::find_fee_preset_pda(&platform, 0),
229			);
230			// Two platforms must not share a tier table, or index 0 on one
231			// would price launches on the other.
232			assert_ne!(
233				crate::nexus::pda::find_default_fee_preset_pda(&platform).0,
234				crate::nexus::pda::find_default_fee_preset_pda(
235					&crate::constants::find_platform_address(b"acme").0
236				)
237				.0,
238			);
239			assert_eq!(
240				crate::nexus::pda::find_default_partner_metadata_pda(&platform),
241				crate::nexus::pda::find_partner_metadata_pda(
242					&platform,
243					&crate::constants::DEFAULT_PARTNER,
244				),
245			);
246			// If these collided a rename on one platform would rename both.
247			assert_ne!(
248				crate::nexus::pda::find_default_partner_metadata_pda(&platform)
249					.0,
250				crate::nexus::pda::find_default_partner_metadata_pda(
251					&crate::constants::find_platform_address(b"acme").0
252				)
253				.0,
254			);
255		}
256
257		/// Pinned to `app/docs/developers/addresses.mdx`. The checks above compare
258		/// finders to finders, so only these catch a seed-order mistake.
259		#[test]
260		fn sendfun_default_partner_addresses_are_pinned() {
261			use crate::constants::{DEFAULT_PARTNER, find_platform_address};
262			use solana_address::address;
263
264			let platform = find_platform_address(b"sendfun").0;
265			assert_eq!(
266				platform,
267				address!("2PJedAsa7pCnScks2XM3U4o2nPaTvvBmi2553VcCnGWB"),
268				"sendfun platform"
269			);
270
271			assert_eq!(
272				crate::nexus::pda::find_partner_config_pda(
273					&platform,
274					&DEFAULT_PARTNER
275				)
276				.0,
277				address!("B77P3Hvj9xLi4tcFtyjNcf6NjhkQrfJvLkz3nVZnBzec"),
278				"default partner config"
279			);
280			assert_eq!(
281				crate::nexus::pda::find_fee_preset_pda(&platform, 0).0,
282				address!("DTWXzPHfg7vjnhwhE7rxhdPZPGGFjuqD1excog3xmKTx"),
283				"default fee preset 0"
284			);
285			assert_eq!(
286				crate::nexus::pda::find_partner_metadata_pda(
287					&platform,
288					&DEFAULT_PARTNER
289				)
290				.0,
291				address!("8WqZvqH4B1LtcfxZHeAdYWCYvwfMaUkQvJqZsw4fmii5"),
292				"default partner metadata"
293			);
294		}
295	}
296
297	#[test]
298	fn nexus_find_creator_fee_config_pda_from_id_matches_hash_derivation() {
299		use solana_address::address;
300		let quote = address!("So11111111111111111111111111111111111111112");
301		let creator_hash =
302			crate::utils::creator_hash_from_id("wallet", "SomeCreator");
303		let (addr1, bump1) =
304			crate::nexus::pda::find_creator_fee_config_pda_from_id(
305				"wallet",
306				"SomeCreator",
307				&quote,
308			);
309		let (addr2, bump2) = crate::nexus::pda::find_creator_fee_config_pda(
310			&creator_hash,
311			&quote,
312		);
313		assert_eq!(addr1, addr2);
314		assert_eq!(bump1, bump2);
315	}
316
317	#[test]
318	fn bonding_curve_status_values() {
319		assert_eq!(
320			crate::launchpad::types::BondingCurveStatus::Funding as u8,
321			0
322		);
323		assert_eq!(
324			crate::launchpad::types::BondingCurveStatus::Completed as u8,
325			1
326		);
327		assert_eq!(
328			crate::launchpad::types::BondingCurveStatus::Migrated as u8,
329			2
330		);
331		assert_eq!(
332			crate::launchpad::types::BondingCurveStatus::Paused as u8,
333			3
334		);
335	}
336
337	#[test]
338	fn pool_status_values() {
339		assert_eq!(crate::dex::types::PoolStatus::Active as u8, 0);
340		assert_eq!(crate::dex::types::PoolStatus::Paused as u8, 1);
341	}
342
343	#[test]
344	fn launchpad_fees_total() {
345		let fees = crate::nexus::types::LaunchpadFees {
346			creation_fee_cents: 0,
347			protocol_fee_bps: 100,
348			creator_fee_bps: 50,
349			fee_decay_seconds: 0,
350			fee_decay_start_bps: 0,
351		};
352		assert_eq!(fees.total_fee_bps(), Some(150));
353	}
354
355	#[test]
356	fn dex_fees_total() {
357		let fees = crate::nexus::types::DexFees {
358			creation_fee_cents: 0,
359			protocol_fee_bps: 80,
360			lp_fee_bps: 30,
361			creator_fee_bps: 40,
362			fee_decay_seconds: 0,
363			fee_decay_start_bps: 0,
364		};
365		assert_eq!(fees.total_fee_bps(), Some(150));
366	}
367}