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,
15 pub base_mint: &'a Address,
16 pub quote_mint: &'a Address,
17 pub partner: &'a Address,
18 pub platform_config: &'a Address,
19 pub quote_token_program: &'a Address,
20 pub name: &'a str,
21 pub symbol: &'a str,
22 pub uri: &'a str,
23 pub creator_fee_mode: u8,
24 pub creator_fee_bps: u16,
25 pub dex_creator_fee_bps: u16,
27}
28
29#[must_use]
32pub fn build_create_token_instruction(
33 p: &BuildCreateTokenParams<'_>,
34) -> solana_instruction::Instruction {
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 (partner_config, _) = crate::nexus::pda::find_partner_config_pda(
48 p.platform_config,
49 p.partner,
50 );
51 let (staking_reward_state, _) = crate::nexus::pda::find_reward_state_pda(
52 &STAKING_CONFIG_ADDRESS,
53 p.quote_mint,
54 );
55 let (staking_vault, _) = crate::utils::find_associated_token_pda(
56 &STAKING_CONFIG_ADDRESS,
57 p.quote_mint,
58 p.quote_token_program,
59 );
60 let (reward_accrual, _) =
61 super::generated::pdas::find_reward_accrual_pda(p.quote_mint);
62 let (creator_fee_config, _) =
63 super::pda::find_creator_fee_config_pda(p.base_mint, p.quote_mint);
64
65 let create = super::instructions::CreateToken {
66 user: *p.user,
67 payer: *p.payer.unwrap_or(p.user),
68 coin_creator: *p.coin_creator,
69 global_config: super::pda::GLOBAL_CONFIG_ADDRESS,
70 base_mint: *p.base_mint,
71 quote_mint: *p.quote_mint,
72 bonding_curve,
73 base_vault,
74 quote_vault,
75 creator_fee_config,
76 nexus_program: NEXUS_PROGRAM_ID,
77 nexus_global_config: crate::nexus::pda::GLOBAL_CONFIG_ADDRESS,
78 partner: crate::utils::partner_account(p.partner),
79 partner_config,
80 nexus_staking_config: STAKING_CONFIG_ADDRESS,
81 staking_reward_state,
82 reward_accrual,
83 staking_vault,
84 staking_wsol_vault: STAKING_WSOL_VAULT_ADDRESS,
85 wsol_reward_accrual:
86 super::generated::pdas::WSOL_REWARD_ACCRUAL_ADDRESS,
87 pyth_price_feed: PYTH_SOL_USD_PRICE_ACCOUNT,
88 base_token_program: TOKEN_2022_PROGRAM_ID,
89 quote_token_program: *p.quote_token_program,
90 wsol_token_program: TOKEN_PROGRAM_ID,
91 associated_token_program: ATA_PROGRAM_ID,
92 system_program: SYSTEM_PROGRAM_ID,
93 event_authority: super::pda::EVENT_AUTHORITY_ADDRESS,
94 program: LAUNCHPAD_PROGRAM_ID,
95 };
96 create.instruction(super::instructions::CreateTokenInstructionArgs {
97 platform_config: *p.platform_config,
98 name: p.name.to_owned(),
99 symbol: p.symbol.to_owned(),
100 uri: p.uri.to_owned(),
101 creator_fee_mode: p.creator_fee_mode,
102 creator_fee_bps: p.creator_fee_bps,
103 dex_creator_fee_bps: p.dex_creator_fee_bps,
104 })
105}
106
107#[cfg(test)]
108mod tests {
109 use solana_address::Address;
110
111 use super::{BuildCreateTokenParams, build_create_token_instruction};
112 use crate::constants::{
113 DEFAULT_PARTNER, TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID, WSOL_MINT,
114 };
115
116 const COIN_CREATOR_INDEX: usize = 2;
117 const CREATOR_FEE_CONFIG_INDEX: usize = 9;
118 const PARTNER_INDEX: usize = 12;
119
120 const COIN_CREATOR: Address = Address::new_from_array([5u8; 32]);
122
123 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: &COIN_CREATOR,
139 base_mint: &base_mint,
140 quote_mint,
141 partner,
142 platform_config: &platform_config,
143 quote_token_program,
144 name: "Sample Token",
145 symbol: "SMPL",
146 uri: "https://example.com/meta.json",
147 creator_fee_mode: 0,
148 creator_fee_bps: 0,
149 dex_creator_fee_bps: 0,
150 })
151 }
152
153 fn wsol_instruction(partner: &Address) -> solana_instruction::Instruction {
154 sample_instruction(partner, &WSOL_MINT, &TOKEN_PROGRAM_ID)
155 }
156
157 #[test]
158 fn non_default_partner_is_marked_as_signer() {
159 let partner = Address::new_from_array([3u8; 32]);
160 let ix = wsol_instruction(&partner);
161
162 assert_eq!(ix.accounts[PARTNER_INDEX].pubkey, partner);
163 assert!(ix.accounts[PARTNER_INDEX].is_signer);
164
165 let matches =
166 ix.accounts.iter().filter(|m| m.pubkey == partner).count();
167 assert_eq!(matches, 1);
168 }
169
170 #[test]
171 fn default_partner_is_not_marked_as_signer() {
172 let ix = wsol_instruction(&DEFAULT_PARTNER);
173
174 assert_eq!(ix.accounts[PARTNER_INDEX].pubkey, DEFAULT_PARTNER);
175 assert!(!ix.accounts[PARTNER_INDEX].is_signer);
176 }
177
178 #[test]
180 fn no_account_is_derived_from_the_creator_and_the_quote_mint() {
181 let user = Address::new_from_array([1u8; 32]);
182 let ix = sample_instruction(
183 &DEFAULT_PARTNER,
184 &TSLAX_MINT,
185 &TOKEN_2022_PROGRAM_ID,
186 );
187
188 let (user_quote_ata, _) = crate::utils::find_associated_token_pda(
189 &user,
190 &TSLAX_MINT,
191 &TOKEN_2022_PROGRAM_ID,
192 );
193 assert!(!ix.accounts.iter().any(|m| m.pubkey == user_quote_ata));
194
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 #[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
212 #[test]
213 fn coin_creator_is_the_named_wallet_and_does_not_sign() {
214 let ix = wsol_instruction(&DEFAULT_PARTNER);
215
216 assert_eq!(ix.accounts[COIN_CREATOR_INDEX].pubkey, COIN_CREATOR);
217 assert!(!ix.accounts[COIN_CREATOR_INDEX].is_signer);
218 assert!(!ix.accounts[COIN_CREATOR_INDEX].is_writable);
219 }
220
221 #[test]
222 fn the_creator_fee_config_slot_is_the_curves_pda() {
223 let ix = sample_instruction(
224 &DEFAULT_PARTNER,
225 &TSLAX_MINT,
226 &TOKEN_2022_PROGRAM_ID,
227 );
228 let base_mint = Address::new_from_array([2u8; 32]);
229 let (derived, _) = Address::find_program_address(
230 &[
231 b"creator_fee_config",
232 base_mint.as_ref(),
233 TSLAX_MINT.as_ref(),
234 ],
235 &crate::constants::LAUNCHPAD_PROGRAM_ID,
236 );
237
238 let slot = &ix.accounts[CREATOR_FEE_CONFIG_INDEX];
239 assert_eq!(slot.pubkey, derived);
240 assert_eq!(
241 slot.pubkey,
242 solana_address::address!(
243 "7wnaFX6M9fKrZmkuQitz2eKYyYDAbfAEZWEc4VagHYz1"
244 ),
245 );
246 assert!(!slot.is_signer);
247 assert!(slot.is_writable);
248 }
249
250 #[test]
251 fn name_follows_platform_config_at_offset_40() {
252 let ix = wsol_instruction(&DEFAULT_PARTNER);
253
254 assert_eq!(ix.data[8..40], [4u8; 32]);
255 assert_eq!(ix.data[40..44], 12u32.to_le_bytes());
256 assert_eq!(&ix.data[44..56], b"Sample Token");
257 assert_eq!(ix.data[56..60], 4u32.to_le_bytes());
258 assert_eq!(&ix.data[60..64], b"SMPL");
259 assert_eq!(ix.data[64..68], 29u32.to_le_bytes());
260 assert_eq!(&ix.data[68..97], b"https://example.com/meta.json");
261 assert_eq!(ix.data[97..102], [0u8; 5]);
263 assert_eq!(ix.data.len(), 102);
264 }
265}