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