use light_compressed_token_sdk::utils::is_light_token_owner;
use solana_account_info::AccountInfo;
use solana_instruction::{AccountMeta, Instruction};
use solana_program_error::ProgramError;
use solana_pubkey::Pubkey;
use super::{
transfer_checked::TransferChecked, transfer_from_spl::TransferFromSpl,
transfer_to_spl::TransferToSpl,
};
use crate::error::LightTokenError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum TransferType {
LightToLight,
LightToSpl,
SplToLight,
SplToSpl,
}
fn determine_transfer_type(
source_owner: &Pubkey,
destination_owner: &Pubkey,
) -> Result<TransferType, ProgramError> {
let source_is_light = is_light_token_owner(source_owner)
.map_err(|_| ProgramError::Custom(LightTokenError::CannotDetermineAccountType.into()))?;
let dest_is_light = is_light_token_owner(destination_owner)
.map_err(|_| ProgramError::Custom(LightTokenError::CannotDetermineAccountType.into()))?;
match (source_is_light, dest_is_light) {
(true, true) => Ok(TransferType::LightToLight),
(true, false) => Ok(TransferType::LightToSpl),
(false, true) => Ok(TransferType::SplToLight),
(false, false) => {
if source_owner == destination_owner {
Ok(TransferType::SplToSpl)
} else {
Err(ProgramError::Custom(
LightTokenError::SplTokenProgramMismatch.into(),
))
}
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct SplInterface {
pub mint: Pubkey,
pub spl_token_program: Pubkey,
pub spl_interface_pda: Pubkey,
pub spl_interface_pda_bump: u8,
}
impl<'info> From<&SplInterfaceCpi<'info>> for SplInterface {
fn from(spl: &SplInterfaceCpi<'info>) -> Self {
Self {
mint: *spl.mint.key,
spl_token_program: *spl.spl_token_program.key,
spl_interface_pda: *spl.spl_interface_pda.key,
spl_interface_pda_bump: spl.spl_interface_pda_bump,
}
}
}
pub struct SplInterfaceCpi<'info> {
pub mint: AccountInfo<'info>,
pub spl_token_program: AccountInfo<'info>,
pub spl_interface_pda: AccountInfo<'info>,
pub spl_interface_pda_bump: u8,
}
pub struct TransferInterface {
pub source: Pubkey,
pub destination: Pubkey,
pub amount: u64,
pub decimals: u8,
pub authority: Pubkey,
pub payer: Pubkey,
pub mint: Pubkey,
pub spl_interface: Option<SplInterface>,
pub source_owner: Pubkey,
pub destination_owner: Pubkey,
}
impl TransferInterface {
pub fn instruction(self) -> Result<Instruction, ProgramError> {
match determine_transfer_type(&self.source_owner, &self.destination_owner)? {
TransferType::LightToLight => TransferChecked {
source: self.source,
mint: self.mint,
destination: self.destination,
amount: self.amount,
decimals: self.decimals,
authority: self.authority,
fee_payer: self.payer,
}
.instruction(),
TransferType::LightToSpl => {
let spl = self.spl_interface.ok_or_else(|| {
ProgramError::Custom(LightTokenError::SplInterfaceRequired.into())
})?;
TransferToSpl {
source: self.source,
destination_spl_token_account: self.destination,
amount: self.amount,
authority: self.authority,
mint: spl.mint,
payer: self.payer,
spl_interface_pda: spl.spl_interface_pda,
spl_interface_pda_bump: spl.spl_interface_pda_bump,
decimals: self.decimals,
spl_token_program: spl.spl_token_program,
}
.instruction()
}
TransferType::SplToLight => {
let spl = self.spl_interface.ok_or_else(|| {
ProgramError::Custom(LightTokenError::SplInterfaceRequired.into())
})?;
TransferFromSpl {
source_spl_token_account: self.source,
destination: self.destination,
amount: self.amount,
authority: self.authority,
mint: spl.mint,
payer: self.payer,
spl_interface_pda: spl.spl_interface_pda,
spl_interface_pda_bump: spl.spl_interface_pda_bump,
decimals: self.decimals,
spl_token_program: spl.spl_token_program,
}
.instruction()
}
TransferType::SplToSpl => {
let spl = self.spl_interface.ok_or_else(|| {
ProgramError::Custom(LightTokenError::SplInterfaceRequired.into())
})?;
let mut data = vec![12u8];
data.extend_from_slice(&self.amount.to_le_bytes());
data.push(self.decimals);
Ok(Instruction {
program_id: self.source_owner, accounts: vec![
AccountMeta::new(self.source, false),
AccountMeta::new_readonly(spl.mint, false),
AccountMeta::new(self.destination, false),
AccountMeta::new_readonly(self.authority, true),
],
data,
})
}
}
}
}
impl<'info> From<&TransferInterfaceCpi<'info>> for TransferInterface {
fn from(cpi: &TransferInterfaceCpi<'info>) -> Self {
Self {
source: *cpi.source_account.key,
destination: *cpi.destination_account.key,
amount: cpi.amount,
decimals: cpi.decimals,
authority: *cpi.authority.key,
payer: *cpi.payer.key,
mint: *cpi.mint.key,
spl_interface: cpi.spl_interface.as_ref().map(SplInterface::from),
source_owner: *cpi.source_account.owner,
destination_owner: *cpi.destination_account.owner,
}
}
}
pub struct TransferInterfaceCpi<'info> {
pub amount: u64,
pub decimals: u8,
pub source_account: AccountInfo<'info>,
pub destination_account: AccountInfo<'info>,
pub authority: AccountInfo<'info>,
pub payer: AccountInfo<'info>,
pub compressed_token_program_authority: AccountInfo<'info>,
pub mint: AccountInfo<'info>,
pub spl_interface: Option<SplInterfaceCpi<'info>>,
pub system_program: AccountInfo<'info>,
}
impl<'info> TransferInterfaceCpi<'info> {
#[allow(clippy::too_many_arguments)]
pub fn new(
amount: u64,
decimals: u8,
source_account: AccountInfo<'info>,
destination_account: AccountInfo<'info>,
authority: AccountInfo<'info>,
payer: AccountInfo<'info>,
compressed_token_program_authority: AccountInfo<'info>,
mint: AccountInfo<'info>,
system_program: AccountInfo<'info>,
) -> Self {
Self {
source_account,
destination_account,
authority,
amount,
decimals,
payer,
compressed_token_program_authority,
mint,
spl_interface: None,
system_program,
}
}
pub fn with_spl_interface(
mut self,
mint: Option<AccountInfo<'info>>,
spl_token_program: Option<AccountInfo<'info>>,
spl_interface_pda: Option<AccountInfo<'info>>,
spl_interface_pda_bump: Option<u8>,
) -> Result<Self, ProgramError> {
let mint =
mint.ok_or_else(|| ProgramError::Custom(LightTokenError::MissingMintAccount.into()))?;
let spl_token_program = spl_token_program
.ok_or_else(|| ProgramError::Custom(LightTokenError::MissingSplTokenProgram.into()))?;
let spl_interface_pda = spl_interface_pda
.ok_or_else(|| ProgramError::Custom(LightTokenError::MissingSplInterfacePda.into()))?;
let spl_interface_pda_bump = spl_interface_pda_bump.ok_or_else(|| {
ProgramError::Custom(LightTokenError::MissingSplInterfacePdaBump.into())
})?;
self.spl_interface = Some(SplInterfaceCpi {
mint,
spl_token_program,
spl_interface_pda,
spl_interface_pda_bump,
});
Ok(self)
}
pub fn instruction(&self) -> Result<Instruction, ProgramError> {
TransferInterface::from(self).instruction()
}
pub fn invoke(self) -> Result<(), ProgramError> {
use solana_cpi::invoke;
let transfer_type =
determine_transfer_type(self.source_account.owner, self.destination_account.owner)?;
let instruction = self.instruction()?;
match transfer_type {
TransferType::LightToLight => {
let account_infos = [
self.source_account,
self.mint,
self.destination_account,
self.authority,
self.system_program,
self.payer,
];
invoke(&instruction, &account_infos)
}
TransferType::LightToSpl => {
let config = self.spl_interface.ok_or_else(|| {
ProgramError::Custom(LightTokenError::SplInterfaceRequired.into())
})?;
let account_infos = [
self.compressed_token_program_authority,
self.payer,
config.mint,
self.source_account,
self.destination_account,
self.authority,
config.spl_interface_pda,
config.spl_token_program,
];
invoke(&instruction, &account_infos)
}
TransferType::SplToLight => {
let config = self.spl_interface.ok_or_else(|| {
ProgramError::Custom(LightTokenError::SplInterfaceRequired.into())
})?;
let account_infos = [
self.compressed_token_program_authority,
self.payer,
config.mint,
self.destination_account,
self.authority,
self.source_account,
config.spl_interface_pda,
config.spl_token_program,
self.system_program,
];
invoke(&instruction, &account_infos)
}
TransferType::SplToSpl => {
let config = self.spl_interface.ok_or_else(|| {
ProgramError::Custom(LightTokenError::SplInterfaceRequired.into())
})?;
let account_infos = [
self.source_account,
config.mint,
self.destination_account,
self.authority,
];
invoke(&instruction, &account_infos)
}
}
}
pub fn invoke_signed(self, signer_seeds: &[&[&[u8]]]) -> Result<(), ProgramError> {
use solana_cpi::invoke_signed;
let transfer_type =
determine_transfer_type(self.source_account.owner, self.destination_account.owner)?;
let instruction = self.instruction()?;
match transfer_type {
TransferType::LightToLight => {
let account_infos = [
self.source_account,
self.mint,
self.destination_account,
self.authority,
self.system_program,
self.payer,
];
invoke_signed(&instruction, &account_infos, signer_seeds)
}
TransferType::LightToSpl => {
let config = self.spl_interface.ok_or_else(|| {
ProgramError::Custom(LightTokenError::SplInterfaceRequired.into())
})?;
let account_infos = [
self.compressed_token_program_authority,
self.payer,
config.mint,
self.source_account,
self.destination_account,
self.authority,
config.spl_interface_pda,
config.spl_token_program,
];
invoke_signed(&instruction, &account_infos, signer_seeds)
}
TransferType::SplToLight => {
let config = self.spl_interface.ok_or_else(|| {
ProgramError::Custom(LightTokenError::SplInterfaceRequired.into())
})?;
let account_infos = [
self.compressed_token_program_authority,
self.payer,
config.mint,
self.destination_account,
self.authority,
self.source_account,
config.spl_interface_pda,
config.spl_token_program,
self.system_program,
];
invoke_signed(&instruction, &account_infos, signer_seeds)
}
TransferType::SplToSpl => {
let config = self.spl_interface.ok_or_else(|| {
ProgramError::Custom(LightTokenError::SplInterfaceRequired.into())
})?;
let account_infos = [
self.source_account,
config.mint,
self.destination_account,
self.authority,
];
invoke_signed(&instruction, &account_infos, signer_seeds)
}
}
}
}