1#[cfg(feature = "anchor")]
4use anchor_lang::{AnchorDeserialize, AnchorSerialize};
5#[cfg(not(feature = "anchor"))]
6use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize};
7use light_sdk::{
8 compressible::{compression_info::CompressedAccountData, config::LightConfig, Pack},
9 instruction::{
10 account_meta::CompressedAccountMetaNoLamportsNoAddress, PackedAccounts,
11 SystemAccountMetaConfig, ValidityProof,
12 },
13};
14use light_token::instruction::{
15 COMPRESSIBLE_CONFIG_V1, LIGHT_TOKEN_CPI_AUTHORITY, LIGHT_TOKEN_PROGRAM_ID, RENT_SPONSOR,
16};
17use solana_instruction::{AccountMeta, Instruction};
18use solana_pubkey::Pubkey;
19
20use crate::indexer::{CompressedAccount, TreeInfo, ValidityProofWithContext};
21
22#[inline]
23fn get_output_queue(tree_info: &TreeInfo) -> Pubkey {
24 tree_info
25 .next_tree_info
26 .as_ref()
27 .map(|next| next.queue)
28 .unwrap_or(tree_info.queue)
29}
30
31#[derive(AnchorSerialize, AnchorDeserialize)]
32pub struct InitializeConfigData {
33 pub rent_sponsor: Pubkey,
34 pub address_space: Vec<Pubkey>,
35 pub config_bump: u8,
36}
37
38#[derive(AnchorSerialize, AnchorDeserialize)]
39pub struct UpdateConfigData {
40 pub new_rent_sponsor: Option<Pubkey>,
41 pub new_address_space: Option<Vec<Pubkey>>,
42 pub new_update_authority: Option<Pubkey>,
43}
44
45#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)]
46pub struct LoadAccountsData<T> {
47 pub proof: ValidityProof,
48 pub compressed_accounts: Vec<CompressedAccountData<T>>,
49 pub system_accounts_offset: u8,
50}
51
52#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)]
53pub struct SaveAccountsData {
54 pub proof: ValidityProof,
55 pub compressed_accounts: Vec<CompressedAccountMetaNoLamportsNoAddress>,
56 pub system_accounts_offset: u8,
57}
58
59pub const INITIALIZE_COMPRESSION_CONFIG_DISCRIMINATOR: [u8; 8] =
61 [133, 228, 12, 169, 56, 76, 222, 61];
62pub const UPDATE_COMPRESSION_CONFIG_DISCRIMINATOR: [u8; 8] = [135, 215, 243, 81, 163, 146, 33, 70];
63pub const DECOMPRESS_ACCOUNTS_IDEMPOTENT_DISCRIMINATOR: [u8; 8] =
64 [114, 67, 61, 123, 234, 31, 1, 112];
65pub const COMPRESS_ACCOUNTS_IDEMPOTENT_DISCRIMINATOR: [u8; 8] =
66 [70, 236, 171, 120, 164, 93, 113, 181];
67
68pub mod load {
70 use super::*;
71
72 pub fn accounts(fee_payer: Pubkey, config: Pubkey, rent_sponsor: Pubkey) -> Vec<AccountMeta> {
74 vec![
75 AccountMeta::new(fee_payer, true),
76 AccountMeta::new_readonly(config, false),
77 AccountMeta::new(rent_sponsor, false),
78 AccountMeta::new(RENT_SPONSOR, false),
79 AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID, false),
80 AccountMeta::new_readonly(LIGHT_TOKEN_CPI_AUTHORITY, false),
81 AccountMeta::new_readonly(COMPRESSIBLE_CONFIG_V1, false),
82 ]
83 }
84
85 pub fn accounts_pda_only(
87 fee_payer: Pubkey,
88 config: Pubkey,
89 rent_sponsor: Pubkey,
90 ) -> Vec<AccountMeta> {
91 vec![
92 AccountMeta::new(fee_payer, true),
93 AccountMeta::new_readonly(config, false),
94 AccountMeta::new(rent_sponsor, false),
95 AccountMeta::new(rent_sponsor, false), AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID, false),
97 AccountMeta::new_readonly(LIGHT_TOKEN_CPI_AUTHORITY, false),
98 AccountMeta::new_readonly(COMPRESSIBLE_CONFIG_V1, false),
99 ]
100 }
101}
102
103#[allow(clippy::too_many_arguments)]
104pub fn initialize_config(
105 program_id: &Pubkey,
106 discriminator: &[u8],
107 payer: &Pubkey,
108 authority: &Pubkey,
109 rent_sponsor: Pubkey,
110 address_space: Vec<Pubkey>,
111 config_bump: Option<u8>,
112) -> Instruction {
113 let config_bump = config_bump.unwrap_or(0);
114 let (config_pda, _) = LightConfig::derive_pda(program_id, config_bump);
115
116 let bpf_loader = solana_pubkey::pubkey!("BPFLoaderUpgradeab1e11111111111111111111111");
117 let (program_data_pda, _) = Pubkey::find_program_address(&[program_id.as_ref()], &bpf_loader);
118
119 let system_program = solana_pubkey::pubkey!("11111111111111111111111111111111");
120 let accounts = vec![
121 AccountMeta::new(*payer, true),
122 AccountMeta::new(config_pda, false),
123 AccountMeta::new_readonly(program_data_pda, false),
124 AccountMeta::new_readonly(*authority, true),
125 AccountMeta::new_readonly(system_program, false),
126 ];
127
128 let ix_data = InitializeConfigData {
129 rent_sponsor,
130 address_space,
131 config_bump,
132 };
133
134 let serialized = ix_data.try_to_vec().expect("serialize");
135 let mut data = Vec::with_capacity(discriminator.len() + serialized.len());
136 data.extend_from_slice(discriminator);
137 data.extend_from_slice(&serialized);
138
139 Instruction {
140 program_id: *program_id,
141 accounts,
142 data,
143 }
144}
145
146pub fn update_config(
147 program_id: &Pubkey,
148 discriminator: &[u8],
149 authority: &Pubkey,
150 new_rent_sponsor: Option<Pubkey>,
151 new_address_space: Option<Vec<Pubkey>>,
152 new_update_authority: Option<Pubkey>,
153) -> Instruction {
154 let (config_pda, _) = LightConfig::derive_pda(program_id, 0);
155
156 let accounts = vec![
157 AccountMeta::new(config_pda, false),
158 AccountMeta::new_readonly(*authority, true),
159 ];
160
161 let ix_data = UpdateConfigData {
162 new_rent_sponsor,
163 new_address_space,
164 new_update_authority,
165 };
166
167 let serialized = ix_data.try_to_vec().expect("serialize");
168 let mut data = Vec::with_capacity(discriminator.len() + serialized.len());
169 data.extend_from_slice(discriminator);
170 data.extend_from_slice(&serialized);
171
172 Instruction {
173 program_id: *program_id,
174 accounts,
175 data,
176 }
177}
178
179#[allow(clippy::too_many_arguments)]
181pub fn create_decompress_accounts_idempotent_instruction<T>(
182 program_id: &Pubkey,
183 discriminator: &[u8],
184 hot_addresses: &[Pubkey],
185 cold_accounts: &[(CompressedAccount, T)],
186 program_account_metas: &[AccountMeta],
187 proof: ValidityProofWithContext,
188) -> Result<Instruction, Box<dyn std::error::Error>>
189where
190 T: Pack + Clone + std::fmt::Debug,
191{
192 if cold_accounts.is_empty() {
193 return Err("cold_accounts cannot be empty".into());
194 }
195 if hot_addresses.len() != cold_accounts.len() {
196 return Err("hot_addresses and cold_accounts must have same length".into());
197 }
198
199 let mut remaining_accounts = PackedAccounts::default();
200
201 let mut has_tokens = false;
202 let mut has_pdas = false;
203 for (acc, _) in cold_accounts.iter() {
204 if acc.owner == LIGHT_TOKEN_PROGRAM_ID {
205 has_tokens = true;
206 } else {
207 has_pdas = true;
208 }
209 if has_tokens && has_pdas {
210 break;
211 }
212 }
213 if !has_tokens && !has_pdas {
214 return Err("No tokens or PDAs found".into());
215 }
216
217 if has_pdas && has_tokens {
219 let first_token_acc = cold_accounts
220 .iter()
221 .find(|(acc, _)| acc.owner == LIGHT_TOKEN_PROGRAM_ID)
222 .ok_or("expected at least one token account when has_tokens is true")?;
223 let first_token_cpi = first_token_acc
224 .0
225 .tree_info
226 .cpi_context
227 .ok_or("missing cpi_context on token account")?;
228 let config = SystemAccountMetaConfig::new_with_cpi_context(*program_id, first_token_cpi);
229 remaining_accounts.add_system_accounts_v2(config)?;
230 } else {
231 remaining_accounts.add_system_accounts_v2(SystemAccountMetaConfig::new(*program_id))?;
232 }
233
234 let output_queue = get_output_queue(&cold_accounts[0].0.tree_info);
235 let output_state_tree_index = remaining_accounts.insert_or_get(output_queue);
236
237 let packed_tree_infos = proof.pack_tree_infos(&mut remaining_accounts);
238 let tree_infos = &packed_tree_infos
239 .state_trees
240 .as_ref()
241 .ok_or("missing state_trees in packed_tree_infos")?
242 .packed_tree_infos;
243
244 let mut accounts = program_account_metas.to_vec();
245 let mut typed_accounts = Vec::with_capacity(cold_accounts.len());
246
247 for (i, (acc, data)) in cold_accounts.iter().enumerate() {
248 let _queue_index = remaining_accounts.insert_or_get(acc.tree_info.queue);
249 let tree_info = tree_infos
250 .get(i)
251 .copied()
252 .ok_or("tree info index out of bounds")?;
253
254 let packed_data = data.pack(&mut remaining_accounts)?;
255 typed_accounts.push(CompressedAccountData {
256 meta: CompressedAccountMetaNoLamportsNoAddress {
257 tree_info,
258 output_state_tree_index,
259 },
260 data: packed_data,
261 });
262 }
263
264 let (system_accounts, system_accounts_offset, _) = remaining_accounts.to_account_metas();
265 accounts.extend(system_accounts);
266
267 for addr in hot_addresses {
268 accounts.push(AccountMeta::new(*addr, false));
269 }
270
271 let ix_data = LoadAccountsData {
272 proof: proof.proof,
273 compressed_accounts: typed_accounts,
274 system_accounts_offset: system_accounts_offset as u8,
275 };
276
277 let serialized = ix_data.try_to_vec()?;
278 let mut data = Vec::with_capacity(discriminator.len() + serialized.len());
279 data.extend_from_slice(discriminator);
280 data.extend_from_slice(&serialized);
281
282 Ok(Instruction {
283 program_id: *program_id,
284 accounts,
285 data,
286 })
287}
288
289pub fn build_compress_accounts_idempotent(
291 program_id: &Pubkey,
292 discriminator: &[u8],
293 account_pubkeys: &[Pubkey],
294 program_account_metas: &[AccountMeta],
295 proof: ValidityProofWithContext,
296) -> Result<Instruction, Box<dyn std::error::Error>> {
297 if proof.accounts.is_empty() {
298 return Err("proof.accounts cannot be empty".into());
299 }
300
301 let mut remaining_accounts = PackedAccounts::default();
302 remaining_accounts.add_system_accounts_v2(SystemAccountMetaConfig::new(*program_id))?;
303
304 let output_queue = get_output_queue(&proof.accounts[0].tree_info);
305 let output_state_tree_index = remaining_accounts.insert_or_get(output_queue);
306
307 let packed_tree_infos = proof.pack_tree_infos(&mut remaining_accounts);
308 let tree_infos = packed_tree_infos
309 .state_trees
310 .as_ref()
311 .ok_or("missing state_trees in packed_tree_infos")?;
312
313 let cold_metas: Vec<_> = tree_infos
314 .packed_tree_infos
315 .iter()
316 .map(|tree_info| CompressedAccountMetaNoLamportsNoAddress {
317 tree_info: *tree_info,
318 output_state_tree_index,
319 })
320 .collect();
321
322 let mut accounts = program_account_metas.to_vec();
323 let (system_accounts, system_accounts_offset, _) = remaining_accounts.to_account_metas();
324 accounts.extend(system_accounts);
325
326 for pubkey in account_pubkeys {
327 accounts.push(AccountMeta::new(*pubkey, false));
328 }
329
330 let ix_data = SaveAccountsData {
331 proof: proof.proof,
332 compressed_accounts: cold_metas,
333 system_accounts_offset: system_accounts_offset as u8,
334 };
335
336 let serialized = ix_data.try_to_vec()?;
337 let mut data = Vec::with_capacity(discriminator.len() + serialized.len());
338 data.extend_from_slice(discriminator);
339 data.extend_from_slice(&serialized);
340
341 Ok(Instruction {
342 program_id: *program_id,
343 accounts,
344 data,
345 })
346}