1use borsh::BorshDeserialize;
4use light_compressed_account::instruction_data::{
5 data::InstructionDataInvoke, invoke_cpi::InstructionDataInvokeCpi,
6 with_account_info::InstructionDataInvokeCpiWithAccountInfo,
7 with_readonly::InstructionDataInvokeCpiWithReadOnly,
8};
9use solana_sdk::{instruction::AccountMeta, pubkey::Pubkey, system_program};
10
11use super::types::ParsedInstructionData;
12
13fn resolve_tree_and_queue_pubkeys(
16 accounts: &[AccountMeta],
17 merkle_tree_index: Option<u8>,
18 nullifier_queue_index: Option<u8>,
19) -> (Option<Pubkey>, Option<Pubkey>) {
20 let mut tree_pubkey = None;
21 let mut queue_pubkey = None;
22
23 let mut system_program_pos = None;
25 for (i, account) in accounts.iter().enumerate() {
26 if account.pubkey == system_program::ID {
27 system_program_pos = Some(i);
28 break;
29 }
30 }
31
32 if let Some(system_pos) = system_program_pos {
33 let tree_accounts_start = system_pos + 2;
35
36 if let Some(tree_idx) = merkle_tree_index {
37 let tree_account_pos = tree_accounts_start + tree_idx as usize;
38 if tree_account_pos < accounts.len() {
39 tree_pubkey = Some(accounts[tree_account_pos].pubkey);
40 }
41 }
42
43 if let Some(queue_idx) = nullifier_queue_index {
44 let queue_account_pos = tree_accounts_start + queue_idx as usize;
45 if queue_account_pos < accounts.len() {
46 queue_pubkey = Some(accounts[queue_account_pos].pubkey);
47 }
48 }
49 }
50
51 (tree_pubkey, queue_pubkey)
52}
53
54pub fn decode_instruction(
56 program_id: &Pubkey,
57 data: &[u8],
58 accounts: &[AccountMeta],
59) -> Option<ParsedInstructionData> {
60 match program_id.to_string().as_str() {
61 "SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7" => {
63 decode_light_system_instruction(data, accounts, program_id)
64 }
65
66 "ComputeBudget111111111111111111111111111111" => decode_compute_budget_instruction(data),
68
69 id if id == system_program::ID.to_string() => decode_system_instruction(data),
71
72 "compr6CUsB5m2jS4Y3831ztGSTnDpnKJTKS95d64XVq" => decode_compression_instruction(data),
74
75 "cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m" => decode_compressed_token_instruction(data),
77
78 _ => Some(ParsedInstructionData::Unknown {
79 program_name: get_program_name(program_id),
80 data_preview: bs58::encode(&data[..data.len().min(16)]).into_string(),
81 }),
82 }
83}
84
85fn decode_light_system_instruction(
87 data: &[u8],
88 accounts: &[AccountMeta],
89 program_id: &Pubkey,
90) -> Option<ParsedInstructionData> {
91 if data.is_empty() {
92 return None;
93 }
94
95 if data.len() < 8 {
97 return Some(ParsedInstructionData::LightSystemProgram {
98 instruction_type: "Invalid".to_string(),
99 compressed_accounts: None,
100 proof_info: None,
101 address_params: None,
102 fee_info: None,
103 input_account_data: None,
104 output_account_data: None,
105 });
106 }
107
108 let discriminator: [u8; 8] = data[0..8].try_into().unwrap();
110
111 let (
113 instruction_type,
114 compressed_accounts,
115 proof_info,
116 address_params,
117 fee_info,
118 input_account_data,
119 output_account_data,
120 ) = match discriminator {
121 [26, 16, 169, 7, 21, 202, 242, 25] => {
122 match parse_invoke_instruction(&data[8..], accounts) {
124 Ok(parsed) => parsed,
125 Err(_) => (
126 "Invoke (parse error)".to_string(),
127 None,
128 None,
129 None,
130 None,
131 None,
132 None,
133 ),
134 }
135 }
136 [49, 212, 191, 129, 39, 194, 43, 196] => {
137 match parse_invoke_cpi_instruction(&data[8..], accounts) {
139 Ok(parsed) => parsed,
140 Err(_) => (
141 "InvokeCpi (parse error)".to_string(),
142 None,
143 None,
144 None,
145 None,
146 None,
147 None,
148 ),
149 }
150 }
151 [86, 47, 163, 166, 21, 223, 92, 8] => {
152 match parse_invoke_cpi_readonly_instruction(&data[8..], accounts) {
154 Ok(parsed) => parsed,
155 Err(_) => (
156 "InvokeCpiWithReadOnly (parse error)".to_string(),
157 None,
158 None,
159 None,
160 None,
161 None,
162 None,
163 ),
164 }
165 }
166 [228, 34, 128, 84, 47, 139, 86, 240] => {
167 match parse_invoke_cpi_account_info_instruction(&data[8..], accounts, program_id) {
169 Ok(parsed) => parsed,
170 Err(_) => (
171 "InvokeCpiWithAccountInfo (parse error)".to_string(),
172 None,
173 None,
174 None,
175 None,
176 None,
177 None,
178 ),
179 }
180 }
181 _ => {
182 let discriminator_str = format!("{:?}", discriminator);
184 (
185 format!("Unknown({})", discriminator_str),
186 None,
187 None,
188 None,
189 None,
190 None,
191 None,
192 )
193 }
194 };
195
196 Some(ParsedInstructionData::LightSystemProgram {
197 instruction_type,
198 compressed_accounts,
199 proof_info,
200 address_params,
201 fee_info,
202 input_account_data,
203 output_account_data,
204 })
205}
206
207type InstructionParseResult = Result<
208 (
209 String,
210 Option<super::types::CompressedAccountSummary>,
211 Option<super::types::ProofSummary>,
212 Option<Vec<super::types::AddressParam>>,
213 Option<super::types::FeeSummary>,
214 Option<Vec<super::types::InputAccountData>>,
215 Option<Vec<super::types::OutputAccountData>>,
216 ),
217 Box<dyn std::error::Error>,
218>;
219
220fn parse_invoke_instruction(data: &[u8], accounts: &[AccountMeta]) -> InstructionParseResult {
222 if data.len() < 4 {
224 return Err("Instruction data too short for Anchor prefix".into());
225 }
226 let instruction_data = InstructionDataInvoke::try_from_slice(&data[4..])?;
227
228 let compressed_accounts = Some(super::types::CompressedAccountSummary {
229 input_accounts: instruction_data
230 .input_compressed_accounts_with_merkle_context
231 .len(),
232 output_accounts: instruction_data.output_compressed_accounts.len(),
233 lamports_change: instruction_data
234 .compress_or_decompress_lamports
235 .map(|l| l as i64),
236 });
237
238 let proof_info = instruction_data
239 .proof
240 .as_ref()
241 .map(|_| super::types::ProofSummary {
242 proof_type: "Validity".to_string(),
243 has_validity_proof: true,
244 });
245
246 let address_params = if !instruction_data.new_address_params.is_empty() {
248 Some(
249 instruction_data
250 .new_address_params
251 .iter()
252 .map(|param| {
253 let tree_idx = Some(param.address_merkle_tree_account_index);
254 let queue_idx = Some(param.address_queue_account_index);
255 let (tree_pubkey, queue_pubkey) =
256 resolve_tree_and_queue_pubkeys(accounts, tree_idx, queue_idx);
257
258 super::types::AddressParam {
259 seed: param.seed,
260 address_queue_index: queue_idx,
261 address_queue_pubkey: queue_pubkey,
262 merkle_tree_index: tree_idx,
263 address_merkle_tree_pubkey: tree_pubkey,
264 root_index: Some(param.address_merkle_tree_root_index),
265 derived_address: None,
266 assigned_account_index: super::types::AddressAssignment::V1,
267 }
268 })
269 .collect(),
270 )
271 } else {
272 None
273 };
274
275 let input_account_data = if !instruction_data
277 .input_compressed_accounts_with_merkle_context
278 .is_empty()
279 {
280 Some(
281 instruction_data
282 .input_compressed_accounts_with_merkle_context
283 .iter()
284 .map(|acc| {
285 let tree_idx = Some(acc.merkle_context.merkle_tree_pubkey_index);
286 let queue_idx = Some(acc.merkle_context.queue_pubkey_index);
287 let (tree_pubkey, queue_pubkey) =
288 resolve_tree_and_queue_pubkeys(accounts, tree_idx, queue_idx);
289
290 super::types::InputAccountData {
291 lamports: acc.compressed_account.lamports,
292 owner: Some(acc.compressed_account.owner.into()),
293 merkle_tree_index: tree_idx,
294 merkle_tree_pubkey: tree_pubkey,
295 queue_index: queue_idx,
296 queue_pubkey,
297 address: acc.compressed_account.address,
298 data_hash: if let Some(ref data) = acc.compressed_account.data {
299 data.data_hash.to_vec()
300 } else {
301 vec![]
302 },
303 discriminator: if let Some(ref data) = acc.compressed_account.data {
304 data.discriminator.to_vec()
305 } else {
306 vec![]
307 },
308 leaf_index: Some(acc.merkle_context.leaf_index),
309 root_index: Some(acc.root_index),
310 }
311 })
312 .collect(),
313 )
314 } else {
315 None
316 };
317
318 let output_account_data = if !instruction_data.output_compressed_accounts.is_empty() {
320 Some(
321 instruction_data
322 .output_compressed_accounts
323 .iter()
324 .map(|acc| {
325 let tree_idx = Some(acc.merkle_tree_index);
326 let (tree_pubkey, _queue_pubkey) =
327 resolve_tree_and_queue_pubkeys(accounts, tree_idx, None);
328
329 super::types::OutputAccountData {
330 lamports: acc.compressed_account.lamports,
331 data: acc.compressed_account.data.as_ref().map(|d| d.data.clone()),
332 owner: Some(acc.compressed_account.owner.into()),
333 merkle_tree_index: tree_idx,
334 merkle_tree_pubkey: tree_pubkey,
335 queue_index: None,
336 queue_pubkey: None,
337 address: acc.compressed_account.address,
338 data_hash: if let Some(ref data) = acc.compressed_account.data {
339 data.data_hash.to_vec()
340 } else {
341 vec![]
342 },
343 discriminator: if let Some(ref data) = acc.compressed_account.data {
344 data.discriminator.to_vec()
345 } else {
346 vec![]
347 },
348 }
349 })
350 .collect(),
351 )
352 } else {
353 None
354 };
355
356 let fee_info = instruction_data
357 .relay_fee
358 .map(|fee| super::types::FeeSummary {
359 relay_fee: Some(fee),
360 compression_fee: None,
361 });
362
363 Ok((
364 "Invoke".to_string(),
365 compressed_accounts,
366 proof_info,
367 address_params,
368 fee_info,
369 input_account_data,
370 output_account_data,
371 ))
372}
373
374fn parse_invoke_cpi_instruction(data: &[u8], accounts: &[AccountMeta]) -> InstructionParseResult {
376 if data.len() < 4 {
378 return Err("Instruction data too short for Anchor prefix".into());
379 }
380 let instruction_data = InstructionDataInvokeCpi::try_from_slice(&data[4..])?;
381
382 let compressed_accounts = Some(super::types::CompressedAccountSummary {
383 input_accounts: instruction_data
384 .input_compressed_accounts_with_merkle_context
385 .len(),
386 output_accounts: instruction_data.output_compressed_accounts.len(),
387 lamports_change: instruction_data
388 .compress_or_decompress_lamports
389 .map(|l| l as i64),
390 });
391
392 let proof_info = instruction_data
393 .proof
394 .as_ref()
395 .map(|_| super::types::ProofSummary {
396 proof_type: "Validity".to_string(),
397 has_validity_proof: true,
398 });
399
400 let address_params = if !instruction_data.new_address_params.is_empty() {
402 Some(
403 instruction_data
404 .new_address_params
405 .iter()
406 .map(|param| {
407 let tree_idx = Some(param.address_merkle_tree_account_index);
408 let queue_idx = Some(param.address_queue_account_index);
409 let (tree_pubkey, queue_pubkey) =
410 resolve_tree_and_queue_pubkeys(accounts, tree_idx, queue_idx);
411
412 super::types::AddressParam {
413 seed: param.seed,
414 address_queue_index: queue_idx,
415 address_queue_pubkey: queue_pubkey,
416 merkle_tree_index: tree_idx,
417 address_merkle_tree_pubkey: tree_pubkey,
418 root_index: Some(param.address_merkle_tree_root_index),
419 derived_address: None,
420 assigned_account_index: super::types::AddressAssignment::V1,
421 }
422 })
423 .collect(),
424 )
425 } else {
426 None
427 };
428
429 let input_account_data = if !instruction_data
431 .input_compressed_accounts_with_merkle_context
432 .is_empty()
433 {
434 Some(
435 instruction_data
436 .input_compressed_accounts_with_merkle_context
437 .iter()
438 .map(|acc| {
439 let tree_idx = Some(acc.merkle_context.merkle_tree_pubkey_index);
440 let queue_idx = Some(acc.merkle_context.queue_pubkey_index);
441 let (tree_pubkey, queue_pubkey) =
442 resolve_tree_and_queue_pubkeys(accounts, tree_idx, queue_idx);
443
444 super::types::InputAccountData {
445 lamports: acc.compressed_account.lamports,
446 owner: Some(acc.compressed_account.owner.into()),
447 merkle_tree_index: tree_idx,
448 merkle_tree_pubkey: tree_pubkey,
449 queue_index: queue_idx,
450 queue_pubkey,
451 address: acc.compressed_account.address,
452 data_hash: if let Some(ref data) = acc.compressed_account.data {
453 data.data_hash.to_vec()
454 } else {
455 vec![]
456 },
457 discriminator: if let Some(ref data) = acc.compressed_account.data {
458 data.discriminator.to_vec()
459 } else {
460 vec![]
461 },
462 leaf_index: Some(acc.merkle_context.leaf_index),
463 root_index: Some(acc.root_index),
464 }
465 })
466 .collect(),
467 )
468 } else {
469 None
470 };
471
472 let output_account_data = if !instruction_data.output_compressed_accounts.is_empty() {
474 Some(
475 instruction_data
476 .output_compressed_accounts
477 .iter()
478 .map(|acc| {
479 let tree_idx = Some(acc.merkle_tree_index);
480 let (tree_pubkey, _queue_pubkey) =
481 resolve_tree_and_queue_pubkeys(accounts, tree_idx, None);
482
483 super::types::OutputAccountData {
484 lamports: acc.compressed_account.lamports,
485 data: acc.compressed_account.data.as_ref().map(|d| d.data.clone()),
486 owner: Some(acc.compressed_account.owner.into()),
487 merkle_tree_index: tree_idx,
488 merkle_tree_pubkey: tree_pubkey,
489 queue_index: None,
490 queue_pubkey: None,
491 address: acc.compressed_account.address,
492 data_hash: if let Some(ref data) = acc.compressed_account.data {
493 data.data_hash.to_vec()
494 } else {
495 vec![]
496 },
497 discriminator: if let Some(ref data) = acc.compressed_account.data {
498 data.discriminator.to_vec()
499 } else {
500 vec![]
501 },
502 }
503 })
504 .collect(),
505 )
506 } else {
507 None
508 };
509
510 let fee_info = instruction_data
511 .relay_fee
512 .map(|fee| super::types::FeeSummary {
513 relay_fee: Some(fee),
514 compression_fee: None,
515 });
516
517 Ok((
518 "InvokeCpi".to_string(),
519 compressed_accounts,
520 proof_info,
521 address_params,
522 fee_info,
523 input_account_data,
524 output_account_data,
525 ))
526}
527
528fn parse_invoke_cpi_readonly_instruction(
530 data: &[u8],
531 accounts: &[AccountMeta],
532) -> InstructionParseResult {
533 let instruction_data = InstructionDataInvokeCpiWithReadOnly::try_from_slice(data)?;
534
535 let compressed_accounts = Some(super::types::CompressedAccountSummary {
536 input_accounts: instruction_data.input_compressed_accounts.len(),
537 output_accounts: instruction_data.output_compressed_accounts.len(),
538 lamports_change: if instruction_data.compress_or_decompress_lamports > 0 {
539 Some(instruction_data.compress_or_decompress_lamports as i64)
540 } else {
541 None
542 },
543 });
544
545 let proof_info = Some(super::types::ProofSummary {
546 proof_type: "Validity".to_string(),
547 has_validity_proof: true,
548 });
549
550 let mut address_params = Vec::new();
552
553 for param in &instruction_data.new_address_params {
555 let tree_idx = Some(param.address_merkle_tree_account_index);
556 let queue_idx = Some(param.address_queue_account_index);
557 let (tree_pubkey, queue_pubkey) =
558 resolve_tree_and_queue_pubkeys(accounts, tree_idx, queue_idx);
559
560 address_params.push(super::types::AddressParam {
561 seed: param.seed,
562 address_queue_index: queue_idx,
563 address_queue_pubkey: queue_pubkey,
564 merkle_tree_index: tree_idx,
565 address_merkle_tree_pubkey: tree_pubkey,
566 root_index: Some(param.address_merkle_tree_root_index),
567 derived_address: None,
568 assigned_account_index: if param.assigned_to_account {
569 super::types::AddressAssignment::AssignedIndex(param.assigned_account_index)
570 } else {
571 super::types::AddressAssignment::None
572 },
573 });
574 }
575
576 for readonly_addr in &instruction_data.read_only_addresses {
578 let tree_idx = Some(readonly_addr.address_merkle_tree_account_index);
579 let (tree_pubkey, _queue_pubkey) = resolve_tree_and_queue_pubkeys(accounts, tree_idx, None);
580
581 address_params.push(super::types::AddressParam {
582 seed: [0; 32], address_queue_index: None,
584 address_queue_pubkey: None,
585 merkle_tree_index: tree_idx,
586 address_merkle_tree_pubkey: tree_pubkey,
587 root_index: Some(readonly_addr.address_merkle_tree_root_index),
588 derived_address: Some(readonly_addr.address),
589 assigned_account_index: super::types::AddressAssignment::None,
590 });
591 }
592
593 let address_params = if !address_params.is_empty() {
594 Some(address_params)
595 } else {
596 None
597 };
598
599 let input_account_data = if !instruction_data.input_compressed_accounts.is_empty() {
601 Some(
602 instruction_data
603 .input_compressed_accounts
604 .iter()
605 .map(|acc| {
606 let tree_idx = Some(acc.merkle_context.merkle_tree_pubkey_index);
607 let queue_idx = Some(acc.merkle_context.queue_pubkey_index);
608 let (tree_pubkey, queue_pubkey) =
609 resolve_tree_and_queue_pubkeys(accounts, tree_idx, queue_idx);
610
611 super::types::InputAccountData {
612 lamports: acc.lamports,
613 owner: Some(instruction_data.invoking_program_id.into()), merkle_tree_index: tree_idx,
615 merkle_tree_pubkey: tree_pubkey,
616 queue_index: queue_idx,
617 queue_pubkey,
618 address: acc.address,
619 data_hash: acc.data_hash.to_vec(),
620 discriminator: acc.discriminator.to_vec(),
621 leaf_index: Some(acc.merkle_context.leaf_index),
622 root_index: Some(acc.root_index),
623 }
624 })
625 .collect(),
626 )
627 } else {
628 None
629 };
630
631 let output_account_data = if !instruction_data.output_compressed_accounts.is_empty() {
633 Some(
634 instruction_data
635 .output_compressed_accounts
636 .iter()
637 .map(|acc| {
638 let tree_idx = Some(acc.merkle_tree_index);
639 let (tree_pubkey, _queue_pubkey) =
640 resolve_tree_and_queue_pubkeys(accounts, tree_idx, None);
641
642 super::types::OutputAccountData {
643 lamports: acc.compressed_account.lamports,
644 data: acc.compressed_account.data.as_ref().map(|d| d.data.clone()),
645 owner: Some(instruction_data.invoking_program_id.into()), merkle_tree_index: tree_idx,
647 merkle_tree_pubkey: tree_pubkey,
648 queue_index: None,
649 queue_pubkey: None,
650 address: acc.compressed_account.address,
651 data_hash: if let Some(ref data) = acc.compressed_account.data {
652 data.data_hash.to_vec()
653 } else {
654 vec![]
655 },
656 discriminator: if let Some(ref data) = acc.compressed_account.data {
657 data.discriminator.to_vec()
658 } else {
659 vec![]
660 },
661 }
662 })
663 .collect(),
664 )
665 } else {
666 None
667 };
668
669 Ok((
670 "InvokeCpiWithReadOnly".to_string(),
671 compressed_accounts,
672 proof_info,
673 address_params,
674 None,
675 input_account_data,
676 output_account_data,
677 ))
678}
679
680fn parse_invoke_cpi_account_info_instruction(
682 data: &[u8],
683 accounts: &[AccountMeta],
684 program_id: &Pubkey,
685) -> InstructionParseResult {
686 let instruction_data = InstructionDataInvokeCpiWithAccountInfo::try_from_slice(data)?;
687
688 let input_accounts = instruction_data
689 .account_infos
690 .iter()
691 .filter(|a| a.input.is_some())
692 .count();
693 let output_accounts = instruction_data
694 .account_infos
695 .iter()
696 .filter(|a| a.output.is_some())
697 .count();
698
699 let compressed_accounts = Some(super::types::CompressedAccountSummary {
700 input_accounts,
701 output_accounts,
702 lamports_change: if instruction_data.compress_or_decompress_lamports > 0 {
703 Some(instruction_data.compress_or_decompress_lamports as i64)
704 } else {
705 None
706 },
707 });
708
709 let proof_info = Some(super::types::ProofSummary {
710 proof_type: "Validity".to_string(),
711 has_validity_proof: true,
712 });
713
714 let mut address_params = Vec::new();
716
717 for param in &instruction_data.new_address_params {
719 let tree_idx = Some(param.address_merkle_tree_account_index);
720 let queue_idx = Some(param.address_queue_account_index);
721 let (tree_pubkey, queue_pubkey) =
722 resolve_tree_and_queue_pubkeys(accounts, tree_idx, queue_idx);
723
724 address_params.push(super::types::AddressParam {
725 seed: param.seed,
726 address_queue_index: queue_idx,
727 address_queue_pubkey: queue_pubkey,
728 merkle_tree_index: tree_idx,
729 address_merkle_tree_pubkey: tree_pubkey,
730 root_index: Some(param.address_merkle_tree_root_index),
731 derived_address: None,
732 assigned_account_index: if param.assigned_to_account {
733 super::types::AddressAssignment::AssignedIndex(param.assigned_account_index)
734 } else {
735 super::types::AddressAssignment::None
736 },
737 });
738 }
739
740 for readonly_addr in &instruction_data.read_only_addresses {
742 let tree_idx = Some(readonly_addr.address_merkle_tree_account_index);
743 let (tree_pubkey, _queue_pubkey) = resolve_tree_and_queue_pubkeys(accounts, tree_idx, None);
744
745 address_params.push(super::types::AddressParam {
746 seed: [0; 32], address_queue_index: None,
748 address_queue_pubkey: None,
749 merkle_tree_index: tree_idx,
750 address_merkle_tree_pubkey: tree_pubkey,
751 root_index: Some(readonly_addr.address_merkle_tree_root_index),
752 derived_address: Some(readonly_addr.address),
753 assigned_account_index: super::types::AddressAssignment::None,
754 });
755 }
756
757 let address_params = if !address_params.is_empty() {
758 Some(address_params)
759 } else {
760 None
761 };
762
763 let input_account_data = {
765 let mut input_data = Vec::new();
766 for account_info in &instruction_data.account_infos {
767 if let Some(ref input) = account_info.input {
768 input_data.push(super::types::InputAccountData {
769 lamports: input.lamports,
770 owner: Some(*program_id), merkle_tree_index: None, merkle_tree_pubkey: None,
773 queue_index: None,
774 queue_pubkey: None,
775 address: account_info.address, data_hash: input.data_hash.to_vec(),
777 discriminator: input.discriminator.to_vec(),
778 leaf_index: Some(input.merkle_context.leaf_index),
779 root_index: Some(input.root_index),
780 });
781 }
782 }
783 if !input_data.is_empty() {
784 Some(input_data)
785 } else {
786 None
787 }
788 };
789
790 let output_account_data = {
792 let mut output_data = Vec::new();
793 for account_info in &instruction_data.account_infos {
794 if let Some(ref output) = account_info.output {
795 let tree_idx = Some(output.output_merkle_tree_index);
796 let (tree_pubkey, _queue_pubkey) =
797 resolve_tree_and_queue_pubkeys(accounts, tree_idx, None);
798
799 output_data.push(super::types::OutputAccountData {
800 lamports: output.lamports,
801 data: if !output.data.is_empty() {
802 Some(output.data.clone())
803 } else {
804 None
805 },
806 owner: Some(*program_id), merkle_tree_index: tree_idx,
808 merkle_tree_pubkey: tree_pubkey,
809 queue_index: None,
810 queue_pubkey: None,
811 address: account_info.address, data_hash: output.data_hash.to_vec(),
813 discriminator: output.discriminator.to_vec(),
814 });
815 }
816 }
817 if !output_data.is_empty() {
818 Some(output_data)
819 } else {
820 None
821 }
822 };
823
824 Ok((
825 "InvokeCpiWithAccountInfo".to_string(),
826 compressed_accounts,
827 proof_info,
828 address_params,
829 None,
830 input_account_data,
831 output_account_data,
832 ))
833}
834
835fn decode_compute_budget_instruction(data: &[u8]) -> Option<ParsedInstructionData> {
837 if data.len() < 4 {
838 return None;
839 }
840
841 let instruction_discriminator = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
842
843 match instruction_discriminator {
844 0 => {
845 if data.len() >= 12 {
847 let units = u32::from_le_bytes([data[4], data[5], data[6], data[7]]) as u64;
848 let _additional_fee =
849 u32::from_le_bytes([data[8], data[9], data[10], data[11]]) as u64;
850 Some(ParsedInstructionData::ComputeBudget {
851 instruction_type: "RequestUnitsDeprecated".to_string(),
852 value: Some(units),
853 })
854 } else {
855 None
856 }
857 }
858 1 => {
859 if data.len() >= 8 {
861 let bytes = u32::from_le_bytes([data[4], data[5], data[6], data[7]]) as u64;
862 Some(ParsedInstructionData::ComputeBudget {
863 instruction_type: "RequestHeapFrame".to_string(),
864 value: Some(bytes),
865 })
866 } else {
867 None
868 }
869 }
870 2 => {
871 if data.len() >= 8 {
873 let units = u32::from_le_bytes([data[4], data[5], data[6], data[7]]) as u64;
874 Some(ParsedInstructionData::ComputeBudget {
875 instruction_type: "SetComputeUnitLimit".to_string(),
876 value: Some(units),
877 })
878 } else {
879 None
880 }
881 }
882 3 => {
883 if data.len() >= 12 {
885 let price = u64::from_le_bytes([
886 data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11],
887 ]);
888 Some(ParsedInstructionData::ComputeBudget {
889 instruction_type: "SetComputeUnitPrice".to_string(),
890 value: Some(price),
891 })
892 } else {
893 None
894 }
895 }
896 _ => Some(ParsedInstructionData::ComputeBudget {
897 instruction_type: "Unknown".to_string(),
898 value: None,
899 }),
900 }
901}
902
903fn decode_system_instruction(data: &[u8]) -> Option<ParsedInstructionData> {
905 if data.len() < 4 {
906 return None;
907 }
908
909 let instruction_type = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
910
911 match instruction_type {
912 0 => {
913 if data.len() >= 52 {
915 let lamports = u64::from_le_bytes([
916 data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11],
917 ]);
918 let space = u64::from_le_bytes([
919 data[12], data[13], data[14], data[15], data[16], data[17], data[18], data[19],
920 ]);
921
922 Some(ParsedInstructionData::System {
923 instruction_type: "CreateAccount".to_string(),
924 lamports: Some(lamports),
925 space: Some(space),
926 new_account: None,
927 })
928 } else {
929 None
930 }
931 }
932 2 => {
933 if data.len() >= 12 {
935 let lamports = u64::from_le_bytes([
936 data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11],
937 ]);
938
939 Some(ParsedInstructionData::System {
940 instruction_type: "Transfer".to_string(),
941 lamports: Some(lamports),
942 space: None,
943 new_account: None,
944 })
945 } else {
946 None
947 }
948 }
949 8 => {
950 if data.len() >= 12 {
952 let space = u64::from_le_bytes([
953 data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11],
954 ]);
955
956 Some(ParsedInstructionData::System {
957 instruction_type: "Allocate".to_string(),
958 lamports: None,
959 space: Some(space),
960 new_account: None,
961 })
962 } else {
963 None
964 }
965 }
966 _ => Some(ParsedInstructionData::System {
967 instruction_type: "Unknown".to_string(),
968 lamports: None,
969 space: None,
970 new_account: None,
971 }),
972 }
973}
974
975fn decode_compression_instruction(data: &[u8]) -> Option<ParsedInstructionData> {
977 let instruction_name = if data.len() >= 8 {
979 "InsertIntoQueues"
981 } else {
982 "Unknown"
983 };
984
985 Some(ParsedInstructionData::Unknown {
986 program_name: "Account Compression".to_string(),
987 data_preview: format!("{}({}bytes)", instruction_name, data.len()),
988 })
989}
990
991fn decode_compressed_token_instruction(data: &[u8]) -> Option<ParsedInstructionData> {
993 let instruction_name = if data.len() >= 8 {
995 "TokenOperation"
997 } else {
998 "Unknown"
999 };
1000
1001 Some(ParsedInstructionData::Unknown {
1002 program_name: "Compressed Token".to_string(),
1003 data_preview: format!("{}({}bytes)", instruction_name, data.len()),
1004 })
1005}
1006
1007fn get_program_name(program_id: &Pubkey) -> String {
1009 match program_id.to_string().as_str() {
1010 id if id == system_program::ID.to_string() => "System Program".to_string(),
1011 "ComputeBudget111111111111111111111111111111" => "Compute Budget".to_string(),
1012 "SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7" => "Light System Program".to_string(),
1013 "compr6CUsB5m2jS4Y3831ztGSTnDpnKJTKS95d64XVq" => "Account Compression".to_string(),
1014 "FNt7byTHev1k5x2cXZLBr8TdWiC3zoP5vcnZR4P682Uy" => "Test Program".to_string(),
1015 _ => {
1016 let pubkey_str = program_id.to_string();
1017 format!("Program {}", &pubkey_str[..8])
1018 }
1019 }
1020}
1021
1022pub fn extract_light_events(
1024 logs: &[String],
1025 _events: &Option<Vec<String>>, ) -> Vec<super::types::LightProtocolEvent> {
1027 let mut light_events = Vec::new();
1028
1029 for log in logs {
1031 if log.contains("PublicTransactionEvent") || log.contains("BatchPublicTransactionEvent") {
1032 light_events.push(super::types::LightProtocolEvent {
1034 event_type: "PublicTransactionEvent".to_string(),
1035 compressed_accounts: Vec::new(),
1036 merkle_tree_changes: Vec::new(),
1037 nullifiers: Vec::new(),
1038 });
1039 }
1040 }
1041
1042 light_events
1043}