use anchor_lang::{
context::CpiContext,
error::ErrorCode,
solana_program::{
account_info::AccountInfo, msg, program_pack::IsInitialized, rent::Rent, sysvar::Sysvar,
},
system_program::create_account,
Result,
};
use anchor_spl::{
token_2022::InitializeAccount3,
token_interface::{
initialize_account3,
spl_token_2022::{
extension::{BaseStateWithExtensions, StateWithExtensions},
state::{Account, Mint},
},
},
};
use super::extension::{get_extension_types, IExtensionType};
pub struct InitializeTokenAccount<'a, 'b, 'info> {
pub token_info: &'a AccountInfo<'info>,
pub mint: &'a AccountInfo<'info>,
pub authority: &'a AccountInfo<'info>,
pub payer: &'a AccountInfo<'info>,
pub system_program: &'a AccountInfo<'info>,
pub token_program: &'a AccountInfo<'info>,
pub signer_seeds: &'a [&'b [u8]],
}
pub fn safe_initialize_token_account(
input: InitializeTokenAccount<'_, '_, '_>,
allow_existing: bool,
) -> Result<()> {
let mint_data = &input.mint.data.borrow();
let mint = StateWithExtensions::<Mint>::unpack(mint_data)?;
let extensions = IExtensionType::get_required_init_account_extensions(&get_extension_types(
mint.get_tlv_data(),
)?);
if input.token_info.owner == &anchor_lang::solana_program::system_program::ID
&& input.token_info.lamports() == 0
{
let required_length = IExtensionType::try_calculate_account_len::<Account>(&extensions)?;
let lamports = Rent::get()?.minimum_balance(required_length);
let cpi_accounts = anchor_lang::system_program::CreateAccount {
from: input.payer.clone(),
to: input.token_info.clone(),
};
let cpi_context = CpiContext::new(input.system_program.clone(), cpi_accounts);
create_account(
cpi_context.with_signer(&[input.signer_seeds]),
lamports,
required_length as u64,
input.token_program.key,
)?;
let cpi_ctx = anchor_lang::context::CpiContext::new(
input.token_program.clone(),
InitializeAccount3 {
account: input.token_info.clone(),
mint: input.mint.clone(),
authority: input.authority.clone(),
},
);
initialize_account3(cpi_ctx)?;
} else if allow_existing {
if input.token_info.owner != input.token_program.key {
msg!("Invalid token account owner");
return Err(ErrorCode::AccountOwnedByWrongProgram.into());
}
let token_data = &input.token_info.data.borrow();
let token = StateWithExtensions::<Account>::unpack(token_data)?;
if !token.base.is_initialized() {
msg!("Token account is not initialized");
return Err(ErrorCode::AccountNotInitialized.into());
}
if token.base.mint != *input.mint.key {
msg!("Invalid mint on token account");
return Err(ErrorCode::ConstraintTokenMint.into());
}
if token.base.owner != *input.authority.key {
msg!("Invalid token owner");
return Err(ErrorCode::ConstraintOwner.into());
}
} else {
msg!("Token account already exists (reinitialization not allowed)");
return Err(ErrorCode::ConstraintState.into());
}
Ok(())
}