use light_compressed_account::instruction_data::compressed_proof::ValidityProof;
use light_program_profiler::profile;
use light_token_interface::{
instructions::{
extensions::ExtensionInstructionData,
transfer2::{CompressedCpiContext, CompressedTokenInstructionDataTransfer2},
},
LIGHT_TOKEN_PROGRAM_ID, TRANSFER2,
};
use solana_instruction::Instruction;
use solana_pubkey::Pubkey;
use super::account_metas::{get_transfer2_instruction_account_metas, Transfer2AccountsMetaConfig};
use crate::{
compressed_token::CTokenAccount2,
error::{Result, TokenSdkError},
AnchorSerialize,
};
#[derive(Debug, Default, PartialEq, Copy, Clone)]
pub struct Transfer2Config {
pub cpi_context: Option<CompressedCpiContext>,
pub with_transaction_hash: bool,
pub sol_pool_pda: bool,
pub sol_decompression_recipient: Option<Pubkey>,
pub filter_zero_amount_outputs: bool,
pub max_top_up: u16,
}
impl Transfer2Config {
pub fn new() -> Self {
Default::default()
}
pub fn with_cpi_context(mut self, cpi_context: CompressedCpiContext) -> Self {
self.cpi_context = Some(cpi_context);
self
}
pub fn with_transaction_hash(mut self) -> Self {
self.with_transaction_hash = true;
self
}
pub fn with_sol_pool(mut self, sol_decompression_recipient: Pubkey) -> Self {
self.sol_pool_pda = true;
self.sol_decompression_recipient = Some(sol_decompression_recipient);
self
}
pub fn filter_zero_amount_outputs(mut self) -> Self {
self.filter_zero_amount_outputs = true;
self
}
pub fn with_max_top_up(mut self, max_top_up: u16) -> Self {
self.max_top_up = max_top_up;
self
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct Transfer2Inputs {
pub token_accounts: Vec<CTokenAccount2>,
pub validity_proof: ValidityProof,
pub transfer_config: Transfer2Config,
pub meta_config: Transfer2AccountsMetaConfig,
pub in_lamports: Option<Vec<u64>>,
pub out_lamports: Option<Vec<u64>>,
pub output_queue: u8,
pub in_tlv: Option<Vec<Vec<ExtensionInstructionData>>>,
}
#[profile]
pub fn create_transfer2_instruction(inputs: Transfer2Inputs) -> Result<Instruction> {
let Transfer2Inputs {
token_accounts,
validity_proof,
transfer_config,
meta_config,
in_lamports,
out_lamports,
output_queue,
in_tlv,
} = inputs;
let mut input_token_data_with_context = Vec::new();
let mut output_compressed_accounts = Vec::new();
let mut collected_compressions = Vec::new();
for token_account in token_accounts {
if let Some(compression) = token_account.compression() {
collected_compressions.push(*compression);
}
let (inputs, output) = token_account.into_inputs_and_outputs();
input_token_data_with_context.extend(inputs);
if !transfer_config.filter_zero_amount_outputs || output.amount > 0 {
output_compressed_accounts.push(output);
}
}
let instruction_data = CompressedTokenInstructionDataTransfer2 {
with_transaction_hash: transfer_config.with_transaction_hash,
with_lamports_change_account_merkle_tree_index: false, lamports_change_account_merkle_tree_index: 0,
lamports_change_account_owner_index: 0,
output_queue,
proof: validity_proof.into(),
in_token_data: input_token_data_with_context,
out_token_data: output_compressed_accounts,
in_lamports,
out_lamports,
in_tlv,
out_tlv: None, compressions: if collected_compressions.is_empty() {
None
} else {
Some(collected_compressions)
},
cpi_context: transfer_config.cpi_context,
max_top_up: transfer_config.max_top_up,
};
let serialized = instruction_data
.try_to_vec()
.map_err(|_| TokenSdkError::SerializationError)?;
let mut data = Vec::with_capacity(1 + serialized.len());
data.push(TRANSFER2);
data.extend(serialized);
let account_metas = get_transfer2_instruction_account_metas(meta_config);
Ok(Instruction {
program_id: Pubkey::from(LIGHT_TOKEN_PROGRAM_ID),
accounts: account_metas,
data,
})
}