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