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::utils::get_associated_token_address_and_bump;
20
21#[derive(Debug, Clone, PartialEq, Eq)]
50pub struct Decompress {
51 pub token_data: TokenData,
53 pub discriminator: [u8; 8],
55 pub merkle_tree: Pubkey,
57 pub queue: Pubkey,
59 pub leaf_index: u32,
61 pub root_index: u16,
63 pub destination: Pubkey,
65 pub payer: Pubkey,
67 pub signer: Pubkey,
69 pub validity_proof: ValidityProof,
71}
72
73impl Decompress {
74 pub fn instruction(self) -> Result<Instruction, ProgramError> {
75 let mut packed_accounts = PackedAccounts::default();
78
79 let merkle_tree_pubkey_index = packed_accounts.insert_or_get(self.merkle_tree);
81 let queue_pubkey_index = packed_accounts.insert_or_get(self.queue);
82
83 let prove_by_index = self.validity_proof.0.is_none();
86 let tree_info = PackedStateTreeInfo {
87 merkle_tree_pubkey_index,
88 queue_pubkey_index,
89 leaf_index: self.leaf_index,
90 root_index: self.root_index,
91 prove_by_index,
92 };
93 let version = TokenDataVersion::from_discriminator(self.discriminator)
95 .map_err(|_| ProgramError::InvalidAccountData)? as u8;
96
97 let is_ata = self.token_data.tlv.as_ref().is_some_and(|exts| {
99 exts.iter()
100 .any(|e| matches!(e, ExtensionStruct::CompressedOnly(co) if co.is_ata != 0))
101 });
102
103 let ata_bump = if is_ata {
106 let (_, bump) = get_associated_token_address_and_bump(
107 &self.signer,
108 &Pubkey::from(self.token_data.mint.to_bytes()),
109 );
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 as u8;
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 let amount: u64 = self.token_data.amount;
146 let indices = pack_for_decompress_full_with_ata(
147 &self.token_data.into(),
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(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}