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 {
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 .0
27}
28
29#[derive(Debug, Clone)]
42pub struct CreateAssociatedTokenAccount {
43 pub payer: Pubkey,
44 pub owner: Pubkey,
45 pub mint: Pubkey,
46 pub associated_token_account: Pubkey,
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 = derive_associated_token_account(&owner, &mint);
54 Self {
55 payer,
56 owner,
57 mint,
58 associated_token_account: ata,
59 compressible: CompressibleParams::default_ata(),
60 idempotent: false,
61 }
62 }
63
64 pub fn with_compressible(mut self, compressible_params: CompressibleParams) -> Self {
65 self.compressible = compressible_params;
66 self
67 }
68
69 pub fn idempotent(mut self) -> Self {
70 self.idempotent = true;
71 self
72 }
73
74 pub fn instruction(self) -> Result<Instruction, ProgramError> {
75 let instruction_data = CreateAssociatedTokenAccountInstructionData {
76 compressible_config: Some(CompressibleExtensionInstructionData {
77 token_account_version: self.compressible.token_account_version as u8,
78 rent_payment: self.compressible.pre_pay_num_epochs,
79 compression_only: self.compressible.compression_only as u8,
80 write_top_up: self.compressible.lamports_per_write.unwrap_or(0),
81 compress_to_account_pubkey: self.compressible.compress_to_account_pubkey.clone(),
82 }),
83 };
84
85 let discriminator = if self.idempotent {
86 CREATE_ATA_IDEMPOTENT_DISCRIMINATOR
87 } else {
88 CREATE_ATA_DISCRIMINATOR
89 };
90
91 let mut data = Vec::new();
92 data.push(discriminator);
93 instruction_data
94 .serialize(&mut data)
95 .map_err(|e| ProgramError::BorshIoError(e.to_string()))?;
96
97 let accounts = vec![
98 AccountMeta::new_readonly(self.owner, false),
99 AccountMeta::new_readonly(self.mint, false),
100 AccountMeta::new(self.payer, true),
101 AccountMeta::new(self.associated_token_account, false),
102 AccountMeta::new_readonly(Pubkey::new_from_array([0; 32]), false), AccountMeta::new_readonly(self.compressible.compressible_config, false),
104 AccountMeta::new(self.compressible.rent_sponsor, false),
105 ];
106
107 Ok(Instruction {
108 program_id: Pubkey::from(light_token_interface::LIGHT_TOKEN_PROGRAM_ID),
109 accounts,
110 data,
111 })
112 }
113}
114
115pub struct CreateTokenAtaCpi<'info> {
134 pub payer: AccountInfo<'info>,
135 pub owner: AccountInfo<'info>,
136 pub mint: AccountInfo<'info>,
137 pub ata: AccountInfo<'info>,
138}
139
140impl<'info> CreateTokenAtaCpi<'info> {
141 pub fn idempotent(self) -> CreateTokenAtaCpiIdempotent<'info> {
143 CreateTokenAtaCpiIdempotent { base: self }
144 }
145
146 pub fn rent_free(
148 self,
149 config: AccountInfo<'info>,
150 sponsor: AccountInfo<'info>,
151 system_program: AccountInfo<'info>,
152 ) -> CreateTokenAtaRentFreeCpi<'info> {
153 CreateTokenAtaRentFreeCpi {
154 payer: self.payer,
155 owner: self.owner,
156 mint: self.mint,
157 ata: self.ata,
158 idempotent: false,
159 config,
160 sponsor,
161 system_program,
162 }
163 }
164
165 pub fn invoke_with(
167 self,
168 compressible: CompressibleParamsCpi<'info>,
169 system_program: AccountInfo<'info>,
170 ) -> Result<(), ProgramError> {
171 InternalCreateAtaCpi {
172 owner: self.owner,
173 mint: self.mint,
174 payer: self.payer,
175 associated_token_account: self.ata,
176 system_program,
177 compressible,
178 idempotent: false,
179 }
180 .invoke()
181 }
182}
183
184pub struct CreateTokenAtaCpiIdempotent<'info> {
186 base: CreateTokenAtaCpi<'info>,
187}
188
189impl<'info> CreateTokenAtaCpiIdempotent<'info> {
190 pub fn rent_free(
192 self,
193 config: AccountInfo<'info>,
194 sponsor: AccountInfo<'info>,
195 system_program: AccountInfo<'info>,
196 ) -> CreateTokenAtaRentFreeCpi<'info> {
197 CreateTokenAtaRentFreeCpi {
198 payer: self.base.payer,
199 owner: self.base.owner,
200 mint: self.base.mint,
201 ata: self.base.ata,
202 idempotent: true,
203 config,
204 sponsor,
205 system_program,
206 }
207 }
208
209 pub fn invoke_with(
211 self,
212 compressible: CompressibleParamsCpi<'info>,
213 system_program: AccountInfo<'info>,
214 ) -> Result<(), ProgramError> {
215 InternalCreateAtaCpi {
216 owner: self.base.owner,
217 mint: self.base.mint,
218 payer: self.base.payer,
219 associated_token_account: self.base.ata,
220 system_program,
221 compressible,
222 idempotent: true,
223 }
224 .invoke()
225 }
226}
227
228pub struct CreateTokenAtaRentFreeCpi<'info> {
230 payer: AccountInfo<'info>,
231 owner: AccountInfo<'info>,
232 mint: AccountInfo<'info>,
233 ata: AccountInfo<'info>,
234 idempotent: bool,
235 config: AccountInfo<'info>,
236 sponsor: AccountInfo<'info>,
237 system_program: AccountInfo<'info>,
238}
239
240impl<'info> CreateTokenAtaRentFreeCpi<'info> {
241 pub fn invoke(self) -> Result<(), ProgramError> {
243 InternalCreateAtaCpi {
244 owner: self.owner,
245 mint: self.mint,
246 payer: self.payer,
247 associated_token_account: self.ata,
248 system_program: self.system_program.clone(),
249 compressible: CompressibleParamsCpi::new_ata(
250 self.config,
251 self.sponsor,
252 self.system_program,
253 ),
254 idempotent: self.idempotent,
255 }
256 .invoke()
257 }
258
259 pub fn invoke_signed(self, signer_seeds: &[&[&[u8]]]) -> Result<(), ProgramError> {
261 InternalCreateAtaCpi {
262 owner: self.owner,
263 mint: self.mint,
264 payer: self.payer,
265 associated_token_account: self.ata,
266 system_program: self.system_program.clone(),
267 compressible: CompressibleParamsCpi::new_ata(
268 self.config,
269 self.sponsor,
270 self.system_program,
271 ),
272 idempotent: self.idempotent,
273 }
274 .invoke_signed(signer_seeds)
275 }
276}
277
278struct InternalCreateAtaCpi<'info> {
280 owner: AccountInfo<'info>,
281 mint: AccountInfo<'info>,
282 payer: AccountInfo<'info>,
283 associated_token_account: AccountInfo<'info>,
284 system_program: AccountInfo<'info>,
285 compressible: CompressibleParamsCpi<'info>,
286 idempotent: bool,
287}
288
289impl<'info> InternalCreateAtaCpi<'info> {
290 fn instruction(&self) -> Result<Instruction, ProgramError> {
291 CreateAssociatedTokenAccount {
292 payer: *self.payer.key,
293 owner: *self.owner.key,
294 mint: *self.mint.key,
295 associated_token_account: *self.associated_token_account.key,
296 compressible: CompressibleParams {
297 compressible_config: *self.compressible.compressible_config.key,
298 rent_sponsor: *self.compressible.rent_sponsor.key,
299 pre_pay_num_epochs: self.compressible.pre_pay_num_epochs,
300 lamports_per_write: self.compressible.lamports_per_write,
301 compress_to_account_pubkey: self.compressible.compress_to_account_pubkey.clone(),
302 token_account_version: self.compressible.token_account_version,
303 compression_only: self.compressible.compression_only,
304 },
305 idempotent: self.idempotent,
306 }
307 .instruction()
308 }
309
310 fn invoke(self) -> Result<(), ProgramError> {
311 let instruction = self.instruction()?;
312 let account_infos = [
313 self.owner,
314 self.mint,
315 self.payer,
316 self.associated_token_account,
317 self.system_program,
318 self.compressible.compressible_config,
319 self.compressible.rent_sponsor,
320 ];
321 invoke(&instruction, &account_infos)
322 }
323
324 fn invoke_signed(self, signer_seeds: &[&[&[u8]]]) -> Result<(), ProgramError> {
325 let instruction = self.instruction()?;
326 let account_infos = [
327 self.owner,
328 self.mint,
329 self.payer,
330 self.associated_token_account,
331 self.system_program,
332 self.compressible.compressible_config,
333 self.compressible.rent_sponsor,
334 ];
335 invoke_signed(&instruction, &account_infos, signer_seeds)
336 }
337}