light_token/compressed_token/v2/
decompress_full.rs1use light_compressed_account::compressed_account::PackedMerkleContext;
2use light_program_profiler::profile;
3use light_sdk::{
4 error::LightSdkError,
5 instruction::{AccountMetasVec, PackedAccounts, PackedStateTreeInfo, SystemAccountMetaConfig},
6};
7use light_token_interface::instructions::{
8 extensions::ExtensionInstructionData,
9 transfer2::{CompressedCpiContext, MultiInputTokenDataWithContext},
10};
11use solana_account_info::AccountInfo;
12use solana_instruction::{AccountMeta, Instruction};
13use solana_pubkey::Pubkey;
14
15use super::{
16 account2::CTokenAccount2,
17 transfer2::{
18 account_metas::Transfer2AccountsMetaConfig, create_transfer2_instruction, Transfer2Config,
19 Transfer2Inputs,
20 },
21};
22use crate::{
23 compat::TokenData, error::TokenSdkError, utils::TokenDefaultAccounts, AnchorDeserialize,
24 AnchorSerialize, ValidityProof,
25};
26
27#[derive(Debug, Clone, AnchorDeserialize, AnchorSerialize)]
30pub struct DecompressFullIndices {
31 pub source: MultiInputTokenDataWithContext, pub destination_index: u8, pub tlv: Option<Vec<ExtensionInstructionData>>,
36 pub is_ata: bool,
39}
40
41#[profile]
53pub fn decompress_full_token_accounts_with_indices<'info>(
54 fee_payer: Pubkey,
55 validity_proof: ValidityProof,
56 cpi_context_pubkey: Option<Pubkey>,
57 indices: &[DecompressFullIndices],
58 packed_accounts: &[AccountInfo<'info>],
59) -> Result<Instruction, TokenSdkError> {
60 if indices.is_empty() {
61 return Err(TokenSdkError::InvalidAccountData);
62 }
63
64 let mut token_accounts = Vec::with_capacity(indices.len());
66 let mut in_tlv_data: Vec<Vec<ExtensionInstructionData>> = Vec::with_capacity(indices.len());
67 let mut has_any_tlv = false;
68
69 let mut signer_flags = vec![false; packed_accounts.len()];
74
75 for idx in indices.iter() {
76 let mut token_account = CTokenAccount2::new(vec![idx.source])?;
79
80 token_account.decompress(idx.source.amount, idx.destination_index)?;
82 token_accounts.push(token_account);
83
84 if let Some(tlv) = &idx.tlv {
86 has_any_tlv = true;
87 in_tlv_data.push(tlv.clone());
88 } else {
89 in_tlv_data.push(Vec::new());
90 }
91
92 let owner_idx = idx.source.owner as usize;
93 if owner_idx >= signer_flags.len() {
94 return Err(TokenSdkError::InvalidAccountData);
95 }
96 if !idx.is_ata {
99 signer_flags[owner_idx] = true;
100 }
101 }
102
103 let mut packed_account_metas = Vec::with_capacity(packed_accounts.len());
104
105 for (i, info) in packed_accounts.iter().enumerate() {
106 packed_account_metas.push(AccountMeta {
107 pubkey: *info.key,
108 is_signer: info.is_signer || signer_flags[i],
109 is_writable: info.is_writable,
110 });
111 }
112 let (meta_config, transfer_config) = if let Some(cpi_context) = cpi_context_pubkey {
113 let cpi_context_config = CompressedCpiContext {
114 set_context: false,
115 first_set_context: false,
116 };
117
118 (
119 Transfer2AccountsMetaConfig {
120 fee_payer: Some(fee_payer),
121 cpi_context: Some(cpi_context),
122 decompressed_accounts_only: false,
123 sol_pool_pda: None,
124 sol_decompression_recipient: None,
125 with_sol_pool: false,
126 packed_accounts: Some(packed_account_metas),
127 },
128 Transfer2Config::default()
129 .filter_zero_amount_outputs()
130 .with_cpi_context(cpi_context_config),
131 )
132 } else {
133 (
134 Transfer2AccountsMetaConfig::new(fee_payer, packed_account_metas),
135 Transfer2Config::default().filter_zero_amount_outputs(),
136 )
137 };
138
139 let inputs = Transfer2Inputs {
141 meta_config,
142 token_accounts,
143 transfer_config,
144 validity_proof,
145 in_tlv: if has_any_tlv { Some(in_tlv_data) } else { None },
146 ..Default::default()
147 };
148
149 create_transfer2_instruction(inputs)
150}
151
152#[profile]
166pub fn pack_for_decompress_full(
167 token: &TokenData,
168 tree_info: &PackedStateTreeInfo,
169 destination: Pubkey,
170 packed_accounts: &mut PackedAccounts,
171 tlv: Option<Vec<ExtensionInstructionData>>,
172 version: u8,
173) -> DecompressFullIndices {
174 let source = MultiInputTokenDataWithContext {
175 owner: packed_accounts.insert_or_get_config(token.owner, true, false),
176 amount: token.amount,
177 has_delegate: token.delegate.is_some(),
178 delegate: token
179 .delegate
180 .map(|d| packed_accounts.insert_or_get(d))
181 .unwrap_or(0),
182 mint: packed_accounts.insert_or_get(token.mint),
183 version,
184 merkle_context: PackedMerkleContext {
185 merkle_tree_pubkey_index: tree_info.merkle_tree_pubkey_index,
186 queue_pubkey_index: tree_info.queue_pubkey_index,
187 prove_by_index: tree_info.prove_by_index,
188 leaf_index: tree_info.leaf_index,
189 },
190 root_index: tree_info.root_index,
191 };
192
193 DecompressFullIndices {
194 source,
195 destination_index: packed_accounts.insert_or_get(destination),
196 tlv,
197 is_ata: false, }
199}
200
201#[profile]
210pub fn pack_for_decompress_full_with_ata(
211 token: &TokenData,
212 tree_info: &PackedStateTreeInfo,
213 destination: Pubkey,
214 packed_accounts: &mut PackedAccounts,
215 tlv: Option<Vec<ExtensionInstructionData>>,
216 version: u8,
217 is_ata: bool,
218) -> DecompressFullIndices {
219 let owner_is_signer = !is_ata;
222
223 let source = MultiInputTokenDataWithContext {
224 owner: packed_accounts.insert_or_get_config(token.owner, owner_is_signer, false),
225 amount: token.amount,
226 has_delegate: token.delegate.is_some(),
227 delegate: token
228 .delegate
229 .map(|d| packed_accounts.insert_or_get(d))
230 .unwrap_or(0),
231 mint: packed_accounts.insert_or_get(token.mint),
232 version,
233 merkle_context: PackedMerkleContext {
234 merkle_tree_pubkey_index: tree_info.merkle_tree_pubkey_index,
235 queue_pubkey_index: tree_info.queue_pubkey_index,
236 prove_by_index: tree_info.prove_by_index,
237 leaf_index: tree_info.leaf_index,
238 },
239 root_index: tree_info.root_index,
240 };
241
242 DecompressFullIndices {
243 source,
244 destination_index: packed_accounts.insert_or_get(destination),
245 tlv,
246 is_ata,
247 }
248}
249
250pub struct DecompressFullAccounts {
251 pub compressed_token_program: Pubkey,
252 pub cpi_authority_pda: Pubkey,
253 pub cpi_context: Option<Pubkey>,
254 pub self_program: Option<Pubkey>,
255}
256
257impl DecompressFullAccounts {
258 pub fn new(cpi_context: Option<Pubkey>) -> Self {
259 Self {
260 compressed_token_program: TokenDefaultAccounts::default().compressed_token_program,
261 cpi_authority_pda: TokenDefaultAccounts::default().cpi_authority_pda,
262 cpi_context,
263 self_program: None,
264 }
265 }
266 pub fn new_with_cpi_context(cpi_context: Option<Pubkey>, self_program: Option<Pubkey>) -> Self {
267 Self {
268 compressed_token_program: TokenDefaultAccounts::default().compressed_token_program,
269 cpi_authority_pda: TokenDefaultAccounts::default().cpi_authority_pda,
270 cpi_context,
271 self_program,
272 }
273 }
274}
275
276impl AccountMetasVec for DecompressFullAccounts {
277 fn get_account_metas_vec(&self, accounts: &mut PackedAccounts) -> Result<(), LightSdkError> {
281 if !accounts.system_accounts_set() {
282 #[cfg(feature = "cpi-context")]
283 let config = {
284 let mut config = SystemAccountMetaConfig::default();
285 config.self_program = self.self_program;
286 config.cpi_context = self.cpi_context;
287 config
288 };
289 #[cfg(not(feature = "cpi-context"))]
290 let config = {
291 let mut config = SystemAccountMetaConfig::default();
292 config.self_program = self.self_program;
293 config
294 };
295
296 accounts.add_system_accounts_v2(config)?;
297 }
298 accounts.pre_accounts.extend_from_slice(&[
300 AccountMeta {
301 pubkey: self.compressed_token_program,
302 is_signer: false,
303 is_writable: false,
304 },
305 AccountMeta {
306 pubkey: self.cpi_authority_pda,
307 is_signer: false,
308 is_writable: false,
309 },
310 ]);
311 Ok(())
312 }
313}