light_token/instruction/
decompress.rs1use light_account::PackedAccounts;
2use light_compressed_account::instruction_data::compressed_proof::ValidityProof;
3use light_compressed_token_sdk::compressed_token::{
4 decompress_full::pack_for_decompress_full_with_ata,
5 transfer2::{
6 create_transfer2_instruction, Transfer2AccountsMetaConfig, Transfer2Config, Transfer2Inputs,
7 },
8 CTokenAccount2,
9};
10use light_sdk::instruction::PackedStateTreeInfo;
11use light_token_interface::{
12 instructions::extensions::{CompressedOnlyExtensionInstructionData, ExtensionInstructionData},
13 state::{AccountState, ExtensionStruct, TokenData, TokenDataVersion},
14};
15use solana_instruction::Instruction;
16use solana_program_error::ProgramError;
17use solana_pubkey::Pubkey;
18
19use crate::{
20 instruction::derive_associated_token_account,
22};
23
24#[derive(Debug, Clone, PartialEq, Eq)]
53pub struct Decompress {
54 pub token_data: TokenData,
56 pub discriminator: [u8; 8],
58 pub merkle_tree: Pubkey,
60 pub queue: Pubkey,
62 pub leaf_index: u32,
64 pub root_index: u16,
66 pub destination: Pubkey,
68 pub payer: Pubkey,
70 pub signer: Pubkey,
72 pub validity_proof: ValidityProof,
74}
75
76impl Decompress {
77 pub fn instruction(self) -> Result<Instruction, ProgramError> {
78 let mut packed_accounts = PackedAccounts::default();
81
82 let merkle_tree_pubkey_index = packed_accounts.insert_or_get(self.merkle_tree);
84 let queue_pubkey_index = packed_accounts.insert_or_get(self.queue);
85
86 let prove_by_index = self.validity_proof.0.is_none();
89 let tree_info = PackedStateTreeInfo {
90 merkle_tree_pubkey_index,
91 queue_pubkey_index,
92 leaf_index: self.leaf_index,
93 root_index: self.root_index,
94 prove_by_index,
95 };
96 let version = TokenDataVersion::from_discriminator(self.discriminator)
98 .map_err(|_| ProgramError::InvalidAccountData)? as u8;
99
100 let is_ata = self.token_data.tlv.as_ref().is_some_and(|exts| {
102 exts.iter()
103 .any(|e| matches!(e, ExtensionStruct::CompressedOnly(co) if co.is_ata != 0))
104 });
105
106 let ata_bump = if is_ata {
109 let (_, bump) = derive_associated_token_account(
110 &self.signer,
111 &Pubkey::from(self.token_data.mint.to_bytes()),
112 );
113 bump
114 } else {
115 0
116 };
117
118 let owner_index = packed_accounts.insert_or_get_config(self.signer, true, false);
120
121 let is_frozen = self.token_data.state == AccountState::Frozen as u8;
123 let tlv: Option<Vec<ExtensionInstructionData>> =
124 self.token_data.tlv.as_ref().map(|extensions| {
125 extensions
126 .iter()
127 .filter_map(|ext| match ext {
128 ExtensionStruct::CompressedOnly(compressed_only) => {
129 Some(ExtensionInstructionData::CompressedOnly(
130 CompressedOnlyExtensionInstructionData {
131 delegated_amount: compressed_only.delegated_amount,
132 withheld_transfer_fee: compressed_only.withheld_transfer_fee,
133 is_frozen,
134 compression_index: 0,
135 is_ata: compressed_only.is_ata != 0,
136 bump: ata_bump,
137 owner_index,
138 },
139 ))
140 }
141 _ => None,
142 })
143 .collect()
144 });
145
146 let in_tlv = tlv.clone().map(|t| vec![t]);
148 let amount: u64 = self.token_data.amount;
149 let indices = pack_for_decompress_full_with_ata(
150 &self.token_data.into(),
151 &tree_info,
152 self.destination,
153 &mut packed_accounts,
154 tlv,
155 version,
156 is_ata,
157 );
158 let mut token_account = CTokenAccount2::new(vec![indices.source])
160 .map_err(|_| ProgramError::InvalidAccountData)?;
161 token_account
162 .decompress(amount, indices.destination_index)
163 .map_err(|_| ProgramError::InvalidAccountData)?;
164
165 let (packed_account_metas, _, _) = packed_accounts.to_account_metas();
167 let meta_config = Transfer2AccountsMetaConfig::new(self.payer, packed_account_metas);
168 let transfer_config = Transfer2Config::default().filter_zero_amount_outputs();
169
170 let inputs = Transfer2Inputs {
171 meta_config,
172 token_accounts: vec![token_account],
173 transfer_config,
174 validity_proof: self.validity_proof,
175 in_tlv,
176 ..Default::default()
177 };
178
179 create_transfer2_instruction(inputs).map_err(ProgramError::from)
180 }
181}