use light_batched_merkle_tree::queue::BatchedQueueAccount;
use light_compressed_account::instruction_data::traits::LightInstructionData;
use light_compressed_token_sdk::compressed_token::mint_action::{
get_mint_action_instruction_account_metas_cpi_write, MintActionMetaConfig,
MintActionMetaConfigCpiWrite,
};
use light_token_interface::{
instructions::{
extensions::{ExtensionInstructionData, TokenMetadataInstructionData},
mint_action::{
Action, CpiContext, CreateMint, DecompressMintAction,
MintActionCompressedInstructionData, MintInstructionData,
},
},
state::MintMetadata,
LIGHT_TOKEN_PROGRAM_ID,
};
use solana_account_info::AccountInfo;
use solana_instruction::Instruction;
use solana_program_error::ProgramError;
use solana_pubkey::Pubkey;
use super::SystemAccountInfos;
pub const DEFAULT_RENT_PAYMENT: u8 = 16;
pub const DEFAULT_WRITE_TOP_UP: u32 = 766;
#[derive(Debug, Clone)]
pub struct SingleMintParams<'a> {
pub decimals: u8,
pub mint_authority: Pubkey,
pub mint_bump: Option<u8>,
pub freeze_authority: Option<Pubkey>,
pub mint_seed_pubkey: Pubkey,
pub authority_seeds: Option<&'a [&'a [u8]]>,
pub mint_signer_seeds: Option<&'a [&'a [u8]]>,
pub token_metadata: Option<&'a TokenMetadataInstructionData>,
}
#[derive(Debug, Clone)]
pub struct CreateMintsParams<'a> {
pub mints: &'a [SingleMintParams<'a>],
pub proof: light_compressed_account::instruction_data::compressed_proof::CompressedProof,
pub address_merkle_tree_root_index: u16,
pub rent_payment: u8,
pub write_top_up: u32,
pub cpi_context_offset: u8,
pub output_queue_index: u8,
pub address_tree_index: u8,
pub state_tree_index: u8,
}
impl<'a> CreateMintsParams<'a> {
#[inline(never)]
pub fn new(
mints: &'a [SingleMintParams<'a>],
proof: light_compressed_account::instruction_data::compressed_proof::CompressedProof,
address_merkle_tree_root_index: u16,
) -> Self {
Self {
mints,
proof,
address_merkle_tree_root_index,
rent_payment: DEFAULT_RENT_PAYMENT,
write_top_up: DEFAULT_WRITE_TOP_UP,
cpi_context_offset: 0,
output_queue_index: 0,
address_tree_index: 1,
state_tree_index: 2,
}
}
pub fn with_rent_payment(mut self, rent_payment: u8) -> Self {
self.rent_payment = rent_payment;
self
}
pub fn with_write_top_up(mut self, write_top_up: u32) -> Self {
self.write_top_up = write_top_up;
self
}
pub fn with_cpi_context_offset(mut self, offset: u8) -> Self {
self.cpi_context_offset = offset;
self
}
pub fn with_output_queue_index(mut self, index: u8) -> Self {
self.output_queue_index = index;
self
}
pub fn with_address_tree_index(mut self, index: u8) -> Self {
self.address_tree_index = index;
self
}
pub fn with_state_tree_index(mut self, index: u8) -> Self {
self.state_tree_index = index;
self
}
}
pub struct CreateMintsCpi<'a, 'info> {
pub mint_seed_accounts: &'a [AccountInfo<'info>],
pub payer: AccountInfo<'info>,
pub address_tree: AccountInfo<'info>,
pub output_queue: AccountInfo<'info>,
pub state_merkle_tree: AccountInfo<'info>,
pub compressible_config: AccountInfo<'info>,
pub mints: &'a [AccountInfo<'info>],
pub rent_sponsor: AccountInfo<'info>,
pub system_accounts: SystemAccountInfos<'info>,
pub cpi_context_account: AccountInfo<'info>,
pub params: CreateMintsParams<'a>,
}
impl<'a, 'info> CreateMintsCpi<'a, 'info> {
#[inline(never)]
pub fn validate(&self) -> Result<(), ProgramError> {
let n = self.params.mints.len();
if n == 0 {
return Err(ProgramError::InvalidArgument);
}
if self.mint_seed_accounts.len() != n {
return Err(ProgramError::InvalidArgument);
}
if self.mints.len() != n {
return Err(ProgramError::InvalidArgument);
}
Ok(())
}
#[inline(never)]
pub fn invoke(self) -> Result<(), ProgramError> {
self.validate()?;
let n = self.params.mints.len();
if n == 1 && self.params.cpi_context_offset == 0 {
self.invoke_single_mint()
} else {
self.invoke_multiple_mints()
}
}
#[inline(never)]
fn invoke_single_mint(self) -> Result<(), ProgramError> {
let mint_params = &self.params.mints[0];
let (mint, bump) = get_mint_and_bump(mint_params);
let mint_data =
build_mint_instruction_data(mint_params, self.mint_seed_accounts[0].key, mint, bump);
let decompress_action = DecompressMintAction {
rent_payment: self.params.rent_payment,
write_top_up: self.params.write_top_up,
};
let instruction_data = MintActionCompressedInstructionData::new_mint(
self.params.address_merkle_tree_root_index,
self.params.proof,
mint_data,
)
.with_decompress_mint(decompress_action);
let mut meta_config = MintActionMetaConfig::new_create_mint(
*self.payer.key,
*self.payer.key,
*self.mint_seed_accounts[0].key,
*self.address_tree.key,
*self.output_queue.key,
)
.with_compressible_mint(
*self.mints[0].key,
*self.compressible_config.key,
*self.rent_sponsor.key,
);
meta_config.input_queue = Some(*self.output_queue.key);
self.invoke_mint_action(instruction_data, meta_config, 0)
}
#[inline(never)]
fn invoke_multiple_mints(self) -> Result<(), ProgramError> {
let n = self.params.mints.len();
let base_leaf_index = get_base_leaf_index(&self.output_queue)?;
let decompress_action = DecompressMintAction {
rent_payment: self.params.rent_payment,
write_top_up: self.params.write_top_up,
};
for i in 0..(n - 1) {
self.invoke_cpi_write(i)?;
}
self.invoke_execute(n - 1, &decompress_action)?;
for i in 0..(n - 1) {
self.invoke_decompress(i, base_leaf_index, &decompress_action)?;
}
Ok(())
}
#[inline(never)]
fn invoke_cpi_write(&self, index: usize) -> Result<(), ProgramError> {
let mint_params = &self.params.mints[index];
let offset = self.params.cpi_context_offset;
let (mint, bump) = get_mint_and_bump(mint_params);
let cpi_context = CpiContext {
set_context: index > 0 || offset > 0,
first_set_context: index == 0 && offset == 0,
in_tree_index: self.params.address_tree_index,
in_queue_index: self.params.output_queue_index,
out_queue_index: self.params.output_queue_index,
token_out_queue_index: 0,
assigned_account_index: offset + index as u8,
read_only_address_trees: [0; 4],
address_tree_pubkey: self.address_tree.key.to_bytes(),
};
let mint_data = build_mint_instruction_data(
mint_params,
self.mint_seed_accounts[index].key,
mint,
bump,
);
let instruction_data = MintActionCompressedInstructionData::new_mint_write_to_cpi_context(
self.params.address_merkle_tree_root_index,
mint_data,
cpi_context,
);
let cpi_write_config = MintActionMetaConfigCpiWrite {
fee_payer: *self.payer.key,
mint_signer: Some(*self.mint_seed_accounts[index].key),
authority: *self.payer.key,
cpi_context: *self.cpi_context_account.key,
};
let account_metas = get_mint_action_instruction_account_metas_cpi_write(cpi_write_config);
let ix_data = instruction_data
.data()
.map_err(|e| ProgramError::BorshIoError(e.to_string()))?;
let account_infos = [
self.system_accounts.light_system_program.clone(),
self.mint_seed_accounts[index].clone(),
self.payer.clone(),
self.payer.clone(),
self.system_accounts.cpi_authority_pda.clone(),
self.cpi_context_account.clone(),
];
let instruction = Instruction {
program_id: Pubkey::new_from_array(LIGHT_TOKEN_PROGRAM_ID),
accounts: account_metas,
data: ix_data,
};
let mut seeds: [&[&[u8]]; 2] = [&[], &[]];
let mut num_signers = 0;
if let Some(s) = mint_params.mint_signer_seeds {
seeds[num_signers] = s;
num_signers += 1;
}
if let Some(s) = mint_params.authority_seeds {
seeds[num_signers] = s;
num_signers += 1;
}
solana_cpi::invoke_signed(&instruction, &account_infos, &seeds[..num_signers])
}
#[inline(never)]
fn invoke_execute(
&self,
last_idx: usize,
decompress_action: &DecompressMintAction,
) -> Result<(), ProgramError> {
let mint_params = &self.params.mints[last_idx];
let offset = self.params.cpi_context_offset;
let (mint, bump) = get_mint_and_bump(mint_params);
let mint_data = build_mint_instruction_data(
mint_params,
self.mint_seed_accounts[last_idx].key,
mint,
bump,
);
let instruction_data = MintActionCompressedInstructionData {
leaf_index: 0,
prove_by_index: false,
root_index: self.params.address_merkle_tree_root_index,
max_top_up: u16::MAX, create_mint: Some(CreateMint::default()),
actions: vec![Action::DecompressMint(*decompress_action)],
proof: Some(self.params.proof),
cpi_context: Some(CpiContext {
set_context: false,
first_set_context: false,
in_tree_index: self.params.address_tree_index,
in_queue_index: self.params.address_tree_index,
out_queue_index: self.params.output_queue_index,
token_out_queue_index: 0,
assigned_account_index: offset + last_idx as u8,
read_only_address_trees: [0; 4],
address_tree_pubkey: self.address_tree.key.to_bytes(),
}),
mint: Some(mint_data),
};
let mut meta_config = MintActionMetaConfig::new_create_mint(
*self.payer.key,
*self.payer.key,
*self.mint_seed_accounts[last_idx].key,
*self.address_tree.key,
*self.output_queue.key,
)
.with_compressible_mint(
*self.mints[last_idx].key,
*self.compressible_config.key,
*self.rent_sponsor.key,
);
meta_config.cpi_context = Some(*self.cpi_context_account.key);
meta_config.input_queue = Some(*self.output_queue.key);
self.invoke_mint_action(instruction_data, meta_config, last_idx)
}
#[inline(never)]
fn invoke_decompress(
&self,
index: usize,
base_leaf_index: u32,
decompress_action: &DecompressMintAction,
) -> Result<(), ProgramError> {
let mint_params = &self.params.mints[index];
let (mint, bump) = get_mint_and_bump(mint_params);
let mint_data = build_mint_instruction_data(
mint_params,
self.mint_seed_accounts[index].key,
mint,
bump,
);
let instruction_data = MintActionCompressedInstructionData {
leaf_index: base_leaf_index + index as u32,
prove_by_index: true,
root_index: 0,
max_top_up: u16::MAX, create_mint: None,
actions: vec![Action::DecompressMint(*decompress_action)],
proof: None,
cpi_context: None,
mint: Some(mint_data),
};
let meta_config = MintActionMetaConfig::new(
*self.payer.key,
*self.payer.key,
*self.state_merkle_tree.key, *self.output_queue.key, *self.output_queue.key, )
.with_compressible_mint(
*self.mints[index].key,
*self.compressible_config.key,
*self.rent_sponsor.key,
);
self.invoke_mint_action(instruction_data, meta_config, index)
}
#[inline(never)]
fn invoke_mint_action(
&self,
instruction_data: MintActionCompressedInstructionData,
meta_config: MintActionMetaConfig,
mint_index: usize,
) -> Result<(), ProgramError> {
let account_metas = meta_config.to_account_metas();
let ix_data = instruction_data
.data()
.map_err(|e| ProgramError::BorshIoError(e.to_string()))?;
let mut account_infos = vec![self.payer.clone()];
account_infos.push(self.system_accounts.light_system_program.clone());
for mint_seed in self.mint_seed_accounts {
account_infos.push(mint_seed.clone());
}
account_infos.push(self.system_accounts.cpi_authority_pda.clone());
account_infos.push(self.system_accounts.registered_program_pda.clone());
account_infos.push(self.system_accounts.account_compression_authority.clone());
account_infos.push(self.system_accounts.account_compression_program.clone());
account_infos.push(self.system_accounts.system_program.clone());
account_infos.push(self.cpi_context_account.clone());
account_infos.push(self.output_queue.clone());
account_infos.push(self.state_merkle_tree.clone());
account_infos.push(self.address_tree.clone());
account_infos.push(self.compressible_config.clone());
account_infos.push(self.rent_sponsor.clone());
for mint in self.mints {
account_infos.push(mint.clone());
}
let instruction = Instruction {
program_id: Pubkey::new_from_array(LIGHT_TOKEN_PROGRAM_ID),
accounts: account_metas,
data: ix_data,
};
let mint_params = &self.params.mints[mint_index];
let mut seeds: [&[&[u8]]; 2] = [&[], &[]];
let mut num_signers = 0;
if let Some(s) = mint_params.mint_signer_seeds {
seeds[num_signers] = s;
num_signers += 1;
}
if let Some(s) = mint_params.authority_seeds {
seeds[num_signers] = s;
num_signers += 1;
}
solana_cpi::invoke_signed(&instruction, &account_infos, &seeds[..num_signers])
}
}
#[inline(never)]
fn get_mint_and_bump(params: &SingleMintParams) -> (Pubkey, u8) {
let (mint, derived_bump) = super::find_mint_address(¶ms.mint_seed_pubkey);
let bump = params.mint_bump.unwrap_or(derived_bump);
(mint, bump)
}
#[inline(never)]
fn build_mint_instruction_data(
mint_params: &SingleMintParams<'_>,
mint_signer: &Pubkey,
mint: Pubkey,
bump: u8,
) -> MintInstructionData {
let extensions = mint_params
.token_metadata
.cloned()
.map(|metadata| vec![ExtensionInstructionData::TokenMetadata(metadata)]);
MintInstructionData {
supply: 0,
decimals: mint_params.decimals,
metadata: MintMetadata {
version: 3,
mint: mint.to_bytes().into(),
mint_decompressed: false,
mint_signer: mint_signer.to_bytes(),
bump,
},
mint_authority: Some(mint_params.mint_authority.to_bytes().into()),
freeze_authority: mint_params.freeze_authority.map(|a| a.to_bytes().into()),
extensions,
}
}
#[inline(never)]
fn get_base_leaf_index(output_queue: &AccountInfo) -> Result<u32, ProgramError> {
let queue = BatchedQueueAccount::output_from_account_info(output_queue)
.map_err(|_| ProgramError::InvalidAccountData)?;
Ok(queue.batch_metadata.next_index as u32)
}
#[inline(never)]
pub fn create_mints<'a, 'info>(
payer: &AccountInfo<'info>,
accounts: &'info [AccountInfo<'info>],
params: CreateMintsParams<'a>,
) -> Result<(), ProgramError> {
if params.mints.is_empty() {
return Err(ProgramError::InvalidArgument);
}
let n = params.mints.len();
let mint_signers_start = 1;
let cpi_authority_idx = n + 1;
let registered_program_idx = n + 2;
let compression_authority_idx = n + 3;
let compression_program_idx = n + 4;
let system_program_idx = n + 5;
let cpi_context_idx = n + 6;
let output_queue_idx = n + 7;
let state_merkle_tree_idx = n + 8;
let address_tree_idx = n + 9;
let compressible_config_idx = n + 10;
let rent_sponsor_idx = n + 11;
let mint_pdas_start = n + 12;
let cpi = CreateMintsCpi {
mint_seed_accounts: &accounts[mint_signers_start..mint_signers_start + n],
payer: payer.clone(),
address_tree: accounts[address_tree_idx].clone(),
output_queue: accounts[output_queue_idx].clone(),
state_merkle_tree: accounts[state_merkle_tree_idx].clone(),
compressible_config: accounts[compressible_config_idx].clone(),
mints: &accounts[mint_pdas_start..mint_pdas_start + n],
rent_sponsor: accounts[rent_sponsor_idx].clone(),
system_accounts: SystemAccountInfos {
light_system_program: accounts[0].clone(),
cpi_authority_pda: accounts[cpi_authority_idx].clone(),
registered_program_pda: accounts[registered_program_idx].clone(),
account_compression_authority: accounts[compression_authority_idx].clone(),
account_compression_program: accounts[compression_program_idx].clone(),
system_program: accounts[system_program_idx].clone(),
},
cpi_context_account: accounts[cpi_context_idx].clone(),
params,
};
cpi.invoke()
}