use light_account::PackedAccounts;
use light_compressed_account::instruction_data::compressed_proof::ValidityProof;
use light_compressed_token_sdk::compressed_token::{
decompress_full::pack_for_decompress_full_with_ata,
transfer2::{
create_transfer2_instruction, Transfer2AccountsMetaConfig, Transfer2Config, Transfer2Inputs,
},
CTokenAccount2,
};
use light_sdk::instruction::PackedStateTreeInfo;
use light_token_interface::{
instructions::extensions::{CompressedOnlyExtensionInstructionData, ExtensionInstructionData},
state::{AccountState, ExtensionStruct, TokenData, TokenDataVersion},
};
use solana_instruction::Instruction;
use solana_program_error::ProgramError;
use solana_pubkey::Pubkey;
use crate::utils::get_associated_token_address_and_bump;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Decompress {
pub token_data: TokenData,
pub discriminator: [u8; 8],
pub merkle_tree: Pubkey,
pub queue: Pubkey,
pub leaf_index: u32,
pub root_index: u16,
pub destination: Pubkey,
pub payer: Pubkey,
pub signer: Pubkey,
pub validity_proof: ValidityProof,
}
impl Decompress {
pub fn instruction(self) -> Result<Instruction, ProgramError> {
let mut packed_accounts = PackedAccounts::default();
let merkle_tree_pubkey_index = packed_accounts.insert_or_get(self.merkle_tree);
let queue_pubkey_index = packed_accounts.insert_or_get(self.queue);
let prove_by_index = self.validity_proof.0.is_none();
let tree_info = PackedStateTreeInfo {
merkle_tree_pubkey_index,
queue_pubkey_index,
leaf_index: self.leaf_index,
root_index: self.root_index,
prove_by_index,
};
let version = TokenDataVersion::from_discriminator(self.discriminator)
.map_err(|_| ProgramError::InvalidAccountData)? as u8;
let is_ata = self.token_data.tlv.as_ref().is_some_and(|exts| {
exts.iter()
.any(|e| matches!(e, ExtensionStruct::CompressedOnly(co) if co.is_ata != 0))
});
let ata_bump = if is_ata {
let (_, bump) = get_associated_token_address_and_bump(
&self.signer,
&Pubkey::from(self.token_data.mint.to_bytes()),
);
bump
} else {
0
};
let owner_index = packed_accounts.insert_or_get_config(self.signer, true, false);
let is_frozen = self.token_data.state == AccountState::Frozen as u8;
let tlv: Option<Vec<ExtensionInstructionData>> =
self.token_data.tlv.as_ref().map(|extensions| {
extensions
.iter()
.filter_map(|ext| match ext {
ExtensionStruct::CompressedOnly(compressed_only) => {
Some(ExtensionInstructionData::CompressedOnly(
CompressedOnlyExtensionInstructionData {
delegated_amount: compressed_only.delegated_amount,
withheld_transfer_fee: compressed_only.withheld_transfer_fee,
is_frozen,
compression_index: 0,
is_ata: compressed_only.is_ata != 0,
bump: ata_bump,
owner_index,
},
))
}
_ => None,
})
.collect()
});
let in_tlv = tlv.clone().map(|t| vec![t]);
let amount: u64 = self.token_data.amount;
let indices = pack_for_decompress_full_with_ata(
&self.token_data.into(),
&tree_info,
self.destination,
&mut packed_accounts,
tlv,
version,
is_ata,
);
let mut token_account = CTokenAccount2::new(vec![indices.source])
.map_err(|_| ProgramError::InvalidAccountData)?;
token_account
.decompress(amount, indices.destination_index)
.map_err(|_| ProgramError::InvalidAccountData)?;
let (packed_account_metas, _, _) = packed_accounts.to_account_metas();
let meta_config = Transfer2AccountsMetaConfig::new(self.payer, packed_account_metas);
let transfer_config = Transfer2Config::default().filter_zero_amount_outputs();
let inputs = Transfer2Inputs {
meta_config,
token_accounts: vec![token_account],
transfer_config,
validity_proof: self.validity_proof,
in_tlv,
..Default::default()
};
create_transfer2_instruction(inputs).map_err(ProgramError::from)
}
}