light_token/instruction/
decompress.rs1use light_compressed_account::instruction_data::compressed_proof::ValidityProof;
2use light_sdk::instruction::{PackedAccounts, PackedStateTreeInfo};
3use light_token_interface::{
4 instructions::extensions::{CompressedOnlyExtensionInstructionData, ExtensionInstructionData},
5 state::{ExtensionStruct, TokenDataVersion},
6};
7use solana_instruction::Instruction;
8use solana_program_error::ProgramError;
9use solana_pubkey::Pubkey;
10
11use crate::{
12 compat::{AccountState, TokenData},
13 compressed_token::{
14 decompress_full::pack_for_decompress_full_with_ata,
15 transfer2::{
16 create_transfer2_instruction, Transfer2AccountsMetaConfig, Transfer2Config,
17 Transfer2Inputs,
18 },
19 CTokenAccount2,
20 },
21 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(&self.signer, &self.token_data.mint);
110 bump
111 } else {
112 0
113 };
114
115 let owner_index = packed_accounts.insert_or_get_config(self.signer, true, false);
117
118 let is_frozen = self.token_data.state == AccountState::Frozen;
120 let tlv: Option<Vec<ExtensionInstructionData>> =
121 self.token_data.tlv.as_ref().map(|extensions| {
122 extensions
123 .iter()
124 .filter_map(|ext| match ext {
125 ExtensionStruct::CompressedOnly(compressed_only) => {
126 Some(ExtensionInstructionData::CompressedOnly(
127 CompressedOnlyExtensionInstructionData {
128 delegated_amount: compressed_only.delegated_amount,
129 withheld_transfer_fee: compressed_only.withheld_transfer_fee,
130 is_frozen,
131 compression_index: 0,
132 is_ata: compressed_only.is_ata != 0,
133 bump: ata_bump,
134 owner_index,
135 },
136 ))
137 }
138 _ => None,
139 })
140 .collect()
141 });
142
143 let in_tlv = tlv.clone().map(|t| vec![t]);
145
146 let indices = pack_for_decompress_full_with_ata(
147 &self.token_data,
148 &tree_info,
149 self.destination,
150 &mut packed_accounts,
151 tlv,
152 version,
153 is_ata,
154 );
155 let mut token_account = CTokenAccount2::new(vec![indices.source])
157 .map_err(|_| ProgramError::InvalidAccountData)?;
158 token_account
159 .decompress(self.token_data.amount, indices.destination_index)
160 .map_err(|_| ProgramError::InvalidAccountData)?;
161
162 let (packed_account_metas, _, _) = packed_accounts.to_account_metas();
164 let meta_config = Transfer2AccountsMetaConfig::new(self.payer, packed_account_metas);
165 let transfer_config = Transfer2Config::default().filter_zero_amount_outputs();
166
167 let inputs = Transfer2Inputs {
168 meta_config,
169 token_accounts: vec![token_account],
170 transfer_config,
171 validity_proof: self.validity_proof,
172 in_tlv,
173 ..Default::default()
174 };
175
176 create_transfer2_instruction(inputs).map_err(ProgramError::from)
177 }
178}