1use borsh::BorshSerialize;
2use light_token_interface::instructions::{
3 create_associated_token_account::CreateAssociatedTokenAccountInstructionData,
4 extensions::CompressibleExtensionInstructionData,
5};
6use solana_account_info::AccountInfo;
7use solana_cpi::{invoke, invoke_signed};
8use solana_instruction::{AccountMeta, Instruction};
9use solana_program_error::ProgramError;
10use solana_pubkey::Pubkey;
11
12use crate::instruction::{compressible::CompressibleParamsCpi, CompressibleParams};
13
14const CREATE_ATA_DISCRIMINATOR: u8 = 100;
15const CREATE_ATA_IDEMPOTENT_DISCRIMINATOR: u8 = 102;
16
17pub fn derive_associated_token_account(owner: &Pubkey, mint: &Pubkey) -> (Pubkey, u8) {
18 Pubkey::find_program_address(
19 &[
20 owner.as_ref(),
21 light_token_interface::LIGHT_TOKEN_PROGRAM_ID.as_ref(),
22 mint.as_ref(),
23 ],
24 &Pubkey::from(light_token_interface::LIGHT_TOKEN_PROGRAM_ID),
25 )
26}
27
28#[derive(Debug, Clone)]
41pub struct CreateAssociatedTokenAccount {
42 pub payer: Pubkey,
43 pub owner: Pubkey,
44 pub mint: Pubkey,
45 pub associated_token_account: Pubkey,
46 pub bump: u8,
47 pub compressible: CompressibleParams,
48 pub idempotent: bool,
49}
50
51impl CreateAssociatedTokenAccount {
52 pub fn new(payer: Pubkey, owner: Pubkey, mint: Pubkey) -> Self {
53 let (ata, bump) = derive_associated_token_account(&owner, &mint);
54 Self {
55 payer,
56 owner,
57 mint,
58 associated_token_account: ata,
59 bump,
60 compressible: CompressibleParams::default_ata(),
61 idempotent: false,
62 }
63 }
64
65 pub fn new_with_bump(
66 payer: Pubkey,
67 owner: Pubkey,
68 mint: Pubkey,
69 associated_token_account: Pubkey,
70 bump: u8,
71 ) -> Self {
72 Self {
73 payer,
74 owner,
75 mint,
76 associated_token_account,
77 bump,
78 compressible: CompressibleParams::default_ata(),
79 idempotent: false,
80 }
81 }
82
83 pub fn with_compressible(mut self, compressible_params: CompressibleParams) -> Self {
84 self.compressible = compressible_params;
85 self
86 }
87
88 pub fn idempotent(mut self) -> Self {
89 self.idempotent = true;
90 self
91 }
92
93 pub fn instruction(self) -> Result<Instruction, ProgramError> {
94 let instruction_data = CreateAssociatedTokenAccountInstructionData {
95 bump: self.bump,
96 compressible_config: Some(CompressibleExtensionInstructionData {
97 token_account_version: self.compressible.token_account_version as u8,
98 rent_payment: self.compressible.pre_pay_num_epochs,
99 compression_only: self.compressible.compression_only as u8,
100 write_top_up: self.compressible.lamports_per_write.unwrap_or(0),
101 compress_to_account_pubkey: self.compressible.compress_to_account_pubkey.clone(),
102 }),
103 };
104
105 let discriminator = if self.idempotent {
106 CREATE_ATA_IDEMPOTENT_DISCRIMINATOR
107 } else {
108 CREATE_ATA_DISCRIMINATOR
109 };
110
111 let mut data = Vec::new();
112 data.push(discriminator);
113 instruction_data
114 .serialize(&mut data)
115 .map_err(|e| ProgramError::BorshIoError(e.to_string()))?;
116
117 let accounts = vec![
118 AccountMeta::new_readonly(self.owner, false),
119 AccountMeta::new_readonly(self.mint, false),
120 AccountMeta::new(self.payer, true),
121 AccountMeta::new(self.associated_token_account, false),
122 AccountMeta::new_readonly(Pubkey::new_from_array([0; 32]), false), AccountMeta::new_readonly(self.compressible.compressible_config, false),
124 AccountMeta::new(self.compressible.rent_sponsor, false),
125 ];
126
127 Ok(Instruction {
128 program_id: Pubkey::from(light_token_interface::LIGHT_TOKEN_PROGRAM_ID),
129 accounts,
130 data,
131 })
132 }
133}
134
135pub struct CreateTokenAtaCpi<'info> {
155 pub payer: AccountInfo<'info>,
156 pub owner: AccountInfo<'info>,
157 pub mint: AccountInfo<'info>,
158 pub ata: AccountInfo<'info>,
159 pub bump: u8,
160}
161
162impl<'info> CreateTokenAtaCpi<'info> {
163 pub fn idempotent(self) -> CreateTokenAtaCpiIdempotent<'info> {
165 CreateTokenAtaCpiIdempotent { base: self }
166 }
167
168 pub fn rent_free(
170 self,
171 config: AccountInfo<'info>,
172 sponsor: AccountInfo<'info>,
173 system_program: AccountInfo<'info>,
174 ) -> CreateTokenAtaRentFreeCpi<'info> {
175 CreateTokenAtaRentFreeCpi {
176 payer: self.payer,
177 owner: self.owner,
178 mint: self.mint,
179 ata: self.ata,
180 bump: self.bump,
181 idempotent: false,
182 config,
183 sponsor,
184 system_program,
185 }
186 }
187
188 pub fn invoke_with(
190 self,
191 compressible: CompressibleParamsCpi<'info>,
192 system_program: AccountInfo<'info>,
193 ) -> Result<(), ProgramError> {
194 InternalCreateAtaCpi {
195 owner: self.owner,
196 mint: self.mint,
197 payer: self.payer,
198 associated_token_account: self.ata,
199 system_program,
200 bump: self.bump,
201 compressible,
202 idempotent: false,
203 }
204 .invoke()
205 }
206}
207
208pub struct CreateTokenAtaCpiIdempotent<'info> {
210 base: CreateTokenAtaCpi<'info>,
211}
212
213impl<'info> CreateTokenAtaCpiIdempotent<'info> {
214 pub fn rent_free(
216 self,
217 config: AccountInfo<'info>,
218 sponsor: AccountInfo<'info>,
219 system_program: AccountInfo<'info>,
220 ) -> CreateTokenAtaRentFreeCpi<'info> {
221 CreateTokenAtaRentFreeCpi {
222 payer: self.base.payer,
223 owner: self.base.owner,
224 mint: self.base.mint,
225 ata: self.base.ata,
226 bump: self.base.bump,
227 idempotent: true,
228 config,
229 sponsor,
230 system_program,
231 }
232 }
233
234 pub fn invoke_with(
236 self,
237 compressible: CompressibleParamsCpi<'info>,
238 system_program: AccountInfo<'info>,
239 ) -> Result<(), ProgramError> {
240 InternalCreateAtaCpi {
241 owner: self.base.owner,
242 mint: self.base.mint,
243 payer: self.base.payer,
244 associated_token_account: self.base.ata,
245 system_program,
246 bump: self.base.bump,
247 compressible,
248 idempotent: true,
249 }
250 .invoke()
251 }
252}
253
254pub struct CreateTokenAtaRentFreeCpi<'info> {
256 payer: AccountInfo<'info>,
257 owner: AccountInfo<'info>,
258 mint: AccountInfo<'info>,
259 ata: AccountInfo<'info>,
260 bump: u8,
261 idempotent: bool,
262 config: AccountInfo<'info>,
263 sponsor: AccountInfo<'info>,
264 system_program: AccountInfo<'info>,
265}
266
267impl<'info> CreateTokenAtaRentFreeCpi<'info> {
268 pub fn invoke(self) -> Result<(), ProgramError> {
270 InternalCreateAtaCpi {
271 owner: self.owner,
272 mint: self.mint,
273 payer: self.payer,
274 associated_token_account: self.ata,
275 system_program: self.system_program.clone(),
276 bump: self.bump,
277 compressible: CompressibleParamsCpi::new_ata(
278 self.config,
279 self.sponsor,
280 self.system_program,
281 ),
282 idempotent: self.idempotent,
283 }
284 .invoke()
285 }
286
287 pub fn invoke_signed(self, signer_seeds: &[&[&[u8]]]) -> Result<(), ProgramError> {
289 InternalCreateAtaCpi {
290 owner: self.owner,
291 mint: self.mint,
292 payer: self.payer,
293 associated_token_account: self.ata,
294 system_program: self.system_program.clone(),
295 bump: self.bump,
296 compressible: CompressibleParamsCpi::new_ata(
297 self.config,
298 self.sponsor,
299 self.system_program,
300 ),
301 idempotent: self.idempotent,
302 }
303 .invoke_signed(signer_seeds)
304 }
305}
306
307struct InternalCreateAtaCpi<'info> {
309 owner: AccountInfo<'info>,
310 mint: AccountInfo<'info>,
311 payer: AccountInfo<'info>,
312 associated_token_account: AccountInfo<'info>,
313 system_program: AccountInfo<'info>,
314 bump: u8,
315 compressible: CompressibleParamsCpi<'info>,
316 idempotent: bool,
317}
318
319impl<'info> InternalCreateAtaCpi<'info> {
320 fn instruction(&self) -> Result<Instruction, ProgramError> {
321 CreateAssociatedTokenAccount {
322 payer: *self.payer.key,
323 owner: *self.owner.key,
324 mint: *self.mint.key,
325 associated_token_account: *self.associated_token_account.key,
326 bump: self.bump,
327 compressible: CompressibleParams {
328 compressible_config: *self.compressible.compressible_config.key,
329 rent_sponsor: *self.compressible.rent_sponsor.key,
330 pre_pay_num_epochs: self.compressible.pre_pay_num_epochs,
331 lamports_per_write: self.compressible.lamports_per_write,
332 compress_to_account_pubkey: self.compressible.compress_to_account_pubkey.clone(),
333 token_account_version: self.compressible.token_account_version,
334 compression_only: self.compressible.compression_only,
335 },
336 idempotent: self.idempotent,
337 }
338 .instruction()
339 }
340
341 fn invoke(self) -> Result<(), ProgramError> {
342 let instruction = self.instruction()?;
343 let account_infos = [
344 self.owner,
345 self.mint,
346 self.payer,
347 self.associated_token_account,
348 self.system_program,
349 self.compressible.compressible_config,
350 self.compressible.rent_sponsor,
351 ];
352 invoke(&instruction, &account_infos)
353 }
354
355 fn invoke_signed(self, signer_seeds: &[&[&[u8]]]) -> Result<(), ProgramError> {
356 let instruction = self.instruction()?;
357 let account_infos = [
358 self.owner,
359 self.mint,
360 self.payer,
361 self.associated_token_account,
362 self.system_program,
363 self.compressible.compressible_config,
364 self.compressible.rent_sponsor,
365 ];
366 invoke_signed(&instruction, &account_infos, signer_seeds)
367 }
368}