Skip to main content

sendfun_sdk/launchpad/
create.rs

1use crate::constants::{
2	ATA_PROGRAM_ID, LAUNCHPAD_PROGRAM_ID, NEXUS_PROGRAM_ID,
3	PYTH_SOL_USD_PRICE_ACCOUNT, STAKING_WSOL_VAULT_ADDRESS, SYSTEM_PROGRAM_ID,
4	TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID,
5};
6use crate::nexus::pda::STAKING_CONFIG_ADDRESS;
7use solana_address::Address;
8
9#[derive(Clone, Debug)]
10pub struct BuildCreateTokenParams<'a> {
11	pub user: &'a Address,
12	pub payer: Option<&'a Address>,
13	pub coin_creator: &'a Address,
14	pub base_mint: &'a Address,
15	pub quote_mint: &'a Address,
16	pub partner: &'a Address,
17	/// Stamped on the curve; seeds `partner_config`.
18	pub platform_config: &'a Address,
19	pub quote_token_program: &'a Address,
20	pub creator_platform: &'a str,
21	pub creator_id: &'a str,
22	pub name: &'a str,
23	pub symbol: &'a str,
24	pub uri: &'a str,
25}
26
27/// `base_mint` must be a new keypair; the caller signs the transaction with it.
28#[must_use]
29pub fn build_create_token_instruction(
30	p: &BuildCreateTokenParams<'_>,
31) -> solana_instruction::Instruction {
32	let creator_hash =
33		crate::utils::creator_hash_from_id(p.creator_platform, p.creator_id);
34	let (bonding_curve, _) =
35		super::pda::find_bonding_curve_pda(p.base_mint, p.quote_mint);
36	let (base_vault, _) = crate::utils::find_associated_token_pda(
37		&bonding_curve,
38		p.base_mint,
39		&TOKEN_2022_PROGRAM_ID,
40	);
41	let (quote_vault, _) = crate::utils::find_associated_token_pda(
42		&bonding_curve,
43		p.quote_mint,
44		p.quote_token_program,
45	);
46	let (creator_fee_config, _) =
47		crate::nexus::pda::find_creator_fee_config_pda(
48			&creator_hash,
49			p.quote_mint,
50		);
51	let (partner_config, _) = crate::nexus::pda::find_partner_config_pda(
52		p.platform_config,
53		p.partner,
54	);
55	let (staking_reward_state, _) = crate::nexus::pda::find_reward_state_pda(
56		&STAKING_CONFIG_ADDRESS,
57		p.quote_mint,
58	);
59	let (staking_vault, _) = crate::utils::find_associated_token_pda(
60		&STAKING_CONFIG_ADDRESS,
61		p.quote_mint,
62		p.quote_token_program,
63	);
64	// `create_token` inits it if missing; the fee accrues to the WSOL one.
65	let (reward_accrual, _) =
66		super::generated::pdas::find_reward_accrual_pda(p.quote_mint);
67
68	let create = super::instructions::CreateToken {
69		user: *p.user,
70		payer: *p.payer.unwrap_or(p.user),
71		coin_creator: *p.coin_creator,
72		global_config: super::pda::GLOBAL_CONFIG_ADDRESS,
73		base_mint: *p.base_mint,
74		quote_mint: *p.quote_mint,
75		bonding_curve,
76		base_vault,
77		quote_vault,
78		creator_fee_config,
79		nexus_program: NEXUS_PROGRAM_ID,
80		nexus_global_config: crate::nexus::pda::GLOBAL_CONFIG_ADDRESS,
81		partner: crate::utils::partner_account(p.partner),
82		partner_config,
83		nexus_staking_config: STAKING_CONFIG_ADDRESS,
84		staking_reward_state,
85		reward_accrual,
86		staking_vault,
87		staking_wsol_vault: STAKING_WSOL_VAULT_ADDRESS,
88		wsol_reward_accrual:
89			super::generated::pdas::WSOL_REWARD_ACCRUAL_ADDRESS,
90		pyth_price_feed: PYTH_SOL_USD_PRICE_ACCOUNT,
91		base_token_program: TOKEN_2022_PROGRAM_ID,
92		quote_token_program: *p.quote_token_program,
93		wsol_token_program: TOKEN_PROGRAM_ID,
94		associated_token_program: ATA_PROGRAM_ID,
95		system_program: SYSTEM_PROGRAM_ID,
96		event_authority: super::pda::EVENT_AUTHORITY_ADDRESS,
97		program: LAUNCHPAD_PROGRAM_ID,
98	};
99	create.instruction(super::instructions::CreateTokenInstructionArgs {
100		platform_config: *p.platform_config,
101		creator_platform: p.creator_platform.to_owned(),
102		creator_id: p.creator_id.to_owned(),
103		creator_hash,
104		name: p.name.to_owned(),
105		symbol: p.symbol.to_owned(),
106		uri: p.uri.to_owned(),
107	})
108}
109
110#[cfg(test)]
111mod tests {
112	use solana_address::Address;
113
114	use super::{BuildCreateTokenParams, build_create_token_instruction};
115	use crate::constants::{
116		DEFAULT_PARTNER, TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID, WSOL_MINT,
117	};
118
119	const PARTNER_INDEX: usize = 12;
120
121	/// A Token-2022 quote mint a fresh creator holds none of.
122	const TSLAX_MINT: Address =
123		solana_address::address!("XsDoVfqeBukxuZHWhdvWHBhgEHjGNst4MLodqsJHzoB");
124
125	fn sample_instruction(
126		partner: &Address,
127		quote_mint: &Address,
128		quote_token_program: &Address,
129	) -> solana_instruction::Instruction {
130		let user = Address::new_from_array([1u8; 32]);
131		let base_mint = Address::new_from_array([2u8; 32]);
132		let platform_config = Address::new_from_array([4u8; 32]);
133		build_create_token_instruction(&BuildCreateTokenParams {
134			user: &user,
135			payer: None,
136			coin_creator: &user,
137			base_mint: &base_mint,
138			quote_mint,
139			partner,
140			platform_config: &platform_config,
141			quote_token_program,
142			creator_platform: "wallet",
143			creator_id: "11111111111111111111111111111111",
144			name: "Sample Token",
145			symbol: "SMPL",
146			uri: "https://example.com/meta.json",
147		})
148	}
149
150	fn wsol_instruction(partner: &Address) -> solana_instruction::Instruction {
151		sample_instruction(partner, &WSOL_MINT, &TOKEN_PROGRAM_ID)
152	}
153
154	#[test]
155	fn non_default_partner_is_marked_as_signer() {
156		let partner = Address::new_from_array([3u8; 32]);
157		let ix = wsol_instruction(&partner);
158
159		assert_eq!(ix.accounts[PARTNER_INDEX].pubkey, partner);
160		assert!(ix.accounts[PARTNER_INDEX].is_signer);
161
162		let matches =
163			ix.accounts.iter().filter(|m| m.pubkey == partner).count();
164		assert_eq!(matches, 1);
165	}
166
167	#[test]
168	fn default_partner_is_not_marked_as_signer() {
169		let ix = wsol_instruction(&DEFAULT_PARTNER);
170
171		assert_eq!(ix.accounts[PARTNER_INDEX].pubkey, DEFAULT_PARTNER);
172		assert!(!ix.accounts[PARTNER_INDEX].is_signer);
173	}
174
175	/// The fee is native SOL, so launching needs no holding of the quote asset.
176	#[test]
177	fn no_account_is_derived_from_the_creator_and_the_quote_mint() {
178		let user = Address::new_from_array([1u8; 32]);
179		let ix = sample_instruction(
180			&DEFAULT_PARTNER,
181			&TSLAX_MINT,
182			&TOKEN_2022_PROGRAM_ID,
183		);
184
185		let (user_quote_ata, _) = crate::utils::find_associated_token_pda(
186			&user,
187			&TSLAX_MINT,
188			&TOKEN_2022_PROGRAM_ID,
189		);
190		assert!(!ix.accounts.iter().any(|m| m.pubkey == user_quote_ata));
191
192		// The fee still lands in the WSOL vault.
193		let (staking_wsol_vault, _) = crate::utils::find_associated_token_pda(
194			&crate::nexus::pda::STAKING_CONFIG_ADDRESS,
195			&WSOL_MINT,
196			&TOKEN_PROGRAM_ID,
197		);
198		assert!(ix.accounts.iter().any(|m| m.pubkey == staking_wsol_vault));
199	}
200
201	/// `user` funds the fee even though `payer` covers rent.
202	#[test]
203	fn the_fee_paying_user_slot_is_writable() {
204		let ix = wsol_instruction(&DEFAULT_PARTNER);
205
206		assert!(ix.accounts[0].is_writable);
207		assert!(ix.accounts[0].is_signer);
208	}
209}