1use light_batched_merkle_tree::queue::BatchedQueueAccount;
13use light_compressed_account::instruction_data::traits::LightInstructionData;
14use light_token_interface::{
15 instructions::{
16 extensions::{ExtensionInstructionData, TokenMetadataInstructionData},
17 mint_action::{
18 Action, CpiContext, CreateMint, DecompressMintAction,
19 MintActionCompressedInstructionData, MintInstructionData,
20 },
21 },
22 state::MintMetadata,
23 LIGHT_TOKEN_PROGRAM_ID,
24};
25use solana_account_info::AccountInfo;
26use solana_instruction::Instruction;
27use solana_program_error::ProgramError;
28use solana_pubkey::Pubkey;
29
30use super::SystemAccountInfos;
31use crate::compressed_token::mint_action::{
32 get_mint_action_instruction_account_metas_cpi_write, MintActionMetaConfig,
33 MintActionMetaConfigCpiWrite,
34};
35
36pub const DEFAULT_RENT_PAYMENT: u8 = 16;
38pub const DEFAULT_WRITE_TOP_UP: u32 = 766;
40
41#[derive(Debug, Clone)]
45pub struct SingleMintParams<'a> {
46 pub decimals: u8,
47 pub address_merkle_tree_root_index: u16,
48 pub mint_authority: Pubkey,
49 pub compression_address: [u8; 32],
50 pub mint: Pubkey,
51 pub bump: u8,
52 pub freeze_authority: Option<Pubkey>,
53 pub mint_seed_pubkey: Pubkey,
55 pub authority_seeds: Option<&'a [&'a [u8]]>,
57 pub mint_signer_seeds: Option<&'a [&'a [u8]]>,
59 pub token_metadata: Option<&'a TokenMetadataInstructionData>,
61}
62
63#[derive(Debug, Clone)]
68pub struct CreateMintsParams<'a> {
69 pub mints: &'a [SingleMintParams<'a>],
71 pub proof: light_compressed_account::instruction_data::compressed_proof::CompressedProof,
73 pub rent_payment: u8,
76 pub write_top_up: u32,
79 pub cpi_context_offset: u8,
84 pub output_queue_index: u8,
87 pub address_tree_index: u8,
90 pub state_tree_index: u8,
94}
95
96impl<'a> CreateMintsParams<'a> {
97 #[inline(never)]
98 pub fn new(
99 mints: &'a [SingleMintParams<'a>],
100 proof: light_compressed_account::instruction_data::compressed_proof::CompressedProof,
101 ) -> Self {
102 Self {
103 mints,
104 proof,
105 rent_payment: DEFAULT_RENT_PAYMENT,
106 write_top_up: DEFAULT_WRITE_TOP_UP,
107 cpi_context_offset: 0,
108 output_queue_index: 0,
109 address_tree_index: 1,
110 state_tree_index: 2,
111 }
112 }
113
114 pub fn with_rent_payment(mut self, rent_payment: u8) -> Self {
115 self.rent_payment = rent_payment;
116 self
117 }
118
119 pub fn with_write_top_up(mut self, write_top_up: u32) -> Self {
120 self.write_top_up = write_top_up;
121 self
122 }
123
124 pub fn with_cpi_context_offset(mut self, offset: u8) -> Self {
129 self.cpi_context_offset = offset;
130 self
131 }
132
133 pub fn with_output_queue_index(mut self, index: u8) -> Self {
135 self.output_queue_index = index;
136 self
137 }
138
139 pub fn with_address_tree_index(mut self, index: u8) -> Self {
141 self.address_tree_index = index;
142 self
143 }
144
145 pub fn with_state_tree_index(mut self, index: u8) -> Self {
148 self.state_tree_index = index;
149 self
150 }
151}
152
153pub struct CreateMintsCpi<'a, 'info> {
178 pub mint_seed_accounts: &'a [AccountInfo<'info>],
180 pub payer: AccountInfo<'info>,
182 pub address_tree: AccountInfo<'info>,
184 pub output_queue: AccountInfo<'info>,
186 pub state_merkle_tree: AccountInfo<'info>,
188 pub compressible_config: AccountInfo<'info>,
190 pub mints: &'a [AccountInfo<'info>],
192 pub rent_sponsor: AccountInfo<'info>,
194 pub system_accounts: SystemAccountInfos<'info>,
196 pub cpi_context_account: AccountInfo<'info>,
198 pub params: CreateMintsParams<'a>,
200}
201
202impl<'a, 'info> CreateMintsCpi<'a, 'info> {
203 #[inline(never)]
205 pub fn validate(&self) -> Result<(), ProgramError> {
206 let n = self.params.mints.len();
207 if n == 0 {
208 return Err(ProgramError::InvalidArgument);
209 }
210 if self.mint_seed_accounts.len() != n {
211 return Err(ProgramError::InvalidArgument);
212 }
213 if self.mints.len() != n {
214 return Err(ProgramError::InvalidArgument);
215 }
216 Ok(())
217 }
218
219 #[inline(never)]
224 pub fn invoke(self) -> Result<(), ProgramError> {
225 self.validate()?;
226 let n = self.params.mints.len();
227
228 if n == 1 && self.params.cpi_context_offset == 0 {
232 self.invoke_single_mint()
233 } else {
234 self.invoke_multiple_mints()
235 }
236 }
237
238 #[inline(never)]
240 fn invoke_single_mint(self) -> Result<(), ProgramError> {
241 let mint_params = &self.params.mints[0];
242
243 let mint_data = build_mint_instruction_data(mint_params, self.mint_seed_accounts[0].key);
244
245 let decompress_action = DecompressMintAction {
246 rent_payment: self.params.rent_payment,
247 write_top_up: self.params.write_top_up,
248 };
249
250 let instruction_data = MintActionCompressedInstructionData::new_mint(
251 mint_params.address_merkle_tree_root_index,
252 self.params.proof,
253 mint_data,
254 )
255 .with_decompress_mint(decompress_action);
256
257 let mut meta_config = MintActionMetaConfig::new_create_mint(
258 *self.payer.key,
259 *self.payer.key,
260 *self.mint_seed_accounts[0].key,
261 *self.address_tree.key,
262 *self.output_queue.key,
263 )
264 .with_compressible_mint(
265 *self.mints[0].key,
266 *self.compressible_config.key,
267 *self.rent_sponsor.key,
268 );
269 meta_config.input_queue = Some(*self.output_queue.key);
270
271 self.invoke_mint_action(instruction_data, meta_config, 0)
272 }
273
274 #[inline(never)]
276 fn invoke_multiple_mints(self) -> Result<(), ProgramError> {
277 let n = self.params.mints.len();
278
279 let base_leaf_index = get_base_leaf_index(&self.output_queue)?;
281
282 let decompress_action = DecompressMintAction {
283 rent_payment: self.params.rent_payment,
284 write_top_up: self.params.write_top_up,
285 };
286
287 for i in 0..(n - 1) {
289 self.invoke_cpi_write(i)?;
290 }
291
292 self.invoke_execute(n - 1, &decompress_action)?;
294
295 for i in 0..(n - 1) {
297 self.invoke_decompress(i, base_leaf_index, &decompress_action)?;
298 }
299
300 Ok(())
301 }
302
303 #[inline(never)]
306 fn invoke_cpi_write(&self, index: usize) -> Result<(), ProgramError> {
307 let mint_params = &self.params.mints[index];
308 let offset = self.params.cpi_context_offset;
309
310 let cpi_context = CpiContext {
315 set_context: index > 0 || offset > 0,
316 first_set_context: index == 0 && offset == 0,
317 in_tree_index: self.params.address_tree_index,
318 in_queue_index: self.params.output_queue_index,
319 out_queue_index: self.params.output_queue_index,
320 token_out_queue_index: 0,
321 assigned_account_index: offset + index as u8,
322 read_only_address_trees: [0; 4],
323 address_tree_pubkey: self.address_tree.key.to_bytes(),
324 };
325
326 let mint_data =
327 build_mint_instruction_data(mint_params, self.mint_seed_accounts[index].key);
328
329 let instruction_data = MintActionCompressedInstructionData::new_mint_write_to_cpi_context(
330 mint_params.address_merkle_tree_root_index,
331 mint_data,
332 cpi_context,
333 );
334
335 let cpi_write_config = MintActionMetaConfigCpiWrite {
336 fee_payer: *self.payer.key,
337 mint_signer: Some(*self.mint_seed_accounts[index].key),
338 authority: *self.payer.key,
339 cpi_context: *self.cpi_context_account.key,
340 };
341
342 let account_metas = get_mint_action_instruction_account_metas_cpi_write(cpi_write_config);
343 let ix_data = instruction_data
344 .data()
345 .map_err(|e| ProgramError::BorshIoError(e.to_string()))?;
346
347 let account_infos = [
355 self.system_accounts.light_system_program.clone(),
356 self.mint_seed_accounts[index].clone(),
357 self.payer.clone(),
358 self.payer.clone(),
359 self.system_accounts.cpi_authority_pda.clone(),
360 self.cpi_context_account.clone(),
361 ];
362 let instruction = Instruction {
363 program_id: Pubkey::new_from_array(LIGHT_TOKEN_PROGRAM_ID),
364 accounts: account_metas,
365 data: ix_data,
366 };
367
368 let mut seeds: [&[&[u8]]; 2] = [&[], &[]];
370 let mut num_signers = 0;
371 if let Some(s) = mint_params.mint_signer_seeds {
372 seeds[num_signers] = s;
373 num_signers += 1;
374 }
375 if let Some(s) = mint_params.authority_seeds {
376 seeds[num_signers] = s;
377 num_signers += 1;
378 }
379 solana_cpi::invoke_signed(&instruction, &account_infos, &seeds[..num_signers])
380 }
381
382 #[inline(never)]
385 fn invoke_execute(
386 &self,
387 last_idx: usize,
388 decompress_action: &DecompressMintAction,
389 ) -> Result<(), ProgramError> {
390 let mint_params = &self.params.mints[last_idx];
391 let offset = self.params.cpi_context_offset;
392
393 let mint_data =
394 build_mint_instruction_data(mint_params, self.mint_seed_accounts[last_idx].key);
395
396 let instruction_data = MintActionCompressedInstructionData {
398 leaf_index: 0,
399 prove_by_index: false,
400 root_index: mint_params.address_merkle_tree_root_index,
401 max_top_up: 0,
402 create_mint: Some(CreateMint::default()),
403 actions: vec![Action::DecompressMint(*decompress_action)],
404 proof: Some(self.params.proof),
405 cpi_context: Some(CpiContext {
406 set_context: false,
407 first_set_context: false,
408 in_tree_index: self.params.address_tree_index,
409 in_queue_index: self.params.address_tree_index,
410 out_queue_index: self.params.output_queue_index,
411 token_out_queue_index: 0,
412 assigned_account_index: offset + last_idx as u8,
413 read_only_address_trees: [0; 4],
414 address_tree_pubkey: self.address_tree.key.to_bytes(),
415 }),
416 mint: Some(mint_data),
417 };
418
419 let mut meta_config = MintActionMetaConfig::new_create_mint(
420 *self.payer.key,
421 *self.payer.key,
422 *self.mint_seed_accounts[last_idx].key,
423 *self.address_tree.key,
424 *self.output_queue.key,
425 )
426 .with_compressible_mint(
427 *self.mints[last_idx].key,
428 *self.compressible_config.key,
429 *self.rent_sponsor.key,
430 );
431 meta_config.cpi_context = Some(*self.cpi_context_account.key);
432 meta_config.input_queue = Some(*self.output_queue.key);
433
434 self.invoke_mint_action(instruction_data, meta_config, last_idx)
435 }
436
437 #[inline(never)]
440 fn invoke_decompress(
441 &self,
442 index: usize,
443 base_leaf_index: u32,
444 decompress_action: &DecompressMintAction,
445 ) -> Result<(), ProgramError> {
446 let mint_params = &self.params.mints[index];
447
448 let mint_data =
449 build_mint_instruction_data(mint_params, self.mint_seed_accounts[index].key);
450
451 let instruction_data = MintActionCompressedInstructionData {
452 leaf_index: base_leaf_index + index as u32,
453 prove_by_index: true,
454 root_index: 0,
455 max_top_up: 0,
456 create_mint: None,
457 actions: vec![Action::DecompressMint(*decompress_action)],
458 proof: None,
459 cpi_context: None,
460 mint: Some(mint_data),
461 };
462
463 let meta_config = MintActionMetaConfig::new(
465 *self.payer.key,
466 *self.payer.key,
467 *self.state_merkle_tree.key, *self.output_queue.key, *self.output_queue.key, )
471 .with_compressible_mint(
472 *self.mints[index].key,
473 *self.compressible_config.key,
474 *self.rent_sponsor.key,
475 );
476
477 self.invoke_mint_action(instruction_data, meta_config, index)
478 }
479
480 #[inline(never)]
483 fn invoke_mint_action(
484 &self,
485 instruction_data: MintActionCompressedInstructionData,
486 meta_config: MintActionMetaConfig,
487 mint_index: usize,
488 ) -> Result<(), ProgramError> {
489 let account_metas = meta_config.to_account_metas();
490 let ix_data = instruction_data
491 .data()
492 .map_err(|e| ProgramError::BorshIoError(e.to_string()))?;
493
494 let mut account_infos = vec![self.payer.clone()];
496
497 account_infos.push(self.system_accounts.light_system_program.clone());
499
500 for mint_seed in self.mint_seed_accounts {
502 account_infos.push(mint_seed.clone());
503 }
504
505 account_infos.push(self.system_accounts.cpi_authority_pda.clone());
507 account_infos.push(self.system_accounts.registered_program_pda.clone());
508 account_infos.push(self.system_accounts.account_compression_authority.clone());
509 account_infos.push(self.system_accounts.account_compression_program.clone());
510 account_infos.push(self.system_accounts.system_program.clone());
511
512 account_infos.push(self.cpi_context_account.clone());
514 account_infos.push(self.output_queue.clone());
515 account_infos.push(self.state_merkle_tree.clone());
516 account_infos.push(self.address_tree.clone());
517 account_infos.push(self.compressible_config.clone());
518 account_infos.push(self.rent_sponsor.clone());
519
520 for mint in self.mints {
522 account_infos.push(mint.clone());
523 }
524
525 let instruction = Instruction {
526 program_id: Pubkey::new_from_array(LIGHT_TOKEN_PROGRAM_ID),
527 accounts: account_metas,
528 data: ix_data,
529 };
530
531 let mint_params = &self.params.mints[mint_index];
533 let mut seeds: [&[&[u8]]; 2] = [&[], &[]];
534 let mut num_signers = 0;
535 if let Some(s) = mint_params.mint_signer_seeds {
536 seeds[num_signers] = s;
537 num_signers += 1;
538 }
539 if let Some(s) = mint_params.authority_seeds {
540 seeds[num_signers] = s;
541 num_signers += 1;
542 }
543 solana_cpi::invoke_signed(&instruction, &account_infos, &seeds[..num_signers])
544 }
545}
546
547#[inline(never)]
549fn build_mint_instruction_data(
550 mint_params: &SingleMintParams<'_>,
551 mint_signer: &Pubkey,
552) -> MintInstructionData {
553 let extensions = mint_params
555 .token_metadata
556 .cloned()
557 .map(|metadata| vec![ExtensionInstructionData::TokenMetadata(metadata)]);
558
559 MintInstructionData {
560 supply: 0,
561 decimals: mint_params.decimals,
562 metadata: MintMetadata {
563 version: 3,
564 mint: mint_params.mint.to_bytes().into(),
565 mint_decompressed: false,
566 mint_signer: mint_signer.to_bytes(),
567 bump: mint_params.bump,
568 },
569 mint_authority: Some(mint_params.mint_authority.to_bytes().into()),
570 freeze_authority: mint_params.freeze_authority.map(|a| a.to_bytes().into()),
571 extensions,
572 }
573}
574
575#[inline(never)]
577fn get_base_leaf_index(output_queue: &AccountInfo) -> Result<u32, ProgramError> {
578 let queue = BatchedQueueAccount::output_from_account_info(output_queue)
579 .map_err(|_| ProgramError::InvalidAccountData)?;
580 Ok(queue.batch_metadata.next_index as u32)
581}
582
583#[inline(never)]
607pub fn create_mints<'a, 'info>(
608 payer: &AccountInfo<'info>,
609 accounts: &'info [AccountInfo<'info>],
610 params: CreateMintsParams<'a>,
611) -> Result<(), ProgramError> {
612 if params.mints.is_empty() {
613 return Err(ProgramError::InvalidArgument);
614 }
615
616 let n = params.mints.len();
617 let mint_signers_start = 1;
618 let cpi_authority_idx = n + 1;
619 let registered_program_idx = n + 2;
620 let compression_authority_idx = n + 3;
621 let compression_program_idx = n + 4;
622 let system_program_idx = n + 5;
623 let cpi_context_idx = n + 6;
624 let output_queue_idx = n + 7;
625 let state_merkle_tree_idx = n + 8;
626 let address_tree_idx = n + 9;
627 let compressible_config_idx = n + 10;
628 let rent_sponsor_idx = n + 11;
629 let mint_pdas_start = n + 12;
630
631 let cpi = CreateMintsCpi {
633 mint_seed_accounts: &accounts[mint_signers_start..mint_signers_start + n],
634 payer: payer.clone(),
635 address_tree: accounts[address_tree_idx].clone(),
636 output_queue: accounts[output_queue_idx].clone(),
637 state_merkle_tree: accounts[state_merkle_tree_idx].clone(),
638 compressible_config: accounts[compressible_config_idx].clone(),
639 mints: &accounts[mint_pdas_start..mint_pdas_start + n],
640 rent_sponsor: accounts[rent_sponsor_idx].clone(),
641 system_accounts: SystemAccountInfos {
642 light_system_program: accounts[0].clone(),
643 cpi_authority_pda: accounts[cpi_authority_idx].clone(),
644 registered_program_pda: accounts[registered_program_idx].clone(),
645 account_compression_authority: accounts[compression_authority_idx].clone(),
646 account_compression_program: accounts[compression_program_idx].clone(),
647 system_program: accounts[system_program_idx].clone(),
648 },
649 cpi_context_account: accounts[cpi_context_idx].clone(),
650 params,
651 };
652 cpi.invoke()
653}
654
655