use crate::error::ErrorCode;
use anchor_lang::prelude::*;
use anchor_spl::{
token::{self, Token, TokenAccount},
token_2022::{
self,
spl_token_2022::{
self,
extension::{
transfer_fee::{TransferFeeConfig, MAX_FEE_BASIS_POINTS},
ExtensionType, StateWithExtensions,
},
},
},
token_interface::{
initialize_account3, spl_token_2022::extension::BaseStateWithExtensions,
InitializeAccount3, Mint,
},
};
use std::collections::HashSet;
const MINT_WHITELIST: [&'static str; 4] = [
"HVbpJAQGNpkgBaYBZQBR1t7yFdvaYVp2vCQQfKKEN4tM",
"Crn4x1Y2HUKko7ox2EZMT6N2t2ZyH7eKtwkBGVnhEq1g",
"FrBfWJ4qE5sCzKm3k3JaAtqZcXUh4LvJygDeketsrsH4",
"2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo",
];
pub fn transfer_from_user_to_pool_vault<'a>(
authority: AccountInfo<'a>,
from: AccountInfo<'a>,
to_vault: AccountInfo<'a>,
mint: AccountInfo<'a>,
token_program: AccountInfo<'a>,
amount: u64,
mint_decimals: u8,
) -> Result<()> {
if amount == 0 {
return Ok(());
}
token_2022::transfer_checked(
CpiContext::new(
token_program.to_account_info(),
token_2022::TransferChecked {
from,
to: to_vault,
authority,
mint,
},
),
amount,
mint_decimals,
)
}
pub fn transfer_from_pool_vault_to_user<'a>(
authority: AccountInfo<'a>,
from_vault: AccountInfo<'a>,
to: AccountInfo<'a>,
mint: AccountInfo<'a>,
token_program: AccountInfo<'a>,
amount: u64,
mint_decimals: u8,
signer_seeds: &[&[&[u8]]],
) -> Result<()> {
if amount == 0 {
return Ok(());
}
token_2022::transfer_checked(
CpiContext::new_with_signer(
token_program.to_account_info(),
token_2022::TransferChecked {
from: from_vault,
to,
authority,
mint,
},
signer_seeds,
),
amount,
mint_decimals,
)
}
pub fn token_mint_to<'a>(
authority: AccountInfo<'a>,
token_program: AccountInfo<'a>,
mint: AccountInfo<'a>,
destination: AccountInfo<'a>,
amount: u64,
signer_seeds: &[&[&[u8]]],
) -> Result<()> {
token_2022::mint_to(
CpiContext::new_with_signer(
token_program,
token_2022::MintTo {
to: destination,
authority,
mint,
},
signer_seeds,
),
amount,
)
}
pub fn token_burn<'a>(
authority: AccountInfo<'a>,
token_program: AccountInfo<'a>,
mint: AccountInfo<'a>,
from: AccountInfo<'a>,
amount: u64,
signer_seeds: &[&[&[u8]]],
) -> Result<()> {
token_2022::burn(
CpiContext::new_with_signer(
token_program.to_account_info(),
token_2022::Burn {
from,
authority,
mint,
},
signer_seeds,
),
amount,
)
}
pub fn get_transfer_inverse_fee(mint_info: &AccountInfo, post_fee_amount: u64) -> Result<u64> {
if *mint_info.owner == Token::id() {
return Ok(0);
}
if post_fee_amount == 0 {
return err!(ErrorCode::InvalidInput);
}
let mint_data = mint_info.try_borrow_data()?;
let mint = StateWithExtensions::<spl_token_2022::state::Mint>::unpack(&mint_data)?;
let fee = if let Ok(transfer_fee_config) = mint.get_extension::<TransferFeeConfig>() {
let epoch = Clock::get()?.epoch;
let transfer_fee = transfer_fee_config.get_epoch_fee(epoch);
if u16::from(transfer_fee.transfer_fee_basis_points) == MAX_FEE_BASIS_POINTS {
u64::from(transfer_fee.maximum_fee)
} else {
transfer_fee_config
.calculate_inverse_epoch_fee(epoch, post_fee_amount)
.unwrap()
}
} else {
0
};
Ok(fee)
}
pub fn get_transfer_fee(mint_info: &AccountInfo, pre_fee_amount: u64) -> Result<u64> {
if *mint_info.owner == Token::id() {
return Ok(0);
}
let mint_data = mint_info.try_borrow_data()?;
let mint = StateWithExtensions::<spl_token_2022::state::Mint>::unpack(&mint_data)?;
let fee = if let Ok(transfer_fee_config) = mint.get_extension::<TransferFeeConfig>() {
transfer_fee_config
.calculate_epoch_fee(Clock::get()?.epoch, pre_fee_amount)
.unwrap()
} else {
0
};
Ok(fee)
}
pub fn is_supported_mint(mint_account: &InterfaceAccount<Mint>) -> Result<bool> {
let mint_info = mint_account.to_account_info();
if *mint_info.owner == Token::id() {
return Ok(true);
}
let mint_whitelist: HashSet<&str> = MINT_WHITELIST.into_iter().collect();
if mint_whitelist.contains(mint_account.key().to_string().as_str()) {
return Ok(true);
}
let mint_data = mint_info.try_borrow_data()?;
let mint = StateWithExtensions::<spl_token_2022::state::Mint>::unpack(&mint_data)?;
let extensions = mint.get_extension_types()?;
for e in extensions {
if e != ExtensionType::TransferFeeConfig
&& e != ExtensionType::MetadataPointer
&& e != ExtensionType::TokenMetadata
{
return Ok(false);
}
}
Ok(true)
}
use solana_program::{
account_info::AccountInfo,
entrypoint::ProgramResult,
program::invoke,
pubkey::Pubkey,
};
pub fn custom_create_associated_token_account<'a>(
funding_account: &AccountInfo<'a>,
wallet_account: &AccountInfo<'a>,
token_mint: &AccountInfo<'a>,
ata: &AccountInfo<'a>,
associated_token_account: &AccountInfo<'a>,
token_program: &AccountInfo<'a>,
system_program: &AccountInfo<'a>,
) -> ProgramResult {
let ix = spl_associated_token_account::instruction::create_associated_token_account(
funding_account.key,
wallet_account.key,
token_mint.key,
token_program.key,
);
invoke(
&ix,
&[
funding_account.clone(),
associated_token_account.clone(),
wallet_account.clone(),
ata.clone(),
token_mint.clone(),
system_program.clone(),
token_program.clone(),
],
)
}
pub fn create_token_account<'a>(
authority: &AccountInfo<'a>,
payer: &AccountInfo<'a>,
token_account: &AccountInfo<'a>,
mint_account: &AccountInfo<'a>,
system_program: &AccountInfo<'a>,
token_program: &AccountInfo<'a>,
signer_seeds: &[&[&[u8]]],
) -> Result<()> {
let space = {
let mint_info = mint_account.to_account_info();
if *mint_info.owner == token_2022::Token2022::id() {
let mint_data = mint_info.try_borrow_data()?;
let mint_state =
StateWithExtensions::<spl_token_2022::state::Mint>::unpack(&mint_data)?;
let mint_extensions = mint_state.get_extension_types()?;
let required_extensions =
ExtensionType::get_required_init_account_extensions(&mint_extensions);
ExtensionType::try_calculate_account_len::<spl_token_2022::state::Account>(
&required_extensions,
)?
} else {
TokenAccount::LEN
}
};
let lamports = Rent::get()?.minimum_balance(space);
let cpi_accounts = anchor_lang::system_program::CreateAccount {
from: payer.to_account_info(),
to: token_account.to_account_info(),
};
let cpi_context = CpiContext::new(system_program.to_account_info(), cpi_accounts);
anchor_lang::system_program::create_account(
cpi_context.with_signer(signer_seeds),
lamports,
space as u64,
token_program.key,
)?;
initialize_account3(CpiContext::new(
token_program.to_account_info(),
InitializeAccount3 {
account: token_account.to_account_info(),
mint: mint_account.to_account_info(),
authority: authority.to_account_info(),
},
))
}