1use light_batched_merkle_tree::queue::BatchedQueueAccount;
13use light_compressed_account::instruction_data::traits::LightInstructionData;
14use light_compressed_token_sdk::compressed_token::mint_action::{
15 get_mint_action_instruction_account_metas_cpi_write, MintActionMetaConfig,
16 MintActionMetaConfigCpiWrite,
17};
18use light_token_interface::{
19 instructions::{
20 extensions::{ExtensionInstructionData, TokenMetadataInstructionData},
21 mint_action::{
22 Action, CpiContext, CreateMint, DecompressMintAction,
23 MintActionCompressedInstructionData, MintInstructionData,
24 },
25 },
26 state::MintMetadata,
27 LIGHT_TOKEN_PROGRAM_ID,
28};
29use solana_account_info::AccountInfo;
30use solana_instruction::Instruction;
31use solana_program_error::ProgramError;
32use solana_pubkey::Pubkey;
33
34use super::SystemAccountInfos;
35
36pub const DEFAULT_RENT_PAYMENT: u8 = 16;
38pub const DEFAULT_WRITE_TOP_UP: u32 = 766;
40
41#[derive(Debug, Clone)]
46pub struct SingleMintParams<'a> {
47 pub decimals: u8,
48 pub mint_authority: Pubkey,
49 pub mint_bump: Option<u8>,
51 pub freeze_authority: Option<Pubkey>,
52 pub mint_seed_pubkey: Pubkey,
54 pub authority_seeds: Option<&'a [&'a [u8]]>,
56 pub mint_signer_seeds: Option<&'a [&'a [u8]]>,
58 pub token_metadata: Option<&'a TokenMetadataInstructionData>,
60}
61
62#[derive(Debug, Clone)]
67pub struct CreateMintsParams<'a> {
68 pub mints: &'a [SingleMintParams<'a>],
70 pub proof: light_compressed_account::instruction_data::compressed_proof::CompressedProof,
72 pub address_merkle_tree_root_index: u16,
74 pub rent_payment: u8,
77 pub write_top_up: u32,
80 pub cpi_context_offset: u8,
85 pub output_queue_index: u8,
88 pub address_tree_index: u8,
91 pub state_tree_index: u8,
95}
96
97impl<'a> CreateMintsParams<'a> {
98 #[inline(never)]
99 pub fn new(
100 mints: &'a [SingleMintParams<'a>],
101 proof: light_compressed_account::instruction_data::compressed_proof::CompressedProof,
102 address_merkle_tree_root_index: u16,
103 ) -> Self {
104 Self {
105 mints,
106 proof,
107 address_merkle_tree_root_index,
108 rent_payment: DEFAULT_RENT_PAYMENT,
109 write_top_up: DEFAULT_WRITE_TOP_UP,
110 cpi_context_offset: 0,
111 output_queue_index: 0,
112 address_tree_index: 1,
113 state_tree_index: 2,
114 }
115 }
116
117 pub fn with_rent_payment(mut self, rent_payment: u8) -> Self {
118 self.rent_payment = rent_payment;
119 self
120 }
121
122 pub fn with_write_top_up(mut self, write_top_up: u32) -> Self {
123 self.write_top_up = write_top_up;
124 self
125 }
126
127 pub fn with_cpi_context_offset(mut self, offset: u8) -> Self {
132 self.cpi_context_offset = offset;
133 self
134 }
135
136 pub fn with_output_queue_index(mut self, index: u8) -> Self {
138 self.output_queue_index = index;
139 self
140 }
141
142 pub fn with_address_tree_index(mut self, index: u8) -> Self {
144 self.address_tree_index = index;
145 self
146 }
147
148 pub fn with_state_tree_index(mut self, index: u8) -> Self {
151 self.state_tree_index = index;
152 self
153 }
154}
155
156pub struct CreateMintsCpi<'a, 'info> {
181 pub mint_seed_accounts: &'a [AccountInfo<'info>],
183 pub payer: AccountInfo<'info>,
185 pub address_tree: AccountInfo<'info>,
187 pub output_queue: AccountInfo<'info>,
189 pub state_merkle_tree: AccountInfo<'info>,
191 pub compressible_config: AccountInfo<'info>,
193 pub mints: &'a [AccountInfo<'info>],
195 pub rent_sponsor: AccountInfo<'info>,
197 pub system_accounts: SystemAccountInfos<'info>,
199 pub cpi_context_account: AccountInfo<'info>,
201 pub params: CreateMintsParams<'a>,
203}
204
205impl<'a, 'info> CreateMintsCpi<'a, 'info> {
206 #[inline(never)]
208 pub fn validate(&self) -> Result<(), ProgramError> {
209 let n = self.params.mints.len();
210 if n == 0 {
211 return Err(ProgramError::InvalidArgument);
212 }
213 if self.mint_seed_accounts.len() != n {
214 return Err(ProgramError::InvalidArgument);
215 }
216 if self.mints.len() != n {
217 return Err(ProgramError::InvalidArgument);
218 }
219 Ok(())
220 }
221
222 #[inline(never)]
227 pub fn invoke(self) -> Result<(), ProgramError> {
228 self.validate()?;
229 let n = self.params.mints.len();
230
231 if n == 1 && self.params.cpi_context_offset == 0 {
235 self.invoke_single_mint()
236 } else {
237 self.invoke_multiple_mints()
238 }
239 }
240
241 #[inline(never)]
243 fn invoke_single_mint(self) -> Result<(), ProgramError> {
244 let mint_params = &self.params.mints[0];
245 let (mint, bump) = get_mint_and_bump(mint_params);
246
247 let mint_data =
248 build_mint_instruction_data(mint_params, self.mint_seed_accounts[0].key, mint, bump);
249
250 let decompress_action = DecompressMintAction {
251 rent_payment: self.params.rent_payment,
252 write_top_up: self.params.write_top_up,
253 };
254
255 let instruction_data = MintActionCompressedInstructionData::new_mint(
256 self.params.address_merkle_tree_root_index,
257 self.params.proof,
258 mint_data,
259 )
260 .with_decompress_mint(decompress_action);
261
262 let mut meta_config = MintActionMetaConfig::new_create_mint(
263 *self.payer.key,
264 *self.payer.key,
265 *self.mint_seed_accounts[0].key,
266 *self.address_tree.key,
267 *self.output_queue.key,
268 )
269 .with_compressible_mint(
270 *self.mints[0].key,
271 *self.compressible_config.key,
272 *self.rent_sponsor.key,
273 );
274 meta_config.input_queue = Some(*self.output_queue.key);
275
276 self.invoke_mint_action(instruction_data, meta_config, 0)
277 }
278
279 #[inline(never)]
281 fn invoke_multiple_mints(self) -> Result<(), ProgramError> {
282 let n = self.params.mints.len();
283
284 let base_leaf_index = get_base_leaf_index(&self.output_queue)?;
286
287 let decompress_action = DecompressMintAction {
288 rent_payment: self.params.rent_payment,
289 write_top_up: self.params.write_top_up,
290 };
291
292 for i in 0..(n - 1) {
294 self.invoke_cpi_write(i)?;
295 }
296
297 self.invoke_execute(n - 1, &decompress_action)?;
299
300 for i in 0..(n - 1) {
302 self.invoke_decompress(i, base_leaf_index, &decompress_action)?;
303 }
304
305 Ok(())
306 }
307
308 #[inline(never)]
311 fn invoke_cpi_write(&self, index: usize) -> Result<(), ProgramError> {
312 let mint_params = &self.params.mints[index];
313 let offset = self.params.cpi_context_offset;
314 let (mint, bump) = get_mint_and_bump(mint_params);
315
316 let cpi_context = CpiContext {
321 set_context: index > 0 || offset > 0,
322 first_set_context: index == 0 && offset == 0,
323 in_tree_index: self.params.address_tree_index,
324 in_queue_index: self.params.output_queue_index,
325 out_queue_index: self.params.output_queue_index,
326 token_out_queue_index: 0,
327 assigned_account_index: offset + index as u8,
328 read_only_address_trees: [0; 4],
329 address_tree_pubkey: self.address_tree.key.to_bytes(),
330 };
331
332 let mint_data = build_mint_instruction_data(
333 mint_params,
334 self.mint_seed_accounts[index].key,
335 mint,
336 bump,
337 );
338
339 let instruction_data = MintActionCompressedInstructionData::new_mint_write_to_cpi_context(
340 self.params.address_merkle_tree_root_index,
341 mint_data,
342 cpi_context,
343 );
344
345 let cpi_write_config = MintActionMetaConfigCpiWrite {
346 fee_payer: *self.payer.key,
347 mint_signer: Some(*self.mint_seed_accounts[index].key),
348 authority: *self.payer.key,
349 cpi_context: *self.cpi_context_account.key,
350 };
351
352 let account_metas = get_mint_action_instruction_account_metas_cpi_write(cpi_write_config);
353 let ix_data = instruction_data
354 .data()
355 .map_err(|e| ProgramError::BorshIoError(e.to_string()))?;
356
357 let account_infos = [
365 self.system_accounts.light_system_program.clone(),
366 self.mint_seed_accounts[index].clone(),
367 self.payer.clone(),
368 self.payer.clone(),
369 self.system_accounts.cpi_authority_pda.clone(),
370 self.cpi_context_account.clone(),
371 ];
372 let instruction = Instruction {
373 program_id: Pubkey::new_from_array(LIGHT_TOKEN_PROGRAM_ID),
374 accounts: account_metas,
375 data: ix_data,
376 };
377
378 let mut seeds: [&[&[u8]]; 2] = [&[], &[]];
380 let mut num_signers = 0;
381 if let Some(s) = mint_params.mint_signer_seeds {
382 seeds[num_signers] = s;
383 num_signers += 1;
384 }
385 if let Some(s) = mint_params.authority_seeds {
386 seeds[num_signers] = s;
387 num_signers += 1;
388 }
389 solana_cpi::invoke_signed(&instruction, &account_infos, &seeds[..num_signers])
390 }
391
392 #[inline(never)]
395 fn invoke_execute(
396 &self,
397 last_idx: usize,
398 decompress_action: &DecompressMintAction,
399 ) -> Result<(), ProgramError> {
400 let mint_params = &self.params.mints[last_idx];
401 let offset = self.params.cpi_context_offset;
402 let (mint, bump) = get_mint_and_bump(mint_params);
403
404 let mint_data = build_mint_instruction_data(
405 mint_params,
406 self.mint_seed_accounts[last_idx].key,
407 mint,
408 bump,
409 );
410
411 let instruction_data = MintActionCompressedInstructionData {
413 leaf_index: 0,
414 prove_by_index: false,
415 root_index: self.params.address_merkle_tree_root_index,
416 max_top_up: 0,
417 create_mint: Some(CreateMint::default()),
418 actions: vec![Action::DecompressMint(*decompress_action)],
419 proof: Some(self.params.proof),
420 cpi_context: Some(CpiContext {
421 set_context: false,
422 first_set_context: false,
423 in_tree_index: self.params.address_tree_index,
424 in_queue_index: self.params.address_tree_index,
425 out_queue_index: self.params.output_queue_index,
426 token_out_queue_index: 0,
427 assigned_account_index: offset + last_idx as u8,
428 read_only_address_trees: [0; 4],
429 address_tree_pubkey: self.address_tree.key.to_bytes(),
430 }),
431 mint: Some(mint_data),
432 };
433
434 let mut meta_config = MintActionMetaConfig::new_create_mint(
435 *self.payer.key,
436 *self.payer.key,
437 *self.mint_seed_accounts[last_idx].key,
438 *self.address_tree.key,
439 *self.output_queue.key,
440 )
441 .with_compressible_mint(
442 *self.mints[last_idx].key,
443 *self.compressible_config.key,
444 *self.rent_sponsor.key,
445 );
446 meta_config.cpi_context = Some(*self.cpi_context_account.key);
447 meta_config.input_queue = Some(*self.output_queue.key);
448
449 self.invoke_mint_action(instruction_data, meta_config, last_idx)
450 }
451
452 #[inline(never)]
455 fn invoke_decompress(
456 &self,
457 index: usize,
458 base_leaf_index: u32,
459 decompress_action: &DecompressMintAction,
460 ) -> Result<(), ProgramError> {
461 let mint_params = &self.params.mints[index];
462 let (mint, bump) = get_mint_and_bump(mint_params);
463
464 let mint_data = build_mint_instruction_data(
465 mint_params,
466 self.mint_seed_accounts[index].key,
467 mint,
468 bump,
469 );
470
471 let instruction_data = MintActionCompressedInstructionData {
472 leaf_index: base_leaf_index + index as u32,
473 prove_by_index: true,
474 root_index: 0,
475 max_top_up: 0,
476 create_mint: None,
477 actions: vec![Action::DecompressMint(*decompress_action)],
478 proof: None,
479 cpi_context: None,
480 mint: Some(mint_data),
481 };
482
483 let meta_config = MintActionMetaConfig::new(
485 *self.payer.key,
486 *self.payer.key,
487 *self.state_merkle_tree.key, *self.output_queue.key, *self.output_queue.key, )
491 .with_compressible_mint(
492 *self.mints[index].key,
493 *self.compressible_config.key,
494 *self.rent_sponsor.key,
495 );
496
497 self.invoke_mint_action(instruction_data, meta_config, index)
498 }
499
500 #[inline(never)]
503 fn invoke_mint_action(
504 &self,
505 instruction_data: MintActionCompressedInstructionData,
506 meta_config: MintActionMetaConfig,
507 mint_index: usize,
508 ) -> Result<(), ProgramError> {
509 let account_metas = meta_config.to_account_metas();
510 let ix_data = instruction_data
511 .data()
512 .map_err(|e| ProgramError::BorshIoError(e.to_string()))?;
513
514 let mut account_infos = vec![self.payer.clone()];
516
517 account_infos.push(self.system_accounts.light_system_program.clone());
519
520 for mint_seed in self.mint_seed_accounts {
522 account_infos.push(mint_seed.clone());
523 }
524
525 account_infos.push(self.system_accounts.cpi_authority_pda.clone());
527 account_infos.push(self.system_accounts.registered_program_pda.clone());
528 account_infos.push(self.system_accounts.account_compression_authority.clone());
529 account_infos.push(self.system_accounts.account_compression_program.clone());
530 account_infos.push(self.system_accounts.system_program.clone());
531
532 account_infos.push(self.cpi_context_account.clone());
534 account_infos.push(self.output_queue.clone());
535 account_infos.push(self.state_merkle_tree.clone());
536 account_infos.push(self.address_tree.clone());
537 account_infos.push(self.compressible_config.clone());
538 account_infos.push(self.rent_sponsor.clone());
539
540 for mint in self.mints {
542 account_infos.push(mint.clone());
543 }
544
545 let instruction = Instruction {
546 program_id: Pubkey::new_from_array(LIGHT_TOKEN_PROGRAM_ID),
547 accounts: account_metas,
548 data: ix_data,
549 };
550
551 let mint_params = &self.params.mints[mint_index];
553 let mut seeds: [&[&[u8]]; 2] = [&[], &[]];
554 let mut num_signers = 0;
555 if let Some(s) = mint_params.mint_signer_seeds {
556 seeds[num_signers] = s;
557 num_signers += 1;
558 }
559 if let Some(s) = mint_params.authority_seeds {
560 seeds[num_signers] = s;
561 num_signers += 1;
562 }
563 solana_cpi::invoke_signed(&instruction, &account_infos, &seeds[..num_signers])
564 }
565}
566
567#[inline(never)]
569fn get_mint_and_bump(params: &SingleMintParams) -> (Pubkey, u8) {
570 let (mint, derived_bump) = super::find_mint_address(¶ms.mint_seed_pubkey);
571 let bump = params.mint_bump.unwrap_or(derived_bump);
572 (mint, bump)
573}
574
575#[inline(never)]
579fn build_mint_instruction_data(
580 mint_params: &SingleMintParams<'_>,
581 mint_signer: &Pubkey,
582 mint: Pubkey,
583 bump: u8,
584) -> MintInstructionData {
585 let extensions = mint_params
587 .token_metadata
588 .cloned()
589 .map(|metadata| vec![ExtensionInstructionData::TokenMetadata(metadata)]);
590
591 MintInstructionData {
592 supply: 0,
593 decimals: mint_params.decimals,
594 metadata: MintMetadata {
595 version: 3,
596 mint: mint.to_bytes().into(),
597 mint_decompressed: false,
598 mint_signer: mint_signer.to_bytes(),
599 bump,
600 },
601 mint_authority: Some(mint_params.mint_authority.to_bytes().into()),
602 freeze_authority: mint_params.freeze_authority.map(|a| a.to_bytes().into()),
603 extensions,
604 }
605}
606
607#[inline(never)]
609fn get_base_leaf_index(output_queue: &AccountInfo) -> Result<u32, ProgramError> {
610 let queue = BatchedQueueAccount::output_from_account_info(output_queue)
611 .map_err(|_| ProgramError::InvalidAccountData)?;
612 Ok(queue.batch_metadata.next_index as u32)
613}
614
615#[inline(never)]
639pub fn create_mints<'a, 'info>(
640 payer: &AccountInfo<'info>,
641 accounts: &'info [AccountInfo<'info>],
642 params: CreateMintsParams<'a>,
643) -> Result<(), ProgramError> {
644 if params.mints.is_empty() {
645 return Err(ProgramError::InvalidArgument);
646 }
647
648 let n = params.mints.len();
649 let mint_signers_start = 1;
650 let cpi_authority_idx = n + 1;
651 let registered_program_idx = n + 2;
652 let compression_authority_idx = n + 3;
653 let compression_program_idx = n + 4;
654 let system_program_idx = n + 5;
655 let cpi_context_idx = n + 6;
656 let output_queue_idx = n + 7;
657 let state_merkle_tree_idx = n + 8;
658 let address_tree_idx = n + 9;
659 let compressible_config_idx = n + 10;
660 let rent_sponsor_idx = n + 11;
661 let mint_pdas_start = n + 12;
662
663 let cpi = CreateMintsCpi {
665 mint_seed_accounts: &accounts[mint_signers_start..mint_signers_start + n],
666 payer: payer.clone(),
667 address_tree: accounts[address_tree_idx].clone(),
668 output_queue: accounts[output_queue_idx].clone(),
669 state_merkle_tree: accounts[state_merkle_tree_idx].clone(),
670 compressible_config: accounts[compressible_config_idx].clone(),
671 mints: &accounts[mint_pdas_start..mint_pdas_start + n],
672 rent_sponsor: accounts[rent_sponsor_idx].clone(),
673 system_accounts: SystemAccountInfos {
674 light_system_program: accounts[0].clone(),
675 cpi_authority_pda: accounts[cpi_authority_idx].clone(),
676 registered_program_pda: accounts[registered_program_idx].clone(),
677 account_compression_authority: accounts[compression_authority_idx].clone(),
678 account_compression_program: accounts[compression_program_idx].clone(),
679 system_program: accounts[system_program_idx].clone(),
680 },
681 cpi_context_account: accounts[cpi_context_idx].clone(),
682 params,
683 };
684 cpi.invoke()
685}
686
687