use borsh::BorshSerialize;
use light_token_types::{
instructions::extensions::compressible::CompressibleExtensionInstructionData,
state::TokenDataVersion,
};
use solana_account_info::AccountInfo;
use solana_instruction::Instruction;
use solana_pubkey::Pubkey;
use crate::error::{LightTokenError, LightTokenResult};
const CREATE_ATA_DISCRIMINATOR: u8 = 100;
const CREATE_ATA_IDEMPOTENT_DISCRIMINATOR: u8 = 102;
#[derive(Debug, Clone)]
pub struct CreateCompressibleAssociatedTokenAccountInputs {
pub payer: Pubkey,
pub owner: Pubkey,
pub mint: Pubkey,
pub compressible_config: Pubkey,
pub rent_sponsor: Pubkey,
pub pre_pay_num_epochs: u8,
pub lamports_per_write: Option<u32>,
pub token_account_version: TokenDataVersion,
}
pub fn create_compressible_associated_token_account(
inputs: CreateCompressibleAssociatedTokenAccountInputs,
) -> LightTokenResult<Instruction> {
create_compressible_associated_token_account_with_mode::<false>(inputs)
}
pub fn create_compressible_associated_token_account_idempotent(
inputs: CreateCompressibleAssociatedTokenAccountInputs,
) -> LightTokenResult<Instruction> {
create_compressible_associated_token_account_with_mode::<true>(inputs)
}
pub fn create_compressible_associated_token_account_with_mode<const IDEMPOTENT: bool>(
inputs: CreateCompressibleAssociatedTokenAccountInputs,
) -> LightTokenResult<Instruction> {
let ata_pubkey = derive_associated_token_account(&inputs.owner, &inputs.mint);
create_ata_instruction_unified::<IDEMPOTENT, true>(
inputs.payer,
inputs.owner,
inputs.mint,
ata_pubkey,
Some((
inputs.pre_pay_num_epochs,
inputs.lamports_per_write,
inputs.rent_sponsor,
inputs.compressible_config,
inputs.token_account_version,
)),
)
}
pub fn create_associated_token_account(
payer: Pubkey,
owner: Pubkey,
mint: Pubkey,
) -> LightTokenResult<Instruction> {
create_associated_token_account_with_mode::<false>(payer, owner, mint)
}
pub fn create_associated_token_account_idempotent(
payer: Pubkey,
owner: Pubkey,
mint: Pubkey,
) -> LightTokenResult<Instruction> {
create_associated_token_account_with_mode::<true>(payer, owner, mint)
}
pub fn create_associated_token_account_with_mode<const IDEMPOTENT: bool>(
payer: Pubkey,
owner: Pubkey,
mint: Pubkey,
) -> LightTokenResult<Instruction> {
let ata_pubkey = derive_associated_token_account(&owner, &mint);
create_ata_instruction_unified::<IDEMPOTENT, false>(payer, owner, mint, ata_pubkey, None)
}
fn create_ata_instruction_unified<const IDEMPOTENT: bool, const COMPRESSIBLE: bool>(
payer: Pubkey,
owner: Pubkey,
mint: Pubkey,
ata_pubkey: Pubkey,
compressible_config: Option<(u8, Option<u32>, Pubkey, Pubkey, TokenDataVersion)>, ) -> LightTokenResult<Instruction> {
let discriminator = if IDEMPOTENT {
CREATE_ATA_IDEMPOTENT_DISCRIMINATOR
} else {
CREATE_ATA_DISCRIMINATOR
};
let compressible_extension = if COMPRESSIBLE {
if let Some((pre_pay_num_epochs, lamports_per_write, _, _, token_account_version)) =
compressible_config
{
Some(CompressibleExtensionInstructionData {
token_account_version: token_account_version as u8,
rent_payment: pre_pay_num_epochs,
compression_only: 0,
write_top_up: lamports_per_write.unwrap_or(0),
compress_to_account_pubkey: None, })
} else {
return Err(LightTokenError::InvalidAccountData);
}
} else {
None
};
let instruction_data =
light_token_interface::instructions::create_associated_token_account::CreateAssociatedTokenAccountInstructionData {
compressible_config: compressible_extension,
};
let mut data = Vec::new();
data.push(discriminator);
instruction_data
.serialize(&mut data)
.map_err(|_| LightTokenError::SerializationError)?;
let mut accounts = vec![
solana_instruction::AccountMeta::new(payer, true), solana_instruction::AccountMeta::new(ata_pubkey, false), solana_instruction::AccountMeta::new_readonly(Pubkey::new_from_array([0; 32]), false), ];
if COMPRESSIBLE {
if let Some((_, _, rent_sponsor, compressible_config_account, _)) = compressible_config {
accounts.push(solana_instruction::AccountMeta::new_readonly(
compressible_config_account,
false,
)); accounts.push(solana_instruction::AccountMeta::new(rent_sponsor, false));
}
}
Ok(Instruction {
program_id: Pubkey::from(light_token_types::COMPRESSED_TOKEN_PROGRAM_ID),
accounts,
data,
})
}
pub fn derive_associated_token_account(owner: &Pubkey, mint: &Pubkey) -> Pubkey {
Pubkey::find_program_address(
&[
owner.as_ref(),
light_token_types::COMPRESSED_TOKEN_PROGRAM_ID.as_ref(),
mint.as_ref(),
],
&Pubkey::from(light_token_types::COMPRESSED_TOKEN_PROGRAM_ID),
)
.0
}
#[allow(clippy::too_many_arguments)]
pub fn create_associated_token_account_cpi<'info>(
payer: AccountInfo<'info>,
associated_token_account: AccountInfo<'info>,
system_program: AccountInfo<'info>,
compressible_config: AccountInfo<'info>,
rent_sponsor: AccountInfo<'info>,
authority: AccountInfo<'info>,
mint: Pubkey,
pre_pay_num_epochs: Option<u8>,
lamports_per_write: Option<u32>,
) -> std::result::Result<(), solana_program_error::ProgramError> {
let inputs = CreateCompressibleAssociatedTokenAccountInputs {
payer: *payer.key,
owner: *authority.key,
mint,
compressible_config: *compressible_config.key,
rent_sponsor: *rent_sponsor.key,
pre_pay_num_epochs: pre_pay_num_epochs.unwrap_or(2),
lamports_per_write,
token_account_version: TokenDataVersion::ShaFlat,
};
let ix = create_compressible_associated_token_account(inputs)?;
solana_cpi::invoke(
&ix,
&[
payer,
associated_token_account,
system_program,
compressible_config,
rent_sponsor,
authority,
],
)
}
#[allow(clippy::too_many_arguments)]
pub fn create_associated_token_account_idempotent_cpi<'info>(
payer: AccountInfo<'info>,
associated_token_account: AccountInfo<'info>,
system_program: AccountInfo<'info>,
compressible_config: AccountInfo<'info>,
rent_sponsor: AccountInfo<'info>,
authority: Pubkey,
mint: Pubkey,
pre_pay_num_epochs: Option<u8>,
lamports_per_write: Option<u32>,
) -> std::result::Result<(), solana_program_error::ProgramError> {
let inputs = CreateCompressibleAssociatedTokenAccountInputs {
payer: *payer.key,
owner: authority,
mint,
compressible_config: *compressible_config.key,
rent_sponsor: *rent_sponsor.key,
pre_pay_num_epochs: pre_pay_num_epochs.unwrap_or(2),
lamports_per_write,
token_account_version: TokenDataVersion::ShaFlat,
};
let ix = create_compressible_associated_token_account_idempotent(inputs)?;
solana_cpi::invoke(
&ix,
&[
payer,
associated_token_account,
system_program,
compressible_config,
rent_sponsor,
],
)
}