Skip to main content

kora_lib/transaction/
instruction_util.rs

1use std::collections::HashMap;
2
3use solana_message::compiled_instruction::CompiledInstruction;
4use solana_sdk::{
5    instruction::{AccountMeta, Instruction},
6    pubkey::Pubkey,
7};
8use solana_system_interface::{instruction::SystemInstruction, program::ID as SYSTEM_PROGRAM_ID};
9use solana_transaction_status_client_types::{UiInstruction, UiParsedInstruction};
10
11use crate::{
12    constant::instruction_indexes, error::KoraError, transaction::VersionedTransactionResolved,
13};
14
15// Instruction type that we support to parse from the transaction
16#[derive(Debug, Clone, PartialEq, Eq, Hash)]
17pub enum ParsedSystemInstructionType {
18    SystemTransfer,
19    SystemCreateAccount,
20    SystemWithdrawNonceAccount,
21    SystemAssign,
22    SystemAllocate,
23    SystemInitializeNonceAccount,
24    SystemAdvanceNonceAccount,
25    SystemAuthorizeNonceAccount,
26    // Note: SystemUpgradeNonceAccount not included - no authority parameter
27}
28
29// Instruction type that we support to parse from the transaction
30#[derive(Debug, Clone, PartialEq, Eq, Hash)]
31pub enum ParsedSystemInstructionData {
32    // Includes transfer and transfer with seed
33    SystemTransfer { lamports: u64, sender: Pubkey, receiver: Pubkey },
34    // Includes create account and create account with seed
35    SystemCreateAccount { lamports: u64, payer: Pubkey },
36    // Includes withdraw nonce account
37    SystemWithdrawNonceAccount { lamports: u64, nonce_authority: Pubkey, recipient: Pubkey },
38    // Includes assign and assign with seed
39    SystemAssign { authority: Pubkey },
40    // Includes allocate and allocate with seed
41    SystemAllocate { account: Pubkey },
42    // Initialize nonce account
43    SystemInitializeNonceAccount { nonce_account: Pubkey, nonce_authority: Pubkey },
44    // Advance nonce account
45    SystemAdvanceNonceAccount { nonce_account: Pubkey, nonce_authority: Pubkey },
46    // Authorize nonce account
47    SystemAuthorizeNonceAccount { nonce_account: Pubkey, nonce_authority: Pubkey },
48    // Note: SystemUpgradeNonceAccount not included - no authority parameter
49}
50
51#[derive(Debug, Clone, PartialEq, Eq, Hash)]
52pub enum ParsedSPLInstructionType {
53    SplTokenTransfer,
54    SplTokenBurn,
55    SplTokenCloseAccount,
56    SplTokenApprove,
57    SplTokenRevoke,
58    SplTokenSetAuthority,
59    SplTokenMintTo,
60    SplTokenInitializeMint,
61    SplTokenInitializeAccount,
62    SplTokenInitializeMultisig,
63    SplTokenFreezeAccount,
64    SplTokenThawAccount,
65}
66
67#[derive(Debug, Clone, PartialEq, Eq, Hash)]
68pub enum ParsedSPLInstructionData {
69    // Includes transfer and transfer with seed (both spl and spl 2022)
70    SplTokenTransfer {
71        amount: u64,
72        owner: Pubkey,
73        mint: Option<Pubkey>,
74        source_address: Pubkey,
75        destination_address: Pubkey,
76        is_2022: bool,
77    },
78    // Includes burn and burn with seed
79    SplTokenBurn {
80        owner: Pubkey,
81        is_2022: bool,
82    },
83    // Includes close account
84    SplTokenCloseAccount {
85        owner: Pubkey,
86        is_2022: bool,
87    },
88    // Includes approve and approve with seed
89    SplTokenApprove {
90        owner: Pubkey,
91        is_2022: bool,
92    },
93    // Revoke
94    SplTokenRevoke {
95        owner: Pubkey,
96        is_2022: bool,
97    },
98    // SetAuthority
99    SplTokenSetAuthority {
100        authority: Pubkey,
101        is_2022: bool,
102    },
103    // MintTo and MintToChecked
104    SplTokenMintTo {
105        mint_authority: Pubkey,
106        is_2022: bool,
107    },
108    // InitializeMint and InitializeMint2
109    SplTokenInitializeMint {
110        mint_authority: Pubkey,
111        is_2022: bool,
112    },
113    // InitializeAccount, InitializeAccount2, InitializeAccount3
114    SplTokenInitializeAccount {
115        owner: Pubkey,
116        is_2022: bool,
117    },
118    // InitializeMultisig and InitializeMultisig2
119    SplTokenInitializeMultisig {
120        signers: Vec<Pubkey>,
121        is_2022: bool,
122    },
123    // FreezeAccount
124    SplTokenFreezeAccount {
125        freeze_authority: Pubkey,
126        is_2022: bool,
127    },
128    // ThawAccount
129    SplTokenThawAccount {
130        freeze_authority: Pubkey,
131        is_2022: bool,
132    },
133}
134
135/// Macro to validate that an instruction has the required number of accounts
136/// Usage: validate_accounts!(instruction, min_count)
137macro_rules! validate_number_accounts {
138    ($instruction:expr, $min_count:expr) => {
139        if $instruction.accounts.len() < $min_count {
140            log::error!("Instruction {:?} has less than {} accounts", $instruction, $min_count);
141            return Err(KoraError::InvalidTransaction(format!(
142                "Instruction doesn't have the required number of accounts",
143            )));
144        }
145    };
146}
147
148/// Macro to parse system instructions with validation and account extraction
149/// Usage: parse_system_instruction!(parsed_instructions, instruction, validate_module, EnumVariant, DataVariant { fields })
150macro_rules! parse_system_instruction {
151    // Simple version: separate constant module path and enum variant names
152    ($parsed:ident, $ix:ident, $const_mod:ident, $enum_variant:ident, $data_variant:ident { $($field:ident: $account_path:expr),* $(,)? }) => {
153        validate_number_accounts!($ix, instruction_indexes::$const_mod::REQUIRED_NUMBER_OF_ACCOUNTS);
154        $parsed
155            .entry(ParsedSystemInstructionType::$enum_variant)
156            .or_default()
157            .push(ParsedSystemInstructionData::$data_variant {
158                $($field: $ix.accounts[$account_path].pubkey,)*
159            });
160    };
161    // Version with extra fields (like lamports) that come from instruction data
162    ($parsed:ident, $ix:ident, $const_mod:ident, $enum_variant:ident, $data_variant:ident { $($data_field:ident: $data_val:expr),* ; $($field:ident: $account_path:expr),* $(,)? }) => {
163        validate_number_accounts!($ix, instruction_indexes::$const_mod::REQUIRED_NUMBER_OF_ACCOUNTS);
164        $parsed
165            .entry(ParsedSystemInstructionType::$enum_variant)
166            .or_default()
167            .push(ParsedSystemInstructionData::$data_variant {
168                $($data_field: $data_val,)*
169                $($field: $ix.accounts[$account_path].pubkey,)*
170            });
171    };
172}
173
174pub struct IxUtils;
175
176pub const PARSED_DATA_FIELD_TYPE: &str = "type";
177pub const PARSED_DATA_FIELD_INFO: &str = "info";
178
179pub const PARSED_DATA_FIELD_SOURCE: &str = "source";
180pub const PARSED_DATA_FIELD_DESTINATION: &str = "destination";
181pub const PARSED_DATA_FIELD_OWNER: &str = "owner";
182
183pub const PARSED_DATA_FIELD_TRANSFER: &str = "transfer";
184pub const PARSED_DATA_FIELD_CREATE_ACCOUNT: &str = "createAccount";
185pub const PARSED_DATA_FIELD_ASSIGN: &str = "assign";
186pub const PARSED_DATA_FIELD_TRANSFER_WITH_SEED: &str = "transferWithSeed";
187pub const PARSED_DATA_FIELD_CREATE_ACCOUNT_WITH_SEED: &str = "createAccountWithSeed";
188pub const PARSED_DATA_FIELD_ASSIGN_WITH_SEED: &str = "assignWithSeed";
189pub const PARSED_DATA_FIELD_WITHDRAW_NONCE_ACCOUNT: &str = "withdrawFromNonce";
190pub const PARSED_DATA_FIELD_ALLOCATE: &str = "allocate";
191pub const PARSED_DATA_FIELD_ALLOCATE_WITH_SEED: &str = "allocateWithSeed";
192pub const PARSED_DATA_FIELD_INITIALIZE_NONCE_ACCOUNT: &str = "initializeNonce";
193pub const PARSED_DATA_FIELD_ADVANCE_NONCE_ACCOUNT: &str = "advanceNonce";
194pub const PARSED_DATA_FIELD_AUTHORIZE_NONCE_ACCOUNT: &str = "authorizeNonce";
195pub const PARSED_DATA_FIELD_BURN: &str = "burn";
196pub const PARSED_DATA_FIELD_BURN_CHECKED: &str = "burnChecked";
197pub const PARSED_DATA_FIELD_CLOSE_ACCOUNT: &str = "closeAccount";
198pub const PARSED_DATA_FIELD_TRANSFER_CHECKED: &str = "transferChecked";
199pub const PARSED_DATA_FIELD_APPROVE: &str = "approve";
200pub const PARSED_DATA_FIELD_APPROVE_CHECKED: &str = "approveChecked";
201
202pub const PARSED_DATA_FIELD_AMOUNT: &str = "amount";
203pub const PARSED_DATA_FIELD_LAMPORTS: &str = "lamports";
204pub const PARSED_DATA_FIELD_DECIMALS: &str = "decimals";
205pub const PARSED_DATA_FIELD_UI_AMOUNT: &str = "uiAmount";
206pub const PARSED_DATA_FIELD_UI_AMOUNT_STRING: &str = "uiAmountString";
207pub const PARSED_DATA_FIELD_TOKEN_AMOUNT: &str = "tokenAmount";
208pub const PARSED_DATA_FIELD_ACCOUNT: &str = "account";
209pub const PARSED_DATA_FIELD_NEW_ACCOUNT: &str = "newAccount";
210pub const PARSED_DATA_FIELD_AUTHORITY: &str = "authority";
211pub const PARSED_DATA_FIELD_MINT: &str = "mint";
212pub const PARSED_DATA_FIELD_SPACE: &str = "space";
213pub const PARSED_DATA_FIELD_DELEGATE: &str = "delegate";
214pub const PARSED_DATA_FIELD_BASE: &str = "base";
215pub const PARSED_DATA_FIELD_SEED: &str = "seed";
216pub const PARSED_DATA_FIELD_SOURCE_BASE: &str = "sourceBase";
217pub const PARSED_DATA_FIELD_SOURCE_SEED: &str = "sourceSeed";
218pub const PARSED_DATA_FIELD_SOURCE_OWNER: &str = "sourceOwner";
219pub const PARSED_DATA_FIELD_NONCE_ACCOUNT: &str = "nonceAccount";
220pub const PARSED_DATA_FIELD_RECIPIENT: &str = "recipient";
221pub const PARSED_DATA_FIELD_NONCE_AUTHORITY: &str = "nonceAuthority";
222pub const PARSED_DATA_FIELD_NEW_AUTHORITY: &str = "newAuthority";
223
224// SPL Token instruction type constants
225pub const PARSED_DATA_FIELD_REVOKE: &str = "revoke";
226pub const PARSED_DATA_FIELD_SET_AUTHORITY: &str = "setAuthority";
227pub const PARSED_DATA_FIELD_MINT_TO: &str = "mintTo";
228pub const PARSED_DATA_FIELD_MINT_TO_CHECKED: &str = "mintToChecked";
229pub const PARSED_DATA_FIELD_INITIALIZE_MINT: &str = "initializeMint";
230pub const PARSED_DATA_FIELD_INITIALIZE_MINT2: &str = "initializeMint2";
231pub const PARSED_DATA_FIELD_INITIALIZE_ACCOUNT: &str = "initializeAccount";
232pub const PARSED_DATA_FIELD_INITIALIZE_ACCOUNT2: &str = "initializeAccount2";
233pub const PARSED_DATA_FIELD_INITIALIZE_ACCOUNT3: &str = "initializeAccount3";
234pub const PARSED_DATA_FIELD_INITIALIZE_MULTISIG: &str = "initializeMultisig";
235pub const PARSED_DATA_FIELD_INITIALIZE_MULTISIG2: &str = "initializeMultisig2";
236pub const PARSED_DATA_FIELD_FREEZE_ACCOUNT: &str = "freezeAccount";
237pub const PARSED_DATA_FIELD_THAW_ACCOUNT: &str = "thawAccount";
238
239// Additional field names for new instructions
240pub const PARSED_DATA_FIELD_MINT_AUTHORITY: &str = "mintAuthority";
241pub const PARSED_DATA_FIELD_FREEZE_AUTHORITY: &str = "freezeAuthority";
242pub const PARSED_DATA_FIELD_AUTHORITY_TYPE: &str = "authorityType";
243pub const PARSED_DATA_FIELD_MULTISIG_ACCOUNT: &str = "multisig";
244pub const PARSED_DATA_FIELD_SIGNERS: &str = "signers";
245
246impl IxUtils {
247    /// Helper method to extract a field as a string from JSON with proper error handling
248    fn get_field_as_str<'a>(
249        info: &'a serde_json::Value,
250        field_name: &str,
251    ) -> Result<&'a str, KoraError> {
252        info.get(field_name)
253            .ok_or_else(|| {
254                KoraError::SerializationError(format!("Missing field '{}'", field_name))
255            })?
256            .as_str()
257            .ok_or_else(|| {
258                KoraError::SerializationError(format!("Field '{}' is not a string", field_name))
259            })
260    }
261
262    /// Helper method to extract a field as a Pubkey from JSON with proper error handling
263    fn get_field_as_pubkey(
264        info: &serde_json::Value,
265        field_name: &str,
266    ) -> Result<Pubkey, KoraError> {
267        let pubkey_str = Self::get_field_as_str(info, field_name)?;
268        pubkey_str.parse::<Pubkey>().map_err(|e| {
269            KoraError::SerializationError(format!(
270                "Field '{}' is not a valid pubkey: {}",
271                field_name, e
272            ))
273        })
274    }
275
276    /// Helper method to extract a field as u64 from JSON string with proper error handling
277    fn get_field_as_u64(info: &serde_json::Value, field_name: &str) -> Result<u64, KoraError> {
278        let value = info.get(field_name).ok_or_else(|| {
279            KoraError::SerializationError(format!("Missing field '{}'", field_name))
280        })?;
281
282        // Try as native JSON number first
283        if let Some(num) = value.as_u64() {
284            return Ok(num);
285        }
286
287        // Fall back to string parsing
288        if let Some(str_val) = value.as_str() {
289            return str_val.parse::<u64>().map_err(|e| {
290                KoraError::SerializationError(format!(
291                    "Field '{}' is not a valid u64: {}",
292                    field_name, e
293                ))
294            });
295        }
296
297        Err(KoraError::SerializationError(format!(
298            "Field '{}' is neither a number nor a string",
299            field_name
300        )))
301    }
302
303    /// Helper method to get account index from hashmap with proper error handling
304    fn get_account_index(
305        account_keys_hashmap: &HashMap<Pubkey, u8>,
306        pubkey: &Pubkey,
307    ) -> Result<u8, KoraError> {
308        account_keys_hashmap.get(pubkey).copied().ok_or_else(|| {
309            KoraError::SerializationError(format!("{} not found in account keys", pubkey))
310        })
311    }
312
313    pub fn build_account_keys_hashmap(account_keys: &[Pubkey]) -> HashMap<Pubkey, u8> {
314        account_keys.iter().enumerate().map(|(idx, key)| (*key, idx as u8)).collect()
315    }
316
317    pub fn get_account_key_if_present(ix: &Instruction, index: usize) -> Option<Pubkey> {
318        if ix.accounts.is_empty() {
319            return None;
320        }
321
322        if index >= ix.accounts.len() {
323            return None;
324        }
325
326        Some(ix.accounts[index].pubkey)
327    }
328
329    pub fn get_account_key_required(
330        account_keys: &[Pubkey],
331        index: usize,
332    ) -> Result<Pubkey, KoraError> {
333        account_keys.get(index).copied().ok_or_else(|| {
334            KoraError::SerializationError(format!("Account key at index {} not found", index))
335        })
336    }
337
338    pub fn build_default_compiled_instruction(program_id_index: u8) -> CompiledInstruction {
339        CompiledInstruction { program_id_index, accounts: vec![], data: vec![] }
340    }
341
342    pub fn uncompile_instructions(
343        instructions: &[CompiledInstruction],
344        account_keys: &[Pubkey],
345    ) -> Result<Vec<Instruction>, KoraError> {
346        instructions
347            .iter()
348            .map(|ix| {
349                let program_id =
350                    Self::get_account_key_required(account_keys, ix.program_id_index as usize)?;
351                let accounts: Result<Vec<AccountMeta>, KoraError> = ix
352                    .accounts
353                    .iter()
354                    .map(|idx| {
355                        Ok(AccountMeta {
356                            pubkey: Self::get_account_key_required(account_keys, *idx as usize)?,
357                            is_signer: false,
358                            is_writable: true,
359                        })
360                    })
361                    .collect();
362
363                Ok(Instruction { program_id, accounts: accounts?, data: ix.data.clone() })
364            })
365            .collect()
366    }
367
368    /// Reconstruct a CompiledInstruction from various UiInstruction formats
369    ///
370    /// This is required because when you simulate a transaction with inner instructions flag,
371    /// the RPC pre-parses some of the instructions (like for SPL program and System Program),
372    /// however this is an issue for Kora, as we expected "Compiled" instructions rather than "Parsed" instructions,
373    /// because we have our own parsing logic on our Kora's side.
374    ///
375    /// So we need to reconstruct the "Compiled" instructions from the "Parsed" instructions, by "unparsing" the "Parsed" instructions.
376    ///
377    /// There's no known way to force the RPC to not parsed the instructions, so we need this "hack" to reverse the process.
378    ///
379    /// Example: https://github.com/anza-xyz/agave/blob/68032b576dc4c14b31c15974c6734ae1513980a3/transaction-status/src/parse_system.rs#L11
380    pub fn reconstruct_instruction_from_ui(
381        ui_instruction: &UiInstruction,
382        all_account_keys: &[Pubkey],
383    ) -> Option<CompiledInstruction> {
384        match ui_instruction {
385            UiInstruction::Compiled(compiled) => {
386                // Already compiled, decode data and return
387                Some(CompiledInstruction {
388                    program_id_index: compiled.program_id_index,
389                    accounts: compiled.accounts.clone(),
390                    data: bs58::decode(&compiled.data).into_vec().unwrap_or_default(),
391                })
392            }
393            UiInstruction::Parsed(ui_parsed) => match ui_parsed {
394                UiParsedInstruction::Parsed(parsed) => {
395                    let account_keys_hashmap = Self::build_account_keys_hashmap(all_account_keys);
396                    // Reconstruct based on program type
397                    if parsed.program_id == SYSTEM_PROGRAM_ID.to_string() {
398                        Self::reconstruct_system_instruction(parsed, &account_keys_hashmap).ok()
399                    } else if parsed.program == spl_token_interface::ID.to_string()
400                        || parsed.program == spl_token_2022_interface::ID.to_string()
401                    {
402                        Self::reconstruct_spl_token_instruction(parsed, &account_keys_hashmap).ok()
403                    } else {
404                        // For unsupported programs, create a stub instruction with just the program ID
405                        // This ensures the program ID is preserved for security validation
406                        let program_id = parsed.program_id.parse::<Pubkey>().ok()?;
407                        let program_id_index = *account_keys_hashmap.get(&program_id)?;
408
409                        Some(Self::build_default_compiled_instruction(program_id_index))
410                    }
411                }
412                UiParsedInstruction::PartiallyDecoded(partial) => {
413                    let account_keys_hashmap = Self::build_account_keys_hashmap(all_account_keys);
414                    if let Ok(program_id) = partial.program_id.parse::<Pubkey>() {
415                        if let Some(program_idx) = account_keys_hashmap.get(&program_id) {
416                            // Convert account addresses to indices
417                            let account_indices: Vec<u8> = partial
418                                .accounts
419                                .iter()
420                                .filter_map(|addr_str| {
421                                    addr_str
422                                        .parse::<Pubkey>()
423                                        .ok()
424                                        .and_then(|pubkey| account_keys_hashmap.get(&pubkey))
425                                        .copied()
426                                })
427                                .collect();
428
429                            return Some(CompiledInstruction {
430                                program_id_index: *program_idx,
431                                accounts: account_indices,
432                                data: bs58::decode(&partial.data).into_vec().unwrap_or_default(),
433                            });
434                        }
435                    }
436
437                    log::error!("Failed to reconstruct partially decoded instruction");
438                    None
439                }
440            },
441        }
442    }
443
444    /// Reconstruct system program instructions from parsed format
445    fn reconstruct_system_instruction(
446        parsed: &solana_transaction_status_client_types::ParsedInstruction,
447        account_keys_hashmap: &HashMap<Pubkey, u8>,
448    ) -> Result<CompiledInstruction, KoraError> {
449        let program_id_index = Self::get_account_index(account_keys_hashmap, &SYSTEM_PROGRAM_ID)?;
450
451        let parsed_data = &parsed.parsed;
452        let instruction_type = Self::get_field_as_str(parsed_data, PARSED_DATA_FIELD_TYPE)?;
453        let info = parsed_data
454            .get(PARSED_DATA_FIELD_INFO)
455            .ok_or_else(|| KoraError::SerializationError("Missing 'info' field".to_string()))?;
456
457        match instruction_type {
458            PARSED_DATA_FIELD_TRANSFER => {
459                let source = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_SOURCE)?;
460                let destination = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_DESTINATION)?;
461                let lamports = Self::get_field_as_u64(info, PARSED_DATA_FIELD_LAMPORTS)?;
462
463                let source_idx = Self::get_account_index(account_keys_hashmap, &source)?;
464                let destination_idx = Self::get_account_index(account_keys_hashmap, &destination)?;
465
466                let transfer_ix = SystemInstruction::Transfer { lamports };
467                let data = bincode::serialize(&transfer_ix).map_err(|e| {
468                    KoraError::SerializationError(format!(
469                        "Failed to serialize Transfer instruction: {}",
470                        e
471                    ))
472                })?;
473
474                Ok(CompiledInstruction {
475                    program_id_index,
476                    accounts: vec![source_idx, destination_idx],
477                    data,
478                })
479            }
480            PARSED_DATA_FIELD_CREATE_ACCOUNT => {
481                let source = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_SOURCE)?;
482                let new_account = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_NEW_ACCOUNT)?;
483                let owner = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_OWNER)?;
484                let lamports = Self::get_field_as_u64(info, PARSED_DATA_FIELD_LAMPORTS)?;
485                let space = Self::get_field_as_u64(info, PARSED_DATA_FIELD_SPACE)?;
486
487                let source_idx = Self::get_account_index(account_keys_hashmap, &source)?;
488                let new_account_idx = Self::get_account_index(account_keys_hashmap, &new_account)?;
489
490                let create_ix = SystemInstruction::CreateAccount { lamports, space, owner };
491                let data = bincode::serialize(&create_ix).map_err(|e| {
492                    KoraError::SerializationError(format!(
493                        "Failed to serialize CreateAccount instruction: {}",
494                        e
495                    ))
496                })?;
497
498                Ok(CompiledInstruction {
499                    program_id_index,
500                    accounts: vec![source_idx, new_account_idx],
501                    data,
502                })
503            }
504            PARSED_DATA_FIELD_ASSIGN => {
505                let authority = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_ACCOUNT)?;
506                let owner = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_OWNER)?;
507
508                let authority_idx = Self::get_account_index(account_keys_hashmap, &authority)?;
509
510                let assign_ix = SystemInstruction::Assign { owner };
511                let data = bincode::serialize(&assign_ix).map_err(|e| {
512                    KoraError::SerializationError(format!(
513                        "Failed to serialize Assign instruction: {}",
514                        e
515                    ))
516                })?;
517
518                Ok(CompiledInstruction { program_id_index, accounts: vec![authority_idx], data })
519            }
520            PARSED_DATA_FIELD_TRANSFER_WITH_SEED => {
521                let source = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_SOURCE)?;
522                let destination = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_DESTINATION)?;
523                let lamports = Self::get_field_as_u64(info, PARSED_DATA_FIELD_LAMPORTS)?;
524                let source_base = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_SOURCE_BASE)?;
525                let source_seed =
526                    Self::get_field_as_str(info, PARSED_DATA_FIELD_SOURCE_SEED)?.to_string();
527                let source_owner = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_SOURCE_OWNER)?;
528
529                let source_idx = Self::get_account_index(account_keys_hashmap, &source)?;
530                let destination_idx = Self::get_account_index(account_keys_hashmap, &destination)?;
531                let source_base_idx = Self::get_account_index(account_keys_hashmap, &source_base)?;
532
533                let transfer_ix = SystemInstruction::TransferWithSeed {
534                    lamports,
535                    from_seed: source_seed,
536                    from_owner: source_owner,
537                };
538                let data = bincode::serialize(&transfer_ix).map_err(|e| {
539                    KoraError::SerializationError(format!(
540                        "Failed to serialize TransferWithSeed instruction: {}",
541                        e
542                    ))
543                })?;
544
545                Ok(CompiledInstruction {
546                    program_id_index,
547                    accounts: vec![source_idx, source_base_idx, destination_idx],
548                    data,
549                })
550            }
551            PARSED_DATA_FIELD_CREATE_ACCOUNT_WITH_SEED => {
552                let source = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_SOURCE)?;
553                let new_account = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_NEW_ACCOUNT)?;
554                let base = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_BASE)?;
555                let seed = Self::get_field_as_str(info, PARSED_DATA_FIELD_SEED)?.to_string();
556                let owner = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_OWNER)?;
557                let lamports = Self::get_field_as_u64(info, PARSED_DATA_FIELD_LAMPORTS)?;
558                let space = Self::get_field_as_u64(info, PARSED_DATA_FIELD_SPACE)?;
559
560                let source_idx = Self::get_account_index(account_keys_hashmap, &source)?;
561                let new_account_idx = Self::get_account_index(account_keys_hashmap, &new_account)?;
562                let base_idx = Self::get_account_index(account_keys_hashmap, &base)?;
563
564                let create_ix =
565                    SystemInstruction::CreateAccountWithSeed { base, seed, lamports, space, owner };
566                let data = bincode::serialize(&create_ix).map_err(|e| {
567                    KoraError::SerializationError(format!(
568                        "Failed to serialize CreateAccountWithSeed instruction: {}",
569                        e
570                    ))
571                })?;
572
573                Ok(CompiledInstruction {
574                    program_id_index,
575                    accounts: vec![source_idx, new_account_idx, base_idx],
576                    data,
577                })
578            }
579            PARSED_DATA_FIELD_ASSIGN_WITH_SEED => {
580                let account = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_ACCOUNT)?;
581                let base = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_BASE)?;
582                let seed = Self::get_field_as_str(info, PARSED_DATA_FIELD_SEED)?.to_string();
583                let owner = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_OWNER)?;
584
585                let account_idx = Self::get_account_index(account_keys_hashmap, &account)?;
586                let base_idx = Self::get_account_index(account_keys_hashmap, &base)?;
587
588                let assign_ix = SystemInstruction::AssignWithSeed { base, seed, owner };
589                let data = bincode::serialize(&assign_ix).map_err(|e| {
590                    KoraError::SerializationError(format!(
591                        "Failed to serialize AssignWithSeed instruction: {}",
592                        e
593                    ))
594                })?;
595
596                Ok(CompiledInstruction {
597                    program_id_index,
598                    accounts: vec![account_idx, base_idx],
599                    data,
600                })
601            }
602            PARSED_DATA_FIELD_WITHDRAW_NONCE_ACCOUNT => {
603                let nonce_account =
604                    Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_NONCE_ACCOUNT)?;
605                let recipient = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_DESTINATION)?;
606                let nonce_authority =
607                    Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_NONCE_AUTHORITY)?;
608                let lamports = Self::get_field_as_u64(info, PARSED_DATA_FIELD_LAMPORTS)?;
609
610                let nonce_account_idx =
611                    Self::get_account_index(account_keys_hashmap, &nonce_account)?;
612                let recipient_idx = Self::get_account_index(account_keys_hashmap, &recipient)?;
613                let nonce_authority_idx =
614                    Self::get_account_index(account_keys_hashmap, &nonce_authority)?;
615
616                let withdraw_ix = SystemInstruction::WithdrawNonceAccount(lamports);
617                let data = bincode::serialize(&withdraw_ix).map_err(|e| {
618                    KoraError::SerializationError(format!(
619                        "Failed to serialize WithdrawNonceAccount instruction: {}",
620                        e
621                    ))
622                })?;
623
624                Ok(CompiledInstruction {
625                    program_id_index,
626                    accounts: vec![nonce_account_idx, recipient_idx, nonce_authority_idx],
627                    data,
628                })
629            }
630            PARSED_DATA_FIELD_ALLOCATE => {
631                let account = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_ACCOUNT)?;
632                let space = Self::get_field_as_u64(info, PARSED_DATA_FIELD_SPACE)?;
633
634                let account_idx = Self::get_account_index(account_keys_hashmap, &account)?;
635
636                let allocate_ix = SystemInstruction::Allocate { space };
637                let data = bincode::serialize(&allocate_ix).map_err(|e| {
638                    KoraError::SerializationError(format!(
639                        "Failed to serialize Allocate instruction: {}",
640                        e
641                    ))
642                })?;
643
644                Ok(CompiledInstruction { program_id_index, accounts: vec![account_idx], data })
645            }
646            PARSED_DATA_FIELD_ALLOCATE_WITH_SEED => {
647                let account = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_ACCOUNT)?;
648                let base = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_BASE)?;
649                let seed = Self::get_field_as_str(info, PARSED_DATA_FIELD_SEED)?.to_string();
650                let space = Self::get_field_as_u64(info, PARSED_DATA_FIELD_SPACE)?;
651                let owner = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_OWNER)?;
652
653                let account_idx = Self::get_account_index(account_keys_hashmap, &account)?;
654                let base_idx = Self::get_account_index(account_keys_hashmap, &base)?;
655
656                let allocate_ix = SystemInstruction::AllocateWithSeed { base, seed, space, owner };
657                let data = bincode::serialize(&allocate_ix).map_err(|e| {
658                    KoraError::SerializationError(format!(
659                        "Failed to serialize AllocateWithSeed instruction: {}",
660                        e
661                    ))
662                })?;
663
664                Ok(CompiledInstruction {
665                    program_id_index,
666                    accounts: vec![account_idx, base_idx],
667                    data,
668                })
669            }
670            PARSED_DATA_FIELD_INITIALIZE_NONCE_ACCOUNT => {
671                let nonce_account =
672                    Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_NONCE_ACCOUNT)?;
673                let nonce_authority =
674                    Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_NONCE_AUTHORITY)?;
675
676                let nonce_account_idx =
677                    Self::get_account_index(account_keys_hashmap, &nonce_account)?;
678
679                let initialize_ix = SystemInstruction::InitializeNonceAccount(nonce_authority);
680                let data = bincode::serialize(&initialize_ix).map_err(|e| {
681                    KoraError::SerializationError(format!(
682                        "Failed to serialize InitializeNonceAccount instruction: {}",
683                        e
684                    ))
685                })?;
686
687                // Accounts: [nonce_account, recent_blockhashes_sysvar, rent_sysvar]
688                // We only have nonce_account in the hashmap for inner instructions
689                Ok(CompiledInstruction {
690                    program_id_index,
691                    accounts: vec![nonce_account_idx],
692                    data,
693                })
694            }
695            PARSED_DATA_FIELD_ADVANCE_NONCE_ACCOUNT => {
696                let nonce_account =
697                    Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_NONCE_ACCOUNT)?;
698                let nonce_authority =
699                    Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_NONCE_AUTHORITY)?;
700
701                let nonce_account_idx =
702                    Self::get_account_index(account_keys_hashmap, &nonce_account)?;
703                let nonce_authority_idx =
704                    Self::get_account_index(account_keys_hashmap, &nonce_authority)?;
705
706                let advance_ix = SystemInstruction::AdvanceNonceAccount;
707                let data = bincode::serialize(&advance_ix).map_err(|e| {
708                    KoraError::SerializationError(format!(
709                        "Failed to serialize AdvanceNonceAccount instruction: {}",
710                        e
711                    ))
712                })?;
713
714                // Accounts: [nonce_account, recent_blockhashes_sysvar, nonce_authority]
715                // We only include accounts that are in the hashmap (from inner instructions)
716                Ok(CompiledInstruction {
717                    program_id_index,
718                    accounts: vec![nonce_account_idx, nonce_authority_idx],
719                    data,
720                })
721            }
722            PARSED_DATA_FIELD_AUTHORIZE_NONCE_ACCOUNT => {
723                let nonce_account =
724                    Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_NONCE_ACCOUNT)?;
725                let nonce_authority =
726                    Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_NONCE_AUTHORITY)?;
727                let new_authority =
728                    Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_NEW_AUTHORITY)?;
729
730                let nonce_account_idx =
731                    Self::get_account_index(account_keys_hashmap, &nonce_account)?;
732                let nonce_authority_idx =
733                    Self::get_account_index(account_keys_hashmap, &nonce_authority)?;
734
735                let authorize_ix = SystemInstruction::AuthorizeNonceAccount(new_authority);
736                let data = bincode::serialize(&authorize_ix).map_err(|e| {
737                    KoraError::SerializationError(format!(
738                        "Failed to serialize AuthorizeNonceAccount instruction: {}",
739                        e
740                    ))
741                })?;
742
743                // Accounts: [nonce_account, nonce_authority]
744                Ok(CompiledInstruction {
745                    program_id_index,
746                    accounts: vec![nonce_account_idx, nonce_authority_idx],
747                    data,
748                })
749            }
750            _ => {
751                log::error!("Unsupported system instruction type: {}", instruction_type);
752                Ok(Self::build_default_compiled_instruction(program_id_index))
753            }
754        }
755    }
756
757    /// Reconstruct SPL token program instructions from parsed format
758    fn reconstruct_spl_token_instruction(
759        parsed: &solana_transaction_status_client_types::ParsedInstruction,
760        account_keys_hashmap: &HashMap<Pubkey, u8>,
761    ) -> Result<CompiledInstruction, KoraError> {
762        let program_id = parsed
763            .program_id
764            .parse::<Pubkey>()
765            .map_err(|e| KoraError::SerializationError(format!("Invalid program ID: {}", e)))?;
766        let program_id_index = Self::get_account_index(account_keys_hashmap, &program_id)?;
767
768        let parsed_data = &parsed.parsed;
769        let instruction_type = Self::get_field_as_str(parsed_data, PARSED_DATA_FIELD_TYPE)?;
770        let info = parsed_data
771            .get(PARSED_DATA_FIELD_INFO)
772            .ok_or_else(|| KoraError::SerializationError("Missing 'info' field".to_string()))?;
773
774        match instruction_type {
775            PARSED_DATA_FIELD_TRANSFER => {
776                let source = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_SOURCE)?;
777                let destination = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_DESTINATION)?;
778                let authority = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_AUTHORITY)?;
779                let amount = Self::get_field_as_u64(info, PARSED_DATA_FIELD_AMOUNT)?;
780
781                let source_idx = Self::get_account_index(account_keys_hashmap, &source)?;
782                let destination_idx = Self::get_account_index(account_keys_hashmap, &destination)?;
783                let authority_idx = Self::get_account_index(account_keys_hashmap, &authority)?;
784
785                let data = if parsed.program_id == spl_token_interface::ID.to_string() {
786                    spl_token_interface::instruction::TokenInstruction::Transfer { amount }.pack()
787                } else {
788                    #[allow(deprecated)]
789                    spl_token_2022_interface::instruction::TokenInstruction::Transfer { amount }
790                        .pack()
791                };
792
793                Ok(CompiledInstruction {
794                    program_id_index,
795                    accounts: vec![source_idx, destination_idx, authority_idx],
796                    data,
797                })
798            }
799            PARSED_DATA_FIELD_TRANSFER_CHECKED => {
800                let source = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_SOURCE)?;
801                let destination = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_DESTINATION)?;
802                let authority = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_AUTHORITY)?;
803                let mint = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_MINT)?;
804
805                let token_amount = info.get(PARSED_DATA_FIELD_TOKEN_AMOUNT).ok_or_else(|| {
806                    KoraError::SerializationError("Missing 'tokenAmount' field".to_string())
807                })?;
808                let amount = Self::get_field_as_u64(token_amount, PARSED_DATA_FIELD_AMOUNT)?;
809                let decimals =
810                    Self::get_field_as_u64(token_amount, PARSED_DATA_FIELD_DECIMALS)? as u8;
811
812                let source_idx = Self::get_account_index(account_keys_hashmap, &source)?;
813                let mint_idx = Self::get_account_index(account_keys_hashmap, &mint)?;
814                let destination_idx = Self::get_account_index(account_keys_hashmap, &destination)?;
815                let authority_idx = Self::get_account_index(account_keys_hashmap, &authority)?;
816
817                let data = if parsed.program_id == spl_token_interface::ID.to_string() {
818                    spl_token_interface::instruction::TokenInstruction::TransferChecked {
819                        amount,
820                        decimals,
821                    }
822                    .pack()
823                } else {
824                    spl_token_2022_interface::instruction::TokenInstruction::TransferChecked {
825                        amount,
826                        decimals,
827                    }
828                    .pack()
829                };
830
831                Ok(CompiledInstruction {
832                    program_id_index,
833                    accounts: vec![source_idx, mint_idx, destination_idx, authority_idx],
834                    data,
835                })
836            }
837            PARSED_DATA_FIELD_BURN | PARSED_DATA_FIELD_BURN_CHECKED => {
838                let account = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_ACCOUNT)?;
839                let authority = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_AUTHORITY)?;
840
841                let (amount, decimals) = if instruction_type == PARSED_DATA_FIELD_BURN_CHECKED {
842                    let token_amount =
843                        info.get(PARSED_DATA_FIELD_TOKEN_AMOUNT).ok_or_else(|| {
844                            KoraError::SerializationError(
845                                "Missing 'tokenAmount' field for burnChecked".to_string(),
846                            )
847                        })?;
848                    let amount = Self::get_field_as_u64(token_amount, PARSED_DATA_FIELD_AMOUNT)?;
849                    let decimals =
850                        Self::get_field_as_u64(token_amount, PARSED_DATA_FIELD_DECIMALS)? as u8;
851                    (amount, Some(decimals))
852                } else {
853                    let amount =
854                        Self::get_field_as_u64(info, PARSED_DATA_FIELD_AMOUNT).unwrap_or(0);
855                    (amount, None)
856                };
857
858                let account_idx = Self::get_account_index(account_keys_hashmap, &account)?;
859                let authority_idx = Self::get_account_index(account_keys_hashmap, &authority)?;
860
861                let accounts = if instruction_type == PARSED_DATA_FIELD_BURN_CHECKED {
862                    let mint = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_MINT)?;
863                    let mint_idx = Self::get_account_index(account_keys_hashmap, &mint)?;
864                    vec![account_idx, mint_idx, authority_idx]
865                } else {
866                    vec![account_idx, authority_idx]
867                };
868
869                let data = if instruction_type == PARSED_DATA_FIELD_BURN_CHECKED {
870                    let decimals = decimals.unwrap(); // Safe because we set it above for burnChecked
871                    if parsed.program_id == spl_token_interface::ID.to_string() {
872                        spl_token_interface::instruction::TokenInstruction::BurnChecked {
873                            amount,
874                            decimals,
875                        }
876                        .pack()
877                    } else {
878                        spl_token_2022_interface::instruction::TokenInstruction::BurnChecked {
879                            amount,
880                            decimals,
881                        }
882                        .pack()
883                    }
884                } else if parsed.program_id == spl_token_interface::ID.to_string() {
885                    spl_token_interface::instruction::TokenInstruction::Burn { amount }.pack()
886                } else {
887                    spl_token_2022_interface::instruction::TokenInstruction::Burn { amount }.pack()
888                };
889
890                Ok(CompiledInstruction { program_id_index, accounts, data })
891            }
892            PARSED_DATA_FIELD_CLOSE_ACCOUNT => {
893                let account = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_ACCOUNT)?;
894                let destination = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_DESTINATION)?;
895                let authority = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_OWNER)?;
896
897                let account_idx = Self::get_account_index(account_keys_hashmap, &account)?;
898                let destination_idx = Self::get_account_index(account_keys_hashmap, &destination)?;
899                let authority_idx = Self::get_account_index(account_keys_hashmap, &authority)?;
900
901                let data = if parsed.program_id == spl_token_interface::ID.to_string() {
902                    spl_token_interface::instruction::TokenInstruction::CloseAccount.pack()
903                } else {
904                    spl_token_2022_interface::instruction::TokenInstruction::CloseAccount.pack()
905                };
906
907                Ok(CompiledInstruction {
908                    program_id_index,
909                    accounts: vec![account_idx, destination_idx, authority_idx],
910                    data,
911                })
912            }
913            PARSED_DATA_FIELD_APPROVE => {
914                let source = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_SOURCE)?;
915                let delegate = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_DELEGATE)?;
916                let owner = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_OWNER)?;
917                let amount = Self::get_field_as_u64(info, PARSED_DATA_FIELD_AMOUNT)?;
918
919                let source_idx = Self::get_account_index(account_keys_hashmap, &source)?;
920                let delegate_idx = Self::get_account_index(account_keys_hashmap, &delegate)?;
921                let owner_idx = Self::get_account_index(account_keys_hashmap, &owner)?;
922
923                let data = if parsed.program_id == spl_token_interface::ID.to_string() {
924                    spl_token_interface::instruction::TokenInstruction::Approve { amount }.pack()
925                } else {
926                    spl_token_2022_interface::instruction::TokenInstruction::Approve { amount }
927                        .pack()
928                };
929
930                Ok(CompiledInstruction {
931                    program_id_index,
932                    accounts: vec![source_idx, delegate_idx, owner_idx],
933                    data,
934                })
935            }
936            PARSED_DATA_FIELD_APPROVE_CHECKED => {
937                let source = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_SOURCE)?;
938                let delegate = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_DELEGATE)?;
939                let owner = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_OWNER)?;
940                let mint = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_MINT)?;
941
942                let token_amount = info.get(PARSED_DATA_FIELD_TOKEN_AMOUNT).ok_or_else(|| {
943                    KoraError::SerializationError("Missing 'tokenAmount' field".to_string())
944                })?;
945                let amount = Self::get_field_as_u64(token_amount, PARSED_DATA_FIELD_AMOUNT)?;
946                let decimals =
947                    Self::get_field_as_u64(token_amount, PARSED_DATA_FIELD_DECIMALS)? as u8;
948
949                let source_idx = Self::get_account_index(account_keys_hashmap, &source)?;
950                let mint_idx = Self::get_account_index(account_keys_hashmap, &mint)?;
951                let delegate_idx = Self::get_account_index(account_keys_hashmap, &delegate)?;
952                let owner_idx = Self::get_account_index(account_keys_hashmap, &owner)?;
953
954                let data = if parsed.program_id == spl_token_interface::ID.to_string() {
955                    spl_token_interface::instruction::TokenInstruction::ApproveChecked {
956                        amount,
957                        decimals,
958                    }
959                    .pack()
960                } else {
961                    spl_token_2022_interface::instruction::TokenInstruction::ApproveChecked {
962                        amount,
963                        decimals,
964                    }
965                    .pack()
966                };
967
968                Ok(CompiledInstruction {
969                    program_id_index,
970                    accounts: vec![source_idx, mint_idx, delegate_idx, owner_idx],
971                    data,
972                })
973            }
974            PARSED_DATA_FIELD_REVOKE => {
975                let source = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_SOURCE)?;
976                let owner = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_OWNER)?;
977
978                let source_idx = Self::get_account_index(account_keys_hashmap, &source)?;
979                let owner_idx = Self::get_account_index(account_keys_hashmap, &owner)?;
980
981                let data = if parsed.program_id == spl_token_interface::ID.to_string() {
982                    spl_token_interface::instruction::TokenInstruction::Revoke.pack()
983                } else {
984                    spl_token_2022_interface::instruction::TokenInstruction::Revoke.pack()
985                };
986
987                Ok(CompiledInstruction {
988                    program_id_index,
989                    accounts: vec![source_idx, owner_idx],
990                    data,
991                })
992            }
993            PARSED_DATA_FIELD_SET_AUTHORITY => {
994                let account = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_ACCOUNT)?;
995                let current_authority =
996                    Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_AUTHORITY)?;
997
998                let account_idx = Self::get_account_index(account_keys_hashmap, &account)?;
999                let current_authority_idx =
1000                    Self::get_account_index(account_keys_hashmap, &current_authority)?;
1001
1002                // SetAuthority has variable data - we reconstruct minimal version
1003                // Real validation happens when checking if fee payer is the authority
1004                let data = vec![6];
1005
1006                Ok(CompiledInstruction {
1007                    program_id_index,
1008                    accounts: vec![account_idx, current_authority_idx],
1009                    data,
1010                })
1011            }
1012            PARSED_DATA_FIELD_MINT_TO => {
1013                let mint = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_MINT)?;
1014                let account = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_ACCOUNT)?;
1015                let mint_authority =
1016                    Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_MINT_AUTHORITY)?;
1017                let amount = Self::get_field_as_u64(info, PARSED_DATA_FIELD_AMOUNT)?;
1018
1019                let mint_idx = Self::get_account_index(account_keys_hashmap, &mint)?;
1020                let account_idx = Self::get_account_index(account_keys_hashmap, &account)?;
1021                let mint_authority_idx =
1022                    Self::get_account_index(account_keys_hashmap, &mint_authority)?;
1023
1024                let data = if parsed.program_id == spl_token_interface::ID.to_string() {
1025                    spl_token_interface::instruction::TokenInstruction::MintTo { amount }.pack()
1026                } else {
1027                    spl_token_2022_interface::instruction::TokenInstruction::MintTo { amount }
1028                        .pack()
1029                };
1030
1031                Ok(CompiledInstruction {
1032                    program_id_index,
1033                    accounts: vec![mint_idx, account_idx, mint_authority_idx],
1034                    data,
1035                })
1036            }
1037            PARSED_DATA_FIELD_MINT_TO_CHECKED => {
1038                let mint = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_MINT)?;
1039                let account = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_ACCOUNT)?;
1040                let mint_authority =
1041                    Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_MINT_AUTHORITY)?;
1042
1043                let token_amount = info.get(PARSED_DATA_FIELD_TOKEN_AMOUNT).ok_or_else(|| {
1044                    KoraError::SerializationError("Missing 'tokenAmount' field".to_string())
1045                })?;
1046                let amount = Self::get_field_as_u64(token_amount, PARSED_DATA_FIELD_AMOUNT)?;
1047                let decimals =
1048                    Self::get_field_as_u64(token_amount, PARSED_DATA_FIELD_DECIMALS)? as u8;
1049
1050                let mint_idx = Self::get_account_index(account_keys_hashmap, &mint)?;
1051                let account_idx = Self::get_account_index(account_keys_hashmap, &account)?;
1052                let mint_authority_idx =
1053                    Self::get_account_index(account_keys_hashmap, &mint_authority)?;
1054
1055                let data = if parsed.program_id == spl_token_interface::ID.to_string() {
1056                    spl_token_interface::instruction::TokenInstruction::MintToChecked {
1057                        amount,
1058                        decimals,
1059                    }
1060                    .pack()
1061                } else {
1062                    spl_token_2022_interface::instruction::TokenInstruction::MintToChecked {
1063                        amount,
1064                        decimals,
1065                    }
1066                    .pack()
1067                };
1068
1069                Ok(CompiledInstruction {
1070                    program_id_index,
1071                    accounts: vec![mint_idx, account_idx, mint_authority_idx],
1072                    data,
1073                })
1074            }
1075            PARSED_DATA_FIELD_INITIALIZE_MINT | PARSED_DATA_FIELD_INITIALIZE_MINT2 => {
1076                let mint = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_MINT)?;
1077                // mint_authority is in instruction data, not used for reconstruction
1078                let _mint_authority =
1079                    Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_MINT_AUTHORITY)?;
1080
1081                let mint_idx = Self::get_account_index(account_keys_hashmap, &mint)?;
1082
1083                // InitializeMint has discriminator only, authority is in data
1084                let data = if instruction_type == PARSED_DATA_FIELD_INITIALIZE_MINT {
1085                    vec![0] // InitializeMint discriminator
1086                } else {
1087                    vec![20] // InitializeMint2 discriminator
1088                };
1089
1090                Ok(CompiledInstruction { program_id_index, accounts: vec![mint_idx], data })
1091            }
1092            PARSED_DATA_FIELD_INITIALIZE_ACCOUNT
1093            | PARSED_DATA_FIELD_INITIALIZE_ACCOUNT2
1094            | PARSED_DATA_FIELD_INITIALIZE_ACCOUNT3 => {
1095                let account = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_ACCOUNT)?;
1096                let mint = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_MINT)?;
1097                let owner = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_OWNER)?;
1098
1099                let account_idx = Self::get_account_index(account_keys_hashmap, &account)?;
1100                let mint_idx = Self::get_account_index(account_keys_hashmap, &mint)?;
1101
1102                // Different variants have different account structures and discriminators
1103                let (data, accounts) = match instruction_type {
1104                    PARSED_DATA_FIELD_INITIALIZE_ACCOUNT => {
1105                        // InitializeAccount: [account, mint, owner, rent]
1106                        // Owner is in accounts, not data
1107                        let owner_idx = Self::get_account_index(account_keys_hashmap, &owner)?;
1108                        (vec![1], vec![account_idx, mint_idx, owner_idx])
1109                    }
1110                    PARSED_DATA_FIELD_INITIALIZE_ACCOUNT2 => {
1111                        // InitializeAccount2: [account, mint, rent], owner in data
1112                        (vec![16], vec![account_idx, mint_idx])
1113                    }
1114                    PARSED_DATA_FIELD_INITIALIZE_ACCOUNT3 => {
1115                        // InitializeAccount3: [account, mint], owner in data
1116                        (vec![18], vec![account_idx, mint_idx])
1117                    }
1118                    _ => unreachable!(),
1119                };
1120
1121                Ok(CompiledInstruction { program_id_index, accounts, data })
1122            }
1123            PARSED_DATA_FIELD_INITIALIZE_MULTISIG | PARSED_DATA_FIELD_INITIALIZE_MULTISIG2 => {
1124                let multisig = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_MULTISIG_ACCOUNT)?;
1125                let multisig_idx = Self::get_account_index(account_keys_hashmap, &multisig)?;
1126
1127                // Extract signer pubkeys from signers array (not currently used for reconstruction)
1128                let _signers_value = info.get(PARSED_DATA_FIELD_SIGNERS).ok_or_else(|| {
1129                    KoraError::SerializationError("Missing 'signers' field".to_string())
1130                })?;
1131
1132                // Discriminator based on instruction variant
1133                let data = if instruction_type == PARSED_DATA_FIELD_INITIALIZE_MULTISIG {
1134                    vec![2] // InitializeMultisig discriminator
1135                } else {
1136                    vec![19] // InitializeMultisig2 discriminator
1137                };
1138
1139                Ok(CompiledInstruction { program_id_index, accounts: vec![multisig_idx], data })
1140            }
1141            PARSED_DATA_FIELD_FREEZE_ACCOUNT => {
1142                let account = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_ACCOUNT)?;
1143                let mint = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_MINT)?;
1144                let freeze_authority =
1145                    Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_FREEZE_AUTHORITY)?;
1146
1147                let account_idx = Self::get_account_index(account_keys_hashmap, &account)?;
1148                let mint_idx = Self::get_account_index(account_keys_hashmap, &mint)?;
1149                let freeze_authority_idx =
1150                    Self::get_account_index(account_keys_hashmap, &freeze_authority)?;
1151
1152                let data = if parsed.program_id == spl_token_interface::ID.to_string() {
1153                    spl_token_interface::instruction::TokenInstruction::FreezeAccount.pack()
1154                } else {
1155                    spl_token_2022_interface::instruction::TokenInstruction::FreezeAccount.pack()
1156                };
1157
1158                Ok(CompiledInstruction {
1159                    program_id_index,
1160                    accounts: vec![account_idx, mint_idx, freeze_authority_idx],
1161                    data,
1162                })
1163            }
1164            PARSED_DATA_FIELD_THAW_ACCOUNT => {
1165                let account = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_ACCOUNT)?;
1166                let mint = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_MINT)?;
1167                let freeze_authority =
1168                    Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_FREEZE_AUTHORITY)?;
1169
1170                let account_idx = Self::get_account_index(account_keys_hashmap, &account)?;
1171                let mint_idx = Self::get_account_index(account_keys_hashmap, &mint)?;
1172                let freeze_authority_idx =
1173                    Self::get_account_index(account_keys_hashmap, &freeze_authority)?;
1174
1175                let data = if parsed.program_id == spl_token_interface::ID.to_string() {
1176                    spl_token_interface::instruction::TokenInstruction::ThawAccount.pack()
1177                } else {
1178                    spl_token_2022_interface::instruction::TokenInstruction::ThawAccount.pack()
1179                };
1180
1181                Ok(CompiledInstruction {
1182                    program_id_index,
1183                    accounts: vec![account_idx, mint_idx, freeze_authority_idx],
1184                    data,
1185                })
1186            }
1187            _ => {
1188                log::error!("Unsupported token instruction type: {}", instruction_type);
1189                Ok(Self::build_default_compiled_instruction(program_id_index))
1190            }
1191        }
1192    }
1193
1194    pub fn parse_system_instructions(
1195        transaction: &VersionedTransactionResolved,
1196    ) -> Result<HashMap<ParsedSystemInstructionType, Vec<ParsedSystemInstructionData>>, KoraError>
1197    {
1198        let mut parsed_instructions: HashMap<
1199            ParsedSystemInstructionType,
1200            Vec<ParsedSystemInstructionData>,
1201        > = HashMap::new();
1202
1203        for instruction in transaction.all_instructions.iter() {
1204            let program_id = instruction.program_id;
1205
1206            // Handle System Program transfers and account creation
1207            if program_id == SYSTEM_PROGRAM_ID {
1208                match bincode::deserialize::<SystemInstruction>(&instruction.data) {
1209                    Ok(SystemInstruction::CreateAccount { lamports, .. })
1210                    | Ok(SystemInstruction::CreateAccountWithSeed { lamports, .. }) => {
1211                        parse_system_instruction!(parsed_instructions, instruction, system_create_account, SystemCreateAccount, SystemCreateAccount {
1212                            lamports: lamports;
1213                            payer: instruction_indexes::system_create_account::PAYER_INDEX
1214                        });
1215                    }
1216                    Ok(SystemInstruction::Transfer { lamports }) => {
1217                        parse_system_instruction!(parsed_instructions, instruction, system_transfer, SystemTransfer, SystemTransfer {
1218                            lamports: lamports;
1219                            sender: instruction_indexes::system_transfer::SENDER_INDEX,
1220                            receiver: instruction_indexes::system_transfer::RECEIVER_INDEX
1221                        });
1222                    }
1223                    Ok(SystemInstruction::TransferWithSeed { lamports, .. }) => {
1224                        // Note: uses system_transfer_with_seed for validation but maps to SystemTransfer type
1225                        validate_number_accounts!(instruction, instruction_indexes::system_transfer_with_seed::REQUIRED_NUMBER_OF_ACCOUNTS);
1226                        parsed_instructions
1227                            .entry(ParsedSystemInstructionType::SystemTransfer)
1228                            .or_default()
1229                            .push(ParsedSystemInstructionData::SystemTransfer {
1230                                lamports,
1231                                sender: instruction.accounts[instruction_indexes::system_transfer_with_seed::SENDER_INDEX].pubkey,
1232                                receiver: instruction.accounts[instruction_indexes::system_transfer_with_seed::RECEIVER_INDEX].pubkey,
1233                            });
1234                    }
1235                    Ok(SystemInstruction::WithdrawNonceAccount(lamports)) => {
1236                        parse_system_instruction!(parsed_instructions, instruction, system_withdraw_nonce_account, SystemWithdrawNonceAccount, SystemWithdrawNonceAccount {
1237                            lamports: lamports;
1238                            nonce_authority: instruction_indexes::system_withdraw_nonce_account::NONCE_AUTHORITY_INDEX,
1239                            recipient: instruction_indexes::system_withdraw_nonce_account::RECIPIENT_INDEX
1240                        });
1241                    }
1242                    Ok(SystemInstruction::Assign { .. }) => {
1243                        parse_system_instruction!(
1244                            parsed_instructions,
1245                            instruction,
1246                            system_assign,
1247                            SystemAssign,
1248                            SystemAssign {
1249                                authority: instruction_indexes::system_assign::AUTHORITY_INDEX
1250                            }
1251                        );
1252                    }
1253                    Ok(SystemInstruction::AssignWithSeed { .. }) => {
1254                        // Note: uses system_assign_with_seed for validation but maps to SystemAssign type
1255                        validate_number_accounts!(instruction, instruction_indexes::system_assign_with_seed::REQUIRED_NUMBER_OF_ACCOUNTS);
1256                        parsed_instructions
1257                            .entry(ParsedSystemInstructionType::SystemAssign)
1258                            .or_default()
1259                            .push(ParsedSystemInstructionData::SystemAssign {
1260                                authority: instruction.accounts
1261                                    [instruction_indexes::system_assign_with_seed::AUTHORITY_INDEX]
1262                                    .pubkey,
1263                            });
1264                    }
1265                    Ok(SystemInstruction::Allocate { .. }) => {
1266                        parse_system_instruction!(
1267                            parsed_instructions,
1268                            instruction,
1269                            system_allocate,
1270                            SystemAllocate,
1271                            SystemAllocate {
1272                                account: instruction_indexes::system_allocate::ACCOUNT_INDEX
1273                            }
1274                        );
1275                    }
1276                    Ok(SystemInstruction::AllocateWithSeed { .. }) => {
1277                        // Note: uses system_allocate_with_seed for validation but maps to SystemAllocate type
1278                        validate_number_accounts!(instruction, instruction_indexes::system_allocate_with_seed::REQUIRED_NUMBER_OF_ACCOUNTS);
1279                        parsed_instructions
1280                            .entry(ParsedSystemInstructionType::SystemAllocate)
1281                            .or_default()
1282                            .push(ParsedSystemInstructionData::SystemAllocate {
1283                                account: instruction.accounts
1284                                    [instruction_indexes::system_allocate_with_seed::ACCOUNT_INDEX]
1285                                    .pubkey,
1286                            });
1287                    }
1288                    Ok(SystemInstruction::InitializeNonceAccount(authority)) => {
1289                        parse_system_instruction!(parsed_instructions, instruction, system_initialize_nonce_account, SystemInitializeNonceAccount, SystemInitializeNonceAccount {
1290                            nonce_authority: authority;
1291                            nonce_account: instruction_indexes::system_initialize_nonce_account::NONCE_ACCOUNT_INDEX
1292                        });
1293                    }
1294                    Ok(SystemInstruction::AdvanceNonceAccount) => {
1295                        parse_system_instruction!(parsed_instructions, instruction, system_advance_nonce_account, SystemAdvanceNonceAccount, SystemAdvanceNonceAccount {
1296                            nonce_account: instruction_indexes::system_advance_nonce_account::NONCE_ACCOUNT_INDEX,
1297                            nonce_authority: instruction_indexes::system_advance_nonce_account::NONCE_AUTHORITY_INDEX
1298                        });
1299                    }
1300                    Ok(SystemInstruction::AuthorizeNonceAccount(_new_authority)) => {
1301                        parse_system_instruction!(parsed_instructions, instruction, system_authorize_nonce_account, SystemAuthorizeNonceAccount, SystemAuthorizeNonceAccount {
1302                            nonce_account: instruction_indexes::system_authorize_nonce_account::NONCE_ACCOUNT_INDEX,
1303                            nonce_authority: instruction_indexes::system_authorize_nonce_account::NONCE_AUTHORITY_INDEX
1304                        });
1305                    }
1306                    // UpgradeNonceAccount: Not parsed - no authority parameter, cannot validate fee payer involvement
1307                    // Anyone can upgrade any nonce account without signing
1308                    Ok(SystemInstruction::UpgradeNonceAccount) => {
1309                        // Skip parsing
1310                    }
1311                    _ => {}
1312                }
1313            }
1314        }
1315        Ok(parsed_instructions)
1316    }
1317
1318    pub fn parse_token_instructions(
1319        transaction: &VersionedTransactionResolved,
1320    ) -> Result<HashMap<ParsedSPLInstructionType, Vec<ParsedSPLInstructionData>>, KoraError> {
1321        let mut parsed_instructions: HashMap<
1322            ParsedSPLInstructionType,
1323            Vec<ParsedSPLInstructionData>,
1324        > = HashMap::new();
1325
1326        for instruction in &transaction.all_instructions {
1327            let program_id = instruction.program_id;
1328
1329            if program_id == spl_token_interface::ID {
1330                if let Ok(spl_ix) =
1331                    spl_token_interface::instruction::TokenInstruction::unpack(&instruction.data)
1332                {
1333                    match spl_ix {
1334                        spl_token_interface::instruction::TokenInstruction::Transfer { amount } => {
1335                            validate_number_accounts!(instruction, instruction_indexes::spl_token_transfer::REQUIRED_NUMBER_OF_ACCOUNTS);
1336
1337                            parsed_instructions
1338                                .entry(ParsedSPLInstructionType::SplTokenTransfer)
1339                                .or_default()
1340                                .push(ParsedSPLInstructionData::SplTokenTransfer {
1341                                    owner: instruction.accounts[instruction_indexes::spl_token_transfer::OWNER_INDEX].pubkey,
1342                                    amount,
1343                                    mint: None,
1344                                    source_address: instruction.accounts[instruction_indexes::spl_token_transfer::SOURCE_ADDRESS_INDEX].pubkey,
1345                                    destination_address: instruction.accounts[instruction_indexes::spl_token_transfer::DESTINATION_ADDRESS_INDEX].pubkey,
1346                                    is_2022: false,
1347                                });
1348                        }
1349                        spl_token_interface::instruction::TokenInstruction::TransferChecked {
1350                            amount,
1351                            ..
1352                        } => {
1353                            validate_number_accounts!(instruction, instruction_indexes::spl_token_transfer_checked::REQUIRED_NUMBER_OF_ACCOUNTS);
1354
1355                            parsed_instructions
1356                                .entry(ParsedSPLInstructionType::SplTokenTransfer)
1357                                .or_default()
1358                                .push(ParsedSPLInstructionData::SplTokenTransfer {
1359                                    owner: instruction.accounts[instruction_indexes::spl_token_transfer_checked::OWNER_INDEX].pubkey,
1360                                    amount,
1361                                    mint: Some(instruction.accounts[instruction_indexes::spl_token_transfer_checked::MINT_INDEX].pubkey),
1362                                    source_address: instruction.accounts[instruction_indexes::spl_token_transfer_checked::SOURCE_ADDRESS_INDEX].pubkey,
1363                                    destination_address: instruction.accounts[instruction_indexes::spl_token_transfer_checked::DESTINATION_ADDRESS_INDEX].pubkey,
1364                                    is_2022: false,
1365                                });
1366                        }
1367                        spl_token_interface::instruction::TokenInstruction::Burn { .. }
1368                        | spl_token_interface::instruction::TokenInstruction::BurnChecked { .. } => {
1369                            validate_number_accounts!(
1370                                instruction,
1371                                instruction_indexes::spl_token_burn::REQUIRED_NUMBER_OF_ACCOUNTS
1372                            );
1373
1374                            parsed_instructions
1375                                .entry(ParsedSPLInstructionType::SplTokenBurn)
1376                                .or_default()
1377                                .push(ParsedSPLInstructionData::SplTokenBurn {
1378                                    owner: instruction.accounts
1379                                        [instruction_indexes::spl_token_burn::OWNER_INDEX]
1380                                        .pubkey,
1381                                    is_2022: false,
1382                                });
1383                        }
1384                        spl_token_interface::instruction::TokenInstruction::CloseAccount { .. } => {
1385                            validate_number_accounts!(instruction, instruction_indexes::spl_token_close_account::REQUIRED_NUMBER_OF_ACCOUNTS);
1386
1387                            parsed_instructions
1388                                .entry(ParsedSPLInstructionType::SplTokenCloseAccount)
1389                                .or_default()
1390                                .push(ParsedSPLInstructionData::SplTokenCloseAccount {
1391                                    owner: instruction.accounts
1392                                        [instruction_indexes::spl_token_close_account::OWNER_INDEX]
1393                                        .pubkey,
1394                                    is_2022: false,
1395                                });
1396                        }
1397                        spl_token_interface::instruction::TokenInstruction::Approve { .. } => {
1398                            validate_number_accounts!(
1399                                instruction,
1400                                instruction_indexes::spl_token_approve::REQUIRED_NUMBER_OF_ACCOUNTS
1401                            );
1402
1403                            parsed_instructions
1404                                .entry(ParsedSPLInstructionType::SplTokenApprove)
1405                                .or_default()
1406                                .push(ParsedSPLInstructionData::SplTokenApprove {
1407                                    owner: instruction.accounts
1408                                        [instruction_indexes::spl_token_approve::OWNER_INDEX]
1409                                        .pubkey,
1410                                    is_2022: false,
1411                                });
1412                        }
1413                        spl_token_interface::instruction::TokenInstruction::ApproveChecked { .. } => {
1414                            validate_number_accounts!(instruction, instruction_indexes::spl_token_approve_checked::REQUIRED_NUMBER_OF_ACCOUNTS);
1415
1416                            parsed_instructions
1417                                .entry(ParsedSPLInstructionType::SplTokenApprove)
1418                                .or_default()
1419                                .push(ParsedSPLInstructionData::SplTokenApprove {
1420                                    owner: instruction.accounts[instruction_indexes::spl_token_approve_checked::OWNER_INDEX].pubkey,
1421                                    is_2022: false,
1422                                });
1423                        }
1424                        spl_token_interface::instruction::TokenInstruction::Revoke => {
1425                            validate_number_accounts!(
1426                                instruction,
1427                                instruction_indexes::spl_token_revoke::REQUIRED_NUMBER_OF_ACCOUNTS
1428                            );
1429
1430                            parsed_instructions
1431                                .entry(ParsedSPLInstructionType::SplTokenRevoke)
1432                                .or_default()
1433                                .push(ParsedSPLInstructionData::SplTokenRevoke {
1434                                    owner: instruction.accounts
1435                                        [instruction_indexes::spl_token_revoke::OWNER_INDEX]
1436                                        .pubkey,
1437                                    is_2022: false,
1438                                });
1439                        }
1440                        spl_token_interface::instruction::TokenInstruction::SetAuthority { .. } => {
1441                            validate_number_accounts!(instruction, instruction_indexes::spl_token_set_authority::REQUIRED_NUMBER_OF_ACCOUNTS);
1442
1443                            parsed_instructions
1444                                .entry(ParsedSPLInstructionType::SplTokenSetAuthority)
1445                                .or_default()
1446                                .push(ParsedSPLInstructionData::SplTokenSetAuthority {
1447                                    authority: instruction.accounts[instruction_indexes::spl_token_set_authority::CURRENT_AUTHORITY_INDEX].pubkey,
1448                                    is_2022: false,
1449                                });
1450                        }
1451                        spl_token_interface::instruction::TokenInstruction::MintTo { .. } => {
1452                            validate_number_accounts!(
1453                                instruction,
1454                                instruction_indexes::spl_token_mint_to::REQUIRED_NUMBER_OF_ACCOUNTS
1455                            );
1456
1457                            parsed_instructions
1458                                .entry(ParsedSPLInstructionType::SplTokenMintTo)
1459                                .or_default()
1460                                .push(ParsedSPLInstructionData::SplTokenMintTo {
1461                                    mint_authority: instruction.accounts[instruction_indexes::spl_token_mint_to::MINT_AUTHORITY_INDEX].pubkey,
1462                                    is_2022: false,
1463                                });
1464                        }
1465                        spl_token_interface::instruction::TokenInstruction::MintToChecked { .. } => {
1466                            validate_number_accounts!(instruction, instruction_indexes::spl_token_mint_to_checked::REQUIRED_NUMBER_OF_ACCOUNTS);
1467
1468                            parsed_instructions
1469                                .entry(ParsedSPLInstructionType::SplTokenMintTo)
1470                                .or_default()
1471                                .push(ParsedSPLInstructionData::SplTokenMintTo {
1472                                    mint_authority: instruction.accounts[instruction_indexes::spl_token_mint_to_checked::MINT_AUTHORITY_INDEX].pubkey,
1473                                    is_2022: false,
1474                                });
1475                        }
1476                        spl_token_interface::instruction::TokenInstruction::InitializeMint {
1477                            mint_authority,
1478                            ..
1479                        } => {
1480                            validate_number_accounts!(instruction, instruction_indexes::spl_token_initialize_mint::REQUIRED_NUMBER_OF_ACCOUNTS);
1481
1482                            parsed_instructions
1483                                .entry(ParsedSPLInstructionType::SplTokenInitializeMint)
1484                                .or_default()
1485                                .push(ParsedSPLInstructionData::SplTokenInitializeMint {
1486                                    mint_authority,
1487                                    is_2022: false,
1488                                });
1489                        }
1490                        spl_token_interface::instruction::TokenInstruction::InitializeMint2 {
1491                            mint_authority,
1492                            ..
1493                        } => {
1494                            validate_number_accounts!(instruction, instruction_indexes::spl_token_initialize_mint2::REQUIRED_NUMBER_OF_ACCOUNTS);
1495
1496                            parsed_instructions
1497                                .entry(ParsedSPLInstructionType::SplTokenInitializeMint)
1498                                .or_default()
1499                                .push(ParsedSPLInstructionData::SplTokenInitializeMint {
1500                                    mint_authority,
1501                                    is_2022: false,
1502                                });
1503                        }
1504                        spl_token_interface::instruction::TokenInstruction::InitializeAccount => {
1505                            validate_number_accounts!(instruction, instruction_indexes::spl_token_initialize_account::REQUIRED_NUMBER_OF_ACCOUNTS);
1506
1507                            parsed_instructions
1508                                .entry(ParsedSPLInstructionType::SplTokenInitializeAccount)
1509                                .or_default()
1510                                .push(ParsedSPLInstructionData::SplTokenInitializeAccount {
1511                                    owner: instruction.accounts[instruction_indexes::spl_token_initialize_account::OWNER_INDEX].pubkey,
1512                                    is_2022: false,
1513                                });
1514                        }
1515                        spl_token_interface::instruction::TokenInstruction::InitializeAccount2 { owner } => {
1516                            validate_number_accounts!(instruction, instruction_indexes::spl_token_initialize_account2::REQUIRED_NUMBER_OF_ACCOUNTS);
1517
1518                            parsed_instructions
1519                                .entry(ParsedSPLInstructionType::SplTokenInitializeAccount)
1520                                .or_default()
1521                                .push(ParsedSPLInstructionData::SplTokenInitializeAccount {
1522                                    owner,
1523                                    is_2022: false,
1524                                });
1525                        }
1526                        spl_token_interface::instruction::TokenInstruction::InitializeAccount3 { owner } => {
1527                            validate_number_accounts!(instruction, instruction_indexes::spl_token_initialize_account3::REQUIRED_NUMBER_OF_ACCOUNTS);
1528
1529                            parsed_instructions
1530                                .entry(ParsedSPLInstructionType::SplTokenInitializeAccount)
1531                                .or_default()
1532                                .push(ParsedSPLInstructionData::SplTokenInitializeAccount {
1533                                    owner,
1534                                    is_2022: false,
1535                                });
1536                        }
1537                        spl_token_interface::instruction::TokenInstruction::InitializeMultisig { .. } => {
1538                            validate_number_accounts!(instruction, instruction_indexes::spl_token_initialize_multisig::REQUIRED_NUMBER_OF_ACCOUNTS);
1539
1540                            // Extract signers from accounts (skip first 2: multisig + rent sysvar)
1541                            let signers: Vec<Pubkey> =
1542                                instruction.accounts.iter().skip(2).map(|a| a.pubkey).collect();
1543
1544                            parsed_instructions
1545                                .entry(ParsedSPLInstructionType::SplTokenInitializeMultisig)
1546                                .or_default()
1547                                .push(ParsedSPLInstructionData::SplTokenInitializeMultisig {
1548                                    signers,
1549                                    is_2022: false,
1550                                });
1551                        }
1552                        spl_token_interface::instruction::TokenInstruction::InitializeMultisig2 {
1553                            ..
1554                        } => {
1555                            validate_number_accounts!(instruction, instruction_indexes::spl_token_initialize_multisig2::REQUIRED_NUMBER_OF_ACCOUNTS);
1556
1557                            // Extract signers from accounts (skip first: multisig only)
1558                            let signers: Vec<Pubkey> =
1559                                instruction.accounts.iter().skip(1).map(|a| a.pubkey).collect();
1560
1561                            parsed_instructions
1562                                .entry(ParsedSPLInstructionType::SplTokenInitializeMultisig)
1563                                .or_default()
1564                                .push(ParsedSPLInstructionData::SplTokenInitializeMultisig {
1565                                    signers,
1566                                    is_2022: false,
1567                                });
1568                        }
1569                        spl_token_interface::instruction::TokenInstruction::FreezeAccount => {
1570                            validate_number_accounts!(instruction, instruction_indexes::spl_token_freeze_account::REQUIRED_NUMBER_OF_ACCOUNTS);
1571
1572                            parsed_instructions
1573                                .entry(ParsedSPLInstructionType::SplTokenFreezeAccount)
1574                                .or_default()
1575                                .push(ParsedSPLInstructionData::SplTokenFreezeAccount {
1576                                    freeze_authority: instruction.accounts[instruction_indexes::spl_token_freeze_account::FREEZE_AUTHORITY_INDEX].pubkey,
1577                                    is_2022: false,
1578                                });
1579                        }
1580                        spl_token_interface::instruction::TokenInstruction::ThawAccount => {
1581                            validate_number_accounts!(instruction, instruction_indexes::spl_token_thaw_account::REQUIRED_NUMBER_OF_ACCOUNTS);
1582
1583                            parsed_instructions
1584                                .entry(ParsedSPLInstructionType::SplTokenThawAccount)
1585                                .or_default()
1586                                .push(ParsedSPLInstructionData::SplTokenThawAccount {
1587                                    freeze_authority: instruction.accounts[instruction_indexes::spl_token_thaw_account::FREEZE_AUTHORITY_INDEX].pubkey,
1588                                    is_2022: false,
1589                                });
1590                        }
1591                        _ => {}
1592                    };
1593                }
1594            } else if program_id == spl_token_2022_interface::ID {
1595                if let Ok(spl_ix) = spl_token_2022_interface::instruction::TokenInstruction::unpack(
1596                    &instruction.data,
1597                ) {
1598                    match spl_ix {
1599                        #[allow(deprecated)]
1600                        spl_token_2022_interface::instruction::TokenInstruction::Transfer { amount } => {
1601                            validate_number_accounts!(instruction, instruction_indexes::spl_token_transfer::REQUIRED_NUMBER_OF_ACCOUNTS);
1602
1603                            parsed_instructions
1604                                .entry(ParsedSPLInstructionType::SplTokenTransfer)
1605                                .or_default()
1606                                .push(ParsedSPLInstructionData::SplTokenTransfer {
1607                                    owner: instruction.accounts[instruction_indexes::spl_token_transfer::OWNER_INDEX].pubkey,
1608                                    amount,
1609                                    mint: None,
1610                                    source_address: instruction.accounts[instruction_indexes::spl_token_transfer::SOURCE_ADDRESS_INDEX].pubkey,
1611                                    destination_address: instruction.accounts[instruction_indexes::spl_token_transfer::DESTINATION_ADDRESS_INDEX].pubkey,
1612                                    is_2022: true,
1613                                });
1614                        }
1615                        spl_token_2022_interface::instruction::TokenInstruction::TransferChecked {
1616                            amount,
1617                            ..
1618                        } => {
1619                            validate_number_accounts!(instruction, instruction_indexes::spl_token_transfer_checked::REQUIRED_NUMBER_OF_ACCOUNTS);
1620
1621                            parsed_instructions
1622                                .entry(ParsedSPLInstructionType::SplTokenTransfer)
1623                                .or_default()
1624                                .push(ParsedSPLInstructionData::SplTokenTransfer {
1625                                    owner: instruction.accounts[instruction_indexes::spl_token_transfer_checked::OWNER_INDEX].pubkey,
1626                                    amount,
1627                                    mint: Some(instruction.accounts[instruction_indexes::spl_token_transfer_checked::MINT_INDEX].pubkey),
1628                                    source_address: instruction.accounts[instruction_indexes::spl_token_transfer_checked::SOURCE_ADDRESS_INDEX].pubkey,
1629                                    destination_address: instruction.accounts[instruction_indexes::spl_token_transfer_checked::DESTINATION_ADDRESS_INDEX].pubkey,
1630                                    is_2022: true,
1631                                });
1632                        }
1633                        spl_token_2022_interface::instruction::TokenInstruction::Burn { .. }
1634                        | spl_token_2022_interface::instruction::TokenInstruction::BurnChecked { .. } => {
1635                            validate_number_accounts!(
1636                                instruction,
1637                                instruction_indexes::spl_token_burn::REQUIRED_NUMBER_OF_ACCOUNTS
1638                            );
1639
1640                            parsed_instructions
1641                                .entry(ParsedSPLInstructionType::SplTokenBurn)
1642                                .or_default()
1643                                .push(ParsedSPLInstructionData::SplTokenBurn {
1644                                    owner: instruction.accounts
1645                                        [instruction_indexes::spl_token_burn::OWNER_INDEX]
1646                                        .pubkey,
1647                                    is_2022: true,
1648                                });
1649                        }
1650                        spl_token_2022_interface::instruction::TokenInstruction::CloseAccount { .. } => {
1651                            validate_number_accounts!(instruction, instruction_indexes::spl_token_close_account::REQUIRED_NUMBER_OF_ACCOUNTS);
1652
1653                            parsed_instructions
1654                                .entry(ParsedSPLInstructionType::SplTokenCloseAccount)
1655                                .or_default()
1656                                .push(ParsedSPLInstructionData::SplTokenCloseAccount {
1657                                    owner: instruction.accounts
1658                                        [instruction_indexes::spl_token_close_account::OWNER_INDEX]
1659                                        .pubkey,
1660                                    is_2022: true,
1661                                });
1662                        }
1663                        spl_token_2022_interface::instruction::TokenInstruction::Approve { .. } => {
1664                            validate_number_accounts!(
1665                                instruction,
1666                                instruction_indexes::spl_token_approve::REQUIRED_NUMBER_OF_ACCOUNTS
1667                            );
1668
1669                            parsed_instructions
1670                                .entry(ParsedSPLInstructionType::SplTokenApprove)
1671                                .or_default()
1672                                .push(ParsedSPLInstructionData::SplTokenApprove {
1673                                    owner: instruction.accounts
1674                                        [instruction_indexes::spl_token_approve::OWNER_INDEX]
1675                                        .pubkey,
1676                                    is_2022: true,
1677                                });
1678                        }
1679                        spl_token_2022_interface::instruction::TokenInstruction::ApproveChecked {
1680                            ..
1681                        } => {
1682                            validate_number_accounts!(instruction, instruction_indexes::spl_token_approve_checked::REQUIRED_NUMBER_OF_ACCOUNTS);
1683
1684                            parsed_instructions
1685                                .entry(ParsedSPLInstructionType::SplTokenApprove)
1686                                .or_default()
1687                                .push(ParsedSPLInstructionData::SplTokenApprove {
1688                                    owner: instruction.accounts[instruction_indexes::spl_token_approve_checked::OWNER_INDEX].pubkey,
1689                                    is_2022: true,
1690                                });
1691                        }
1692                        spl_token_2022_interface::instruction::TokenInstruction::Revoke => {
1693                            validate_number_accounts!(
1694                                instruction,
1695                                instruction_indexes::spl_token_revoke::REQUIRED_NUMBER_OF_ACCOUNTS
1696                            );
1697
1698                            parsed_instructions
1699                                .entry(ParsedSPLInstructionType::SplTokenRevoke)
1700                                .or_default()
1701                                .push(ParsedSPLInstructionData::SplTokenRevoke {
1702                                    owner: instruction.accounts
1703                                        [instruction_indexes::spl_token_revoke::OWNER_INDEX]
1704                                        .pubkey,
1705                                    is_2022: true,
1706                                });
1707                        }
1708                        spl_token_2022_interface::instruction::TokenInstruction::SetAuthority { .. } => {
1709                            validate_number_accounts!(instruction, instruction_indexes::spl_token_set_authority::REQUIRED_NUMBER_OF_ACCOUNTS);
1710
1711                            parsed_instructions
1712                                .entry(ParsedSPLInstructionType::SplTokenSetAuthority)
1713                                .or_default()
1714                                .push(ParsedSPLInstructionData::SplTokenSetAuthority {
1715                                    authority: instruction.accounts[instruction_indexes::spl_token_set_authority::CURRENT_AUTHORITY_INDEX].pubkey,
1716                                    is_2022: true,
1717                                });
1718                        }
1719                        spl_token_2022_interface::instruction::TokenInstruction::MintTo { .. } => {
1720                            validate_number_accounts!(
1721                                instruction,
1722                                instruction_indexes::spl_token_mint_to::REQUIRED_NUMBER_OF_ACCOUNTS
1723                            );
1724
1725                            parsed_instructions
1726                                .entry(ParsedSPLInstructionType::SplTokenMintTo)
1727                                .or_default()
1728                                .push(ParsedSPLInstructionData::SplTokenMintTo {
1729                                    mint_authority: instruction.accounts[instruction_indexes::spl_token_mint_to::MINT_AUTHORITY_INDEX].pubkey,
1730                                    is_2022: true,
1731                                });
1732                        }
1733                        spl_token_2022_interface::instruction::TokenInstruction::MintToChecked { .. } => {
1734                            validate_number_accounts!(instruction, instruction_indexes::spl_token_mint_to_checked::REQUIRED_NUMBER_OF_ACCOUNTS);
1735
1736                            parsed_instructions
1737                                .entry(ParsedSPLInstructionType::SplTokenMintTo)
1738                                .or_default()
1739                                .push(ParsedSPLInstructionData::SplTokenMintTo {
1740                                    mint_authority: instruction.accounts[instruction_indexes::spl_token_mint_to_checked::MINT_AUTHORITY_INDEX].pubkey,
1741                                    is_2022: true,
1742                                });
1743                        }
1744                        spl_token_2022_interface::instruction::TokenInstruction::InitializeMint {
1745                            mint_authority,
1746                            ..
1747                        } => {
1748                            validate_number_accounts!(instruction, instruction_indexes::spl_token_initialize_mint::REQUIRED_NUMBER_OF_ACCOUNTS);
1749
1750                            parsed_instructions
1751                                .entry(ParsedSPLInstructionType::SplTokenInitializeMint)
1752                                .or_default()
1753                                .push(ParsedSPLInstructionData::SplTokenInitializeMint {
1754                                    mint_authority,
1755                                    is_2022: true,
1756                                });
1757                        }
1758                        spl_token_2022_interface::instruction::TokenInstruction::InitializeMint2 {
1759                            mint_authority,
1760                            ..
1761                        } => {
1762                            validate_number_accounts!(instruction, instruction_indexes::spl_token_initialize_mint2::REQUIRED_NUMBER_OF_ACCOUNTS);
1763
1764                            parsed_instructions
1765                                .entry(ParsedSPLInstructionType::SplTokenInitializeMint)
1766                                .or_default()
1767                                .push(ParsedSPLInstructionData::SplTokenInitializeMint {
1768                                    mint_authority,
1769                                    is_2022: true,
1770                                });
1771                        }
1772                        spl_token_2022_interface::instruction::TokenInstruction::InitializeAccount => {
1773                            validate_number_accounts!(instruction, instruction_indexes::spl_token_initialize_account::REQUIRED_NUMBER_OF_ACCOUNTS);
1774
1775                            parsed_instructions
1776                                .entry(ParsedSPLInstructionType::SplTokenInitializeAccount)
1777                                .or_default()
1778                                .push(ParsedSPLInstructionData::SplTokenInitializeAccount {
1779                                    owner: instruction.accounts[instruction_indexes::spl_token_initialize_account::OWNER_INDEX].pubkey,
1780                                    is_2022: true,
1781                                });
1782                        }
1783                        spl_token_2022_interface::instruction::TokenInstruction::InitializeAccount2 {
1784                            owner,
1785                        } => {
1786                            validate_number_accounts!(instruction, instruction_indexes::spl_token_initialize_account2::REQUIRED_NUMBER_OF_ACCOUNTS);
1787
1788                            parsed_instructions
1789                                .entry(ParsedSPLInstructionType::SplTokenInitializeAccount)
1790                                .or_default()
1791                                .push(ParsedSPLInstructionData::SplTokenInitializeAccount {
1792                                    owner,
1793                                    is_2022: true,
1794                                });
1795                        }
1796                        spl_token_2022_interface::instruction::TokenInstruction::InitializeAccount3 {
1797                            owner,
1798                        } => {
1799                            validate_number_accounts!(instruction, instruction_indexes::spl_token_initialize_account3::REQUIRED_NUMBER_OF_ACCOUNTS);
1800
1801                            parsed_instructions
1802                                .entry(ParsedSPLInstructionType::SplTokenInitializeAccount)
1803                                .or_default()
1804                                .push(ParsedSPLInstructionData::SplTokenInitializeAccount {
1805                                    owner,
1806                                    is_2022: true,
1807                                });
1808                        }
1809                        spl_token_2022_interface::instruction::TokenInstruction::InitializeMultisig {
1810                            ..
1811                        } => {
1812                            validate_number_accounts!(instruction, instruction_indexes::spl_token_initialize_multisig::REQUIRED_NUMBER_OF_ACCOUNTS);
1813
1814                            // Extract signers from accounts (skip first 2: multisig + rent sysvar)
1815                            let signers: Vec<Pubkey> =
1816                                instruction.accounts.iter().skip(2).map(|a| a.pubkey).collect();
1817
1818                            parsed_instructions
1819                                .entry(ParsedSPLInstructionType::SplTokenInitializeMultisig)
1820                                .or_default()
1821                                .push(ParsedSPLInstructionData::SplTokenInitializeMultisig {
1822                                    signers,
1823                                    is_2022: true,
1824                                });
1825                        }
1826                        spl_token_2022_interface::instruction::TokenInstruction::InitializeMultisig2 {
1827                            ..
1828                        } => {
1829                            validate_number_accounts!(instruction, instruction_indexes::spl_token_initialize_multisig2::REQUIRED_NUMBER_OF_ACCOUNTS);
1830
1831                            // Extract signers from accounts (skip first: multisig only)
1832                            let signers: Vec<Pubkey> =
1833                                instruction.accounts.iter().skip(1).map(|a| a.pubkey).collect();
1834
1835                            parsed_instructions
1836                                .entry(ParsedSPLInstructionType::SplTokenInitializeMultisig)
1837                                .or_default()
1838                                .push(ParsedSPLInstructionData::SplTokenInitializeMultisig {
1839                                    signers,
1840                                    is_2022: true,
1841                                });
1842                        }
1843                        spl_token_2022_interface::instruction::TokenInstruction::FreezeAccount => {
1844                            validate_number_accounts!(instruction, instruction_indexes::spl_token_freeze_account::REQUIRED_NUMBER_OF_ACCOUNTS);
1845
1846                            parsed_instructions
1847                                .entry(ParsedSPLInstructionType::SplTokenFreezeAccount)
1848                                .or_default()
1849                                .push(ParsedSPLInstructionData::SplTokenFreezeAccount {
1850                                    freeze_authority: instruction.accounts[instruction_indexes::spl_token_freeze_account::FREEZE_AUTHORITY_INDEX].pubkey,
1851                                    is_2022: true,
1852                                });
1853                        }
1854                        spl_token_2022_interface::instruction::TokenInstruction::ThawAccount => {
1855                            validate_number_accounts!(instruction, instruction_indexes::spl_token_thaw_account::REQUIRED_NUMBER_OF_ACCOUNTS);
1856
1857                            parsed_instructions
1858                                .entry(ParsedSPLInstructionType::SplTokenThawAccount)
1859                                .or_default()
1860                                .push(ParsedSPLInstructionData::SplTokenThawAccount {
1861                                    freeze_authority: instruction.accounts[instruction_indexes::spl_token_thaw_account::FREEZE_AUTHORITY_INDEX].pubkey,
1862                                    is_2022: true,
1863                                });
1864                        }
1865                        _ => {}
1866                    };
1867                }
1868            }
1869        }
1870        Ok(parsed_instructions)
1871    }
1872}
1873
1874#[cfg(test)]
1875mod tests {
1876
1877    use super::*;
1878    use solana_sdk::message::{AccountKeys, Message};
1879    use solana_transaction_status::parse_instruction;
1880
1881    fn create_parsed_system_transfer(
1882        source: &Pubkey,
1883        destination: &Pubkey,
1884        lamports: u64,
1885    ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
1886    {
1887        let solana_instruction =
1888            solana_system_interface::instruction::transfer(source, destination, lamports);
1889
1890        let message = Message::new(&[solana_instruction], None);
1891        let compiled_instruction = &message.instructions[0];
1892
1893        let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
1894
1895        let parsed = parse_instruction::parse(
1896            &SYSTEM_PROGRAM_ID,
1897            compiled_instruction,
1898            &account_keys_for_parsing,
1899            None,
1900        )?;
1901
1902        Ok(parsed)
1903    }
1904
1905    fn create_parsed_system_transfer_with_seed(
1906        source: &Pubkey,
1907        destination: &Pubkey,
1908        lamports: u64,
1909        source_base: &Pubkey,
1910        seed: &str,
1911        source_owner: &Pubkey,
1912    ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
1913    {
1914        let solana_instruction = solana_system_interface::instruction::transfer_with_seed(
1915            source,
1916            source_base,
1917            seed.to_string(),
1918            source_owner,
1919            destination,
1920            lamports,
1921        );
1922
1923        let message = Message::new(&[solana_instruction], None);
1924        let compiled_instruction = &message.instructions[0];
1925
1926        let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
1927
1928        let parsed = parse_instruction::parse(
1929            &SYSTEM_PROGRAM_ID,
1930            compiled_instruction,
1931            &account_keys_for_parsing,
1932            None,
1933        )?;
1934
1935        Ok(parsed)
1936    }
1937
1938    fn create_parsed_system_create_account(
1939        source: &Pubkey,
1940        new_account: &Pubkey,
1941        lamports: u64,
1942        space: u64,
1943        owner: &Pubkey,
1944    ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
1945    {
1946        let solana_instruction = solana_system_interface::instruction::create_account(
1947            source,
1948            new_account,
1949            lamports,
1950            space,
1951            owner,
1952        );
1953
1954        let message = Message::new(&[solana_instruction], None);
1955        let compiled_instruction = &message.instructions[0];
1956
1957        let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
1958
1959        let parsed = parse_instruction::parse(
1960            &SYSTEM_PROGRAM_ID,
1961            compiled_instruction,
1962            &account_keys_for_parsing,
1963            None,
1964        )?;
1965
1966        Ok(parsed)
1967    }
1968
1969    fn create_parsed_system_create_account_with_seed(
1970        source: &Pubkey,
1971        new_account: &Pubkey,
1972        base: &Pubkey,
1973        seed: &str,
1974        lamports: u64,
1975        space: u64,
1976        owner: &Pubkey,
1977    ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
1978    {
1979        let solana_instruction = solana_system_interface::instruction::create_account_with_seed(
1980            source,
1981            new_account,
1982            base,
1983            seed,
1984            lamports,
1985            space,
1986            owner,
1987        );
1988
1989        let message = Message::new(&[solana_instruction], None);
1990        let compiled_instruction = &message.instructions[0];
1991
1992        let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
1993
1994        let parsed = parse_instruction::parse(
1995            &SYSTEM_PROGRAM_ID,
1996            compiled_instruction,
1997            &account_keys_for_parsing,
1998            None,
1999        )?;
2000
2001        Ok(parsed)
2002    }
2003
2004    fn create_parsed_system_assign(
2005        account: &Pubkey,
2006        owner: &Pubkey,
2007    ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2008    {
2009        let solana_instruction = solana_system_interface::instruction::assign(account, owner);
2010
2011        let message = Message::new(&[solana_instruction], None);
2012        let compiled_instruction = &message.instructions[0];
2013
2014        let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2015
2016        let parsed = parse_instruction::parse(
2017            &SYSTEM_PROGRAM_ID,
2018            compiled_instruction,
2019            &account_keys_for_parsing,
2020            None,
2021        )?;
2022
2023        Ok(parsed)
2024    }
2025
2026    fn create_parsed_system_assign_with_seed(
2027        account: &Pubkey,
2028        base: &Pubkey,
2029        seed: &str,
2030        owner: &Pubkey,
2031    ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2032    {
2033        let solana_instruction =
2034            solana_system_interface::instruction::assign_with_seed(account, base, seed, owner);
2035
2036        let message = Message::new(&[solana_instruction], None);
2037        let compiled_instruction = &message.instructions[0];
2038
2039        let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2040
2041        let parsed = parse_instruction::parse(
2042            &SYSTEM_PROGRAM_ID,
2043            compiled_instruction,
2044            &account_keys_for_parsing,
2045            None,
2046        )?;
2047
2048        Ok(parsed)
2049    }
2050
2051    fn create_parsed_system_withdraw_nonce_account(
2052        nonce_account: &Pubkey,
2053        nonce_authority: &Pubkey,
2054        recipient: &Pubkey,
2055        lamports: u64,
2056    ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2057    {
2058        let solana_instruction = solana_system_interface::instruction::withdraw_nonce_account(
2059            nonce_account,
2060            nonce_authority,
2061            recipient,
2062            lamports,
2063        );
2064
2065        let message = Message::new(&[solana_instruction], None);
2066        let compiled_instruction = &message.instructions[0];
2067
2068        let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2069
2070        let parsed = parse_instruction::parse(
2071            &SYSTEM_PROGRAM_ID,
2072            compiled_instruction,
2073            &account_keys_for_parsing,
2074            None,
2075        )?;
2076
2077        Ok(parsed)
2078    }
2079
2080    fn create_parsed_spl_token_transfer(
2081        source: &Pubkey,
2082        destination: &Pubkey,
2083        authority: &Pubkey,
2084        amount: u64,
2085    ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2086    {
2087        let solana_instruction = spl_token_interface::instruction::transfer(
2088            &spl_token_interface::ID,
2089            source,
2090            destination,
2091            authority,
2092            &[],
2093            amount,
2094        )?;
2095
2096        let message = Message::new(&[solana_instruction], None);
2097        let compiled_instruction = &message.instructions[0];
2098
2099        let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2100
2101        let parsed = parse_instruction::parse(
2102            &spl_token_interface::ID,
2103            compiled_instruction,
2104            &account_keys_for_parsing,
2105            None,
2106        )?;
2107
2108        Ok(parsed)
2109    }
2110
2111    fn create_parsed_spl_token_transfer_checked(
2112        source: &Pubkey,
2113        mint: &Pubkey,
2114        destination: &Pubkey,
2115        authority: &Pubkey,
2116        amount: u64,
2117        decimals: u8,
2118    ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2119    {
2120        let solana_instruction = spl_token_interface::instruction::transfer_checked(
2121            &spl_token_interface::ID,
2122            source,
2123            mint,
2124            destination,
2125            authority,
2126            &[],
2127            amount,
2128            decimals,
2129        )?;
2130
2131        let message = Message::new(&[solana_instruction], None);
2132        let compiled_instruction = &message.instructions[0];
2133
2134        let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2135
2136        let parsed = parse_instruction::parse(
2137            &spl_token_interface::ID,
2138            compiled_instruction,
2139            &account_keys_for_parsing,
2140            None,
2141        )?;
2142
2143        Ok(parsed)
2144    }
2145
2146    fn create_parsed_spl_token_burn(
2147        account: &Pubkey,
2148        mint: &Pubkey,
2149        authority: &Pubkey,
2150        amount: u64,
2151    ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2152    {
2153        let solana_instruction = spl_token_interface::instruction::burn(
2154            &spl_token_interface::ID,
2155            account,
2156            mint,
2157            authority,
2158            &[],
2159            amount,
2160        )?;
2161
2162        let message = Message::new(&[solana_instruction], None);
2163        let compiled_instruction = &message.instructions[0];
2164
2165        let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2166
2167        let parsed = parse_instruction::parse(
2168            &spl_token_interface::ID,
2169            compiled_instruction,
2170            &account_keys_for_parsing,
2171            None,
2172        )?;
2173
2174        Ok(parsed)
2175    }
2176
2177    fn create_parsed_spl_token_burn_checked(
2178        account: &Pubkey,
2179        mint: &Pubkey,
2180        authority: &Pubkey,
2181        amount: u64,
2182        decimals: u8,
2183    ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2184    {
2185        let solana_instruction = spl_token_interface::instruction::burn_checked(
2186            &spl_token_interface::ID,
2187            account,
2188            mint,
2189            authority,
2190            &[],
2191            amount,
2192            decimals,
2193        )?;
2194
2195        let message = Message::new(&[solana_instruction], None);
2196        let compiled_instruction = &message.instructions[0];
2197
2198        let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2199
2200        let parsed = parse_instruction::parse(
2201            &spl_token_interface::ID,
2202            compiled_instruction,
2203            &account_keys_for_parsing,
2204            None,
2205        )?;
2206
2207        Ok(parsed)
2208    }
2209
2210    fn create_parsed_spl_token_close_account(
2211        account: &Pubkey,
2212        destination: &Pubkey,
2213        authority: &Pubkey,
2214    ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2215    {
2216        let solana_instruction = spl_token_interface::instruction::close_account(
2217            &spl_token_interface::ID,
2218            account,
2219            destination,
2220            authority,
2221            &[],
2222        )?;
2223
2224        let message = Message::new(&[solana_instruction], None);
2225        let compiled_instruction = &message.instructions[0];
2226
2227        let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2228
2229        let parsed = parse_instruction::parse(
2230            &spl_token_interface::ID,
2231            compiled_instruction,
2232            &account_keys_for_parsing,
2233            None,
2234        )?;
2235
2236        Ok(parsed)
2237    }
2238
2239    fn create_parsed_spl_token_approve(
2240        source: &Pubkey,
2241        delegate: &Pubkey,
2242        authority: &Pubkey,
2243        amount: u64,
2244    ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2245    {
2246        let solana_instruction = spl_token_interface::instruction::approve(
2247            &spl_token_interface::ID,
2248            source,
2249            delegate,
2250            authority,
2251            &[],
2252            amount,
2253        )?;
2254
2255        let message = Message::new(&[solana_instruction], None);
2256        let compiled_instruction = &message.instructions[0];
2257
2258        let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2259
2260        let parsed = parse_instruction::parse(
2261            &spl_token_interface::ID,
2262            compiled_instruction,
2263            &account_keys_for_parsing,
2264            None,
2265        )?;
2266
2267        Ok(parsed)
2268    }
2269
2270    fn create_parsed_spl_token_approve_checked(
2271        source: &Pubkey,
2272        mint: &Pubkey,
2273        delegate: &Pubkey,
2274        authority: &Pubkey,
2275        amount: u64,
2276        decimals: u8,
2277    ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2278    {
2279        let solana_instruction = spl_token_interface::instruction::approve_checked(
2280            &spl_token_interface::ID,
2281            source,
2282            mint,
2283            delegate,
2284            authority,
2285            &[],
2286            amount,
2287            decimals,
2288        )?;
2289
2290        let message = Message::new(&[solana_instruction], None);
2291        let compiled_instruction = &message.instructions[0];
2292
2293        let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2294
2295        let parsed = parse_instruction::parse(
2296            &spl_token_interface::ID,
2297            compiled_instruction,
2298            &account_keys_for_parsing,
2299            None,
2300        )?;
2301
2302        Ok(parsed)
2303    }
2304
2305    fn create_parsed_token2022_transfer(
2306        source: &Pubkey,
2307        destination: &Pubkey,
2308        authority: &Pubkey,
2309        amount: u64,
2310    ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2311    {
2312        #[allow(deprecated)]
2313        let solana_instruction = spl_token_2022_interface::instruction::transfer(
2314            &spl_token_2022_interface::ID,
2315            source,
2316            destination,
2317            authority,
2318            &[],
2319            amount,
2320        )?;
2321
2322        let message = Message::new(&[solana_instruction], None);
2323        let compiled_instruction = &message.instructions[0];
2324
2325        let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2326
2327        let parsed = parse_instruction::parse(
2328            &spl_token_2022_interface::ID,
2329            compiled_instruction,
2330            &account_keys_for_parsing,
2331            None,
2332        )?;
2333
2334        Ok(parsed)
2335    }
2336
2337    fn create_parsed_token2022_transfer_checked(
2338        source: &Pubkey,
2339        mint: &Pubkey,
2340        destination: &Pubkey,
2341        authority: &Pubkey,
2342        amount: u64,
2343        decimals: u8,
2344    ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2345    {
2346        let solana_instruction = spl_token_2022_interface::instruction::transfer_checked(
2347            &spl_token_2022_interface::ID,
2348            source,
2349            mint,
2350            destination,
2351            authority,
2352            &[],
2353            amount,
2354            decimals,
2355        )?;
2356
2357        let message = Message::new(&[solana_instruction], None);
2358        let compiled_instruction = &message.instructions[0];
2359
2360        let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2361
2362        let parsed = parse_instruction::parse(
2363            &spl_token_2022_interface::ID,
2364            compiled_instruction,
2365            &account_keys_for_parsing,
2366            None,
2367        )?;
2368
2369        Ok(parsed)
2370    }
2371
2372    fn create_parsed_token2022_burn(
2373        account: &Pubkey,
2374        mint: &Pubkey,
2375        authority: &Pubkey,
2376        amount: u64,
2377    ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2378    {
2379        let solana_instruction = spl_token_2022_interface::instruction::burn(
2380            &spl_token_2022_interface::ID,
2381            account,
2382            mint,
2383            authority,
2384            &[],
2385            amount,
2386        )?;
2387
2388        let message = Message::new(&[solana_instruction], None);
2389        let compiled_instruction = &message.instructions[0];
2390
2391        let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2392
2393        let parsed = parse_instruction::parse(
2394            &spl_token_2022_interface::ID,
2395            compiled_instruction,
2396            &account_keys_for_parsing,
2397            None,
2398        )?;
2399
2400        Ok(parsed)
2401    }
2402
2403    fn create_parsed_token2022_burn_checked(
2404        account: &Pubkey,
2405        mint: &Pubkey,
2406        authority: &Pubkey,
2407        amount: u64,
2408        decimals: u8,
2409    ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2410    {
2411        let solana_instruction = spl_token_2022_interface::instruction::burn_checked(
2412            &spl_token_2022_interface::ID,
2413            account,
2414            mint,
2415            authority,
2416            &[],
2417            amount,
2418            decimals,
2419        )?;
2420
2421        let message = Message::new(&[solana_instruction], None);
2422        let compiled_instruction = &message.instructions[0];
2423
2424        let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2425
2426        let parsed = parse_instruction::parse(
2427            &spl_token_2022_interface::ID,
2428            compiled_instruction,
2429            &account_keys_for_parsing,
2430            None,
2431        )?;
2432
2433        Ok(parsed)
2434    }
2435
2436    fn create_parsed_token2022_close_account(
2437        account: &Pubkey,
2438        destination: &Pubkey,
2439        authority: &Pubkey,
2440    ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2441    {
2442        let solana_instruction = spl_token_2022_interface::instruction::close_account(
2443            &spl_token_2022_interface::ID,
2444            account,
2445            destination,
2446            authority,
2447            &[],
2448        )?;
2449
2450        let message = Message::new(&[solana_instruction], None);
2451        let compiled_instruction = &message.instructions[0];
2452
2453        let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2454
2455        let parsed = parse_instruction::parse(
2456            &spl_token_2022_interface::ID,
2457            compiled_instruction,
2458            &account_keys_for_parsing,
2459            None,
2460        )?;
2461
2462        Ok(parsed)
2463    }
2464
2465    fn create_parsed_token2022_approve(
2466        source: &Pubkey,
2467        delegate: &Pubkey,
2468        authority: &Pubkey,
2469        amount: u64,
2470    ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2471    {
2472        let solana_instruction = spl_token_2022_interface::instruction::approve(
2473            &spl_token_2022_interface::ID,
2474            source,
2475            delegate,
2476            authority,
2477            &[],
2478            amount,
2479        )?;
2480
2481        let message = Message::new(&[solana_instruction], None);
2482        let compiled_instruction = &message.instructions[0];
2483
2484        let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2485
2486        let parsed = parse_instruction::parse(
2487            &spl_token_2022_interface::ID,
2488            compiled_instruction,
2489            &account_keys_for_parsing,
2490            None,
2491        )?;
2492
2493        Ok(parsed)
2494    }
2495
2496    fn create_parsed_token2022_approve_checked(
2497        source: &Pubkey,
2498        mint: &Pubkey,
2499        delegate: &Pubkey,
2500        authority: &Pubkey,
2501        amount: u64,
2502        decimals: u8,
2503    ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2504    {
2505        let solana_instruction = spl_token_2022_interface::instruction::approve_checked(
2506            &spl_token_2022_interface::ID,
2507            source,
2508            mint,
2509            delegate,
2510            authority,
2511            &[],
2512            amount,
2513            decimals,
2514        )?;
2515
2516        let message = Message::new(&[solana_instruction], None);
2517        let compiled_instruction = &message.instructions[0];
2518
2519        let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2520
2521        let parsed = parse_instruction::parse(
2522            &spl_token_2022_interface::ID,
2523            compiled_instruction,
2524            &account_keys_for_parsing,
2525            None,
2526        )?;
2527
2528        Ok(parsed)
2529    }
2530
2531    #[test]
2532    fn test_uncompile_instructions() {
2533        let program_id = Pubkey::new_unique();
2534        let account1 = Pubkey::new_unique();
2535        let account2 = Pubkey::new_unique();
2536
2537        let account_keys = vec![program_id, account1, account2];
2538        let compiled_ix = CompiledInstruction {
2539            program_id_index: 0,
2540            accounts: vec![1, 2], // indices into account_keys
2541            data: vec![1, 2, 3],
2542        };
2543
2544        let instructions = IxUtils::uncompile_instructions(&[compiled_ix], &account_keys).unwrap();
2545
2546        assert_eq!(instructions.len(), 1);
2547        let uncompiled = &instructions[0];
2548        assert_eq!(uncompiled.program_id, program_id);
2549        assert_eq!(uncompiled.accounts.len(), 2);
2550        assert_eq!(uncompiled.accounts[0].pubkey, account1);
2551        assert_eq!(uncompiled.accounts[1].pubkey, account2);
2552        assert_eq!(uncompiled.data, vec![1, 2, 3]);
2553    }
2554
2555    #[test]
2556    fn test_reconstruct_instruction_from_ui_compiled() {
2557        let program_id = Pubkey::new_unique();
2558        let account1 = Pubkey::new_unique();
2559        let account_keys = vec![program_id, account1];
2560
2561        let ui_compiled = solana_transaction_status_client_types::UiCompiledInstruction {
2562            program_id_index: 0,
2563            accounts: vec![1],
2564            data: bs58::encode(&[1, 2, 3]).into_string(),
2565            stack_height: None,
2566        };
2567
2568        let result = IxUtils::reconstruct_instruction_from_ui(
2569            &UiInstruction::Compiled(ui_compiled),
2570            &account_keys,
2571        );
2572
2573        assert!(result.is_some());
2574        let compiled = result.unwrap();
2575        assert_eq!(compiled.program_id_index, 0);
2576        assert_eq!(compiled.accounts, vec![1]);
2577        assert_eq!(compiled.data, vec![1, 2, 3]);
2578    }
2579
2580    #[test]
2581    fn test_reconstruct_partially_decoded_instruction() {
2582        let program_id = Pubkey::new_unique();
2583        let account1 = Pubkey::new_unique();
2584        let account2 = Pubkey::new_unique();
2585        let account_keys = vec![program_id, account1, account2];
2586
2587        let partial = solana_transaction_status_client_types::UiPartiallyDecodedInstruction {
2588            program_id: program_id.to_string(),
2589            accounts: vec![account1.to_string(), account2.to_string()],
2590            data: bs58::encode(&[5, 6, 7]).into_string(),
2591            stack_height: None,
2592        };
2593
2594        let ui_parsed = UiParsedInstruction::PartiallyDecoded(partial);
2595
2596        let result = IxUtils::reconstruct_instruction_from_ui(
2597            &UiInstruction::Parsed(ui_parsed),
2598            &account_keys,
2599        );
2600
2601        assert!(result.is_some());
2602        let compiled = result.unwrap();
2603        assert_eq!(compiled.program_id_index, 0);
2604        assert_eq!(compiled.accounts, vec![1, 2]); // account1, account2 indices
2605        assert_eq!(compiled.data, vec![5, 6, 7]);
2606    }
2607
2608    #[test]
2609    fn test_reconstruct_system_transfer_instruction() {
2610        let source = Pubkey::new_unique();
2611        let destination = Pubkey::new_unique();
2612        let system_program_id = SYSTEM_PROGRAM_ID;
2613        let account_keys = vec![system_program_id, source, destination];
2614        let lamports = 1000000u64;
2615
2616        let transfer_instruction =
2617            solana_system_interface::instruction::transfer(&source, &destination, lamports);
2618
2619        let solana_parsed_transfer = create_parsed_system_transfer(&source, &destination, lamports)
2620            .expect("Failed to create authentic parsed instruction");
2621
2622        let result = IxUtils::reconstruct_system_instruction(
2623            &solana_parsed_transfer,
2624            &IxUtils::build_account_keys_hashmap(&account_keys),
2625        );
2626
2627        assert!(result.is_ok());
2628        let compiled = result.unwrap();
2629        assert_eq!(compiled.program_id_index, 0);
2630        assert_eq!(compiled.accounts, vec![1, 2]); // source, destination indices
2631        assert_eq!(compiled.data, transfer_instruction.data);
2632    }
2633
2634    #[test]
2635    fn test_reconstruct_system_transfer_with_seed_instruction() {
2636        let source = Pubkey::new_unique();
2637        let destination = Pubkey::new_unique();
2638        let source_base = Pubkey::new_unique();
2639        let source_owner = Pubkey::new_unique();
2640        let system_program_id = SYSTEM_PROGRAM_ID;
2641        let account_keys = vec![system_program_id, source, source_base, destination];
2642        let lamports = 5000000u64;
2643
2644        let instruction = solana_system_interface::instruction::transfer_with_seed(
2645            &source,
2646            &source_base,
2647            "test_seed".to_string(),
2648            &source_owner,
2649            &destination,
2650            lamports,
2651        );
2652
2653        let solana_parsed = create_parsed_system_transfer_with_seed(
2654            &source,
2655            &destination,
2656            lamports,
2657            &source_base,
2658            "test_seed",
2659            &source_owner,
2660        )
2661        .expect("Failed to create parsed instruction");
2662
2663        let result = IxUtils::reconstruct_system_instruction(
2664            &solana_parsed,
2665            &IxUtils::build_account_keys_hashmap(&account_keys),
2666        );
2667
2668        assert!(result.is_ok());
2669        let compiled = result.unwrap();
2670        assert_eq!(compiled.program_id_index, 0);
2671        assert_eq!(compiled.accounts, vec![1, 2, 3]); // source, source_base, destination indices
2672        assert_eq!(compiled.data, instruction.data);
2673    }
2674
2675    #[test]
2676    fn test_reconstruct_system_create_account_instruction() {
2677        let source = Pubkey::new_unique();
2678        let new_account = Pubkey::new_unique();
2679        let owner = Pubkey::new_unique();
2680        let system_program_id = SYSTEM_PROGRAM_ID;
2681        let account_keys = vec![system_program_id, source, new_account];
2682        let lamports = 2000000u64;
2683        let space = 165u64;
2684
2685        let instruction = solana_system_interface::instruction::create_account(
2686            &source,
2687            &new_account,
2688            lamports,
2689            space,
2690            &owner,
2691        );
2692
2693        let solana_parsed =
2694            create_parsed_system_create_account(&source, &new_account, lamports, space, &owner)
2695                .expect("Failed to create parsed instruction");
2696
2697        let result = IxUtils::reconstruct_system_instruction(
2698            &solana_parsed,
2699            &IxUtils::build_account_keys_hashmap(&account_keys),
2700        );
2701
2702        assert!(result.is_ok());
2703        let compiled = result.unwrap();
2704        assert_eq!(compiled.program_id_index, 0);
2705        assert_eq!(compiled.accounts, vec![1, 2]); // source, new_account indices
2706        assert_eq!(compiled.data, instruction.data);
2707    }
2708
2709    #[test]
2710    fn test_reconstruct_system_create_account_with_seed_instruction() {
2711        let source = Pubkey::new_unique();
2712        let new_account = Pubkey::new_unique();
2713        let base = Pubkey::new_unique();
2714        let owner = Pubkey::new_unique();
2715        let system_program_id = SYSTEM_PROGRAM_ID;
2716        let account_keys = vec![system_program_id, source, new_account, base];
2717        let lamports = 3000000u64;
2718        let space = 200u64;
2719
2720        let instruction = solana_system_interface::instruction::create_account_with_seed(
2721            &source,
2722            &new_account,
2723            &base,
2724            "test_seed_create",
2725            lamports,
2726            space,
2727            &owner,
2728        );
2729
2730        let solana_parsed = create_parsed_system_create_account_with_seed(
2731            &source,
2732            &new_account,
2733            &base,
2734            "test_seed_create",
2735            lamports,
2736            space,
2737            &owner,
2738        )
2739        .expect("Failed to create parsed instruction");
2740
2741        let result = IxUtils::reconstruct_system_instruction(
2742            &solana_parsed,
2743            &IxUtils::build_account_keys_hashmap(&account_keys),
2744        );
2745
2746        assert!(result.is_ok());
2747        let compiled = result.unwrap();
2748        assert_eq!(compiled.program_id_index, 0);
2749        assert_eq!(compiled.accounts, vec![1, 2, 3]); // source, new_account, base indices
2750        assert_eq!(compiled.data, instruction.data);
2751    }
2752
2753    #[test]
2754    fn test_reconstruct_system_assign_instruction() {
2755        let account = Pubkey::new_unique();
2756        let owner = Pubkey::new_unique();
2757        let system_program_id = SYSTEM_PROGRAM_ID;
2758        let account_keys = vec![system_program_id, account];
2759
2760        let instruction = solana_system_interface::instruction::assign(&account, &owner);
2761
2762        let solana_parsed = create_parsed_system_assign(&account, &owner)
2763            .expect("Failed to create parsed instruction");
2764
2765        let result = IxUtils::reconstruct_system_instruction(
2766            &solana_parsed,
2767            &IxUtils::build_account_keys_hashmap(&account_keys),
2768        );
2769
2770        assert!(result.is_ok());
2771        let compiled = result.unwrap();
2772        assert_eq!(compiled.program_id_index, 0);
2773        assert_eq!(compiled.accounts, vec![1]); // account index
2774        assert_eq!(compiled.data, instruction.data);
2775    }
2776
2777    #[test]
2778    fn test_reconstruct_system_assign_with_seed_instruction() {
2779        let account = Pubkey::new_unique();
2780        let base = Pubkey::new_unique();
2781        let owner = Pubkey::new_unique();
2782        let system_program_id = SYSTEM_PROGRAM_ID;
2783        let account_keys = vec![system_program_id, account, base];
2784
2785        let instruction = solana_system_interface::instruction::assign_with_seed(
2786            &account,
2787            &base,
2788            "test_assign_seed",
2789            &owner,
2790        );
2791
2792        let solana_parsed =
2793            create_parsed_system_assign_with_seed(&account, &base, "test_assign_seed", &owner)
2794                .expect("Failed to create parsed instruction");
2795
2796        let result = IxUtils::reconstruct_system_instruction(
2797            &solana_parsed,
2798            &IxUtils::build_account_keys_hashmap(&account_keys),
2799        );
2800
2801        assert!(result.is_ok());
2802        let compiled = result.unwrap();
2803        assert_eq!(compiled.program_id_index, 0);
2804        assert_eq!(compiled.accounts, vec![1, 2]); // account, base indices
2805        assert_eq!(compiled.data, instruction.data);
2806    }
2807
2808    #[test]
2809    fn test_reconstruct_system_withdraw_nonce_account_instruction() {
2810        let nonce_account = Pubkey::new_unique();
2811        let recipient = Pubkey::new_unique();
2812        let nonce_authority = Pubkey::new_unique();
2813        let system_program_id = SYSTEM_PROGRAM_ID;
2814        let account_keys = vec![system_program_id, nonce_account, recipient, nonce_authority];
2815        let lamports = 1500000u64;
2816
2817        let instruction = solana_system_interface::instruction::withdraw_nonce_account(
2818            &nonce_account,
2819            &nonce_authority,
2820            &recipient,
2821            lamports,
2822        );
2823
2824        let solana_parsed = create_parsed_system_withdraw_nonce_account(
2825            &nonce_account,
2826            &nonce_authority,
2827            &recipient,
2828            lamports,
2829        )
2830        .expect("Failed to create parsed instruction");
2831
2832        let result = IxUtils::reconstruct_system_instruction(
2833            &solana_parsed,
2834            &IxUtils::build_account_keys_hashmap(&account_keys),
2835        );
2836
2837        assert!(result.is_ok());
2838        let compiled = result.unwrap();
2839        assert_eq!(compiled.program_id_index, 0);
2840        assert_eq!(compiled.accounts, vec![1, 2, 3]); // nonce_account, recipient, nonce_authority indices
2841        assert_eq!(compiled.data, instruction.data);
2842    }
2843
2844    #[test]
2845    fn test_reconstruct_spl_token_transfer_instruction() {
2846        let source = Pubkey::new_unique();
2847        let destination = Pubkey::new_unique();
2848        let authority = Pubkey::new_unique();
2849        let token_program_id = spl_token_interface::ID;
2850        let account_keys = vec![token_program_id, source, destination, authority];
2851        let amount = 1000000u64;
2852
2853        let transfer_instruction = spl_token_interface::instruction::transfer(
2854            &spl_token_interface::ID,
2855            &source,
2856            &destination,
2857            &authority,
2858            &[],
2859            amount,
2860        )
2861        .expect("Failed to create transfer instruction");
2862
2863        let solana_parsed_transfer =
2864            create_parsed_spl_token_transfer(&source, &destination, &authority, amount)
2865                .expect("Failed to create parsed instruction");
2866
2867        let result = IxUtils::reconstruct_spl_token_instruction(
2868            &solana_parsed_transfer,
2869            &IxUtils::build_account_keys_hashmap(&account_keys),
2870        );
2871
2872        assert!(result.is_ok());
2873        let compiled = result.unwrap();
2874        assert_eq!(compiled.program_id_index, 0);
2875        assert_eq!(compiled.accounts, vec![1, 2, 3]); // source, destination, authority indices
2876        assert_eq!(compiled.data, transfer_instruction.data);
2877    }
2878
2879    #[test]
2880    fn test_reconstruct_spl_token_transfer_checked_instruction() {
2881        let source = Pubkey::new_unique();
2882        let destination = Pubkey::new_unique();
2883        let authority = Pubkey::new_unique();
2884        let mint = Pubkey::new_unique();
2885        let token_program_id = spl_token_interface::ID;
2886        let account_keys = vec![token_program_id, source, mint, destination, authority];
2887        let amount = 2000000u64;
2888        let decimals = 6u8;
2889
2890        let instruction = spl_token_interface::instruction::transfer_checked(
2891            &spl_token_interface::ID,
2892            &source,
2893            &mint,
2894            &destination,
2895            &authority,
2896            &[],
2897            amount,
2898            decimals,
2899        )
2900        .expect("Failed to create transfer_checked instruction");
2901
2902        let solana_parsed = create_parsed_spl_token_transfer_checked(
2903            &source,
2904            &mint,
2905            &destination,
2906            &authority,
2907            amount,
2908            decimals,
2909        )
2910        .expect("Failed to create parsed instruction");
2911
2912        let result = IxUtils::reconstruct_spl_token_instruction(
2913            &solana_parsed,
2914            &IxUtils::build_account_keys_hashmap(&account_keys),
2915        );
2916
2917        assert!(result.is_ok());
2918        let compiled = result.unwrap();
2919        assert_eq!(compiled.program_id_index, 0);
2920        assert_eq!(compiled.accounts, vec![1, 2, 3, 4]); // source, mint, destination, authority indices
2921        assert_eq!(compiled.data, instruction.data);
2922    }
2923
2924    #[test]
2925    fn test_reconstruct_spl_token_burn_instruction() {
2926        let account = Pubkey::new_unique();
2927        let mint = Pubkey::new_unique();
2928        let authority = Pubkey::new_unique();
2929        let token_program_id = spl_token_interface::ID;
2930        let account_keys = vec![token_program_id, account, mint, authority];
2931        let amount = 500000u64;
2932
2933        let instruction = spl_token_interface::instruction::burn(
2934            &spl_token_interface::ID,
2935            &account,
2936            &mint,
2937            &authority,
2938            &[],
2939            amount,
2940        )
2941        .expect("Failed to create burn instruction");
2942
2943        let solana_parsed = create_parsed_spl_token_burn(&account, &mint, &authority, amount)
2944            .expect("Failed to create parsed instruction");
2945
2946        let result = IxUtils::reconstruct_spl_token_instruction(
2947            &solana_parsed,
2948            &IxUtils::build_account_keys_hashmap(&account_keys),
2949        );
2950
2951        assert!(result.is_ok());
2952        let compiled = result.unwrap();
2953        assert_eq!(compiled.program_id_index, 0);
2954        assert_eq!(compiled.accounts, vec![1, 3]); // account, authority indices (mint at index 2 is skipped)
2955        assert_eq!(compiled.data, instruction.data);
2956    }
2957
2958    #[test]
2959    fn test_reconstruct_spl_token_burn_checked_instruction() {
2960        let account = Pubkey::new_unique();
2961        let authority = Pubkey::new_unique();
2962        let mint = Pubkey::new_unique();
2963        let token_program_id = spl_token_interface::ID;
2964        let account_keys = vec![token_program_id, account, mint, authority];
2965        let amount = 750000u64;
2966        let decimals = 6u8;
2967
2968        let instruction = spl_token_interface::instruction::burn_checked(
2969            &spl_token_interface::ID,
2970            &account,
2971            &mint,
2972            &authority,
2973            &[],
2974            amount,
2975            decimals,
2976        )
2977        .expect("Failed to create burn_checked instruction");
2978
2979        let solana_parsed =
2980            create_parsed_spl_token_burn_checked(&account, &mint, &authority, amount, decimals)
2981                .expect("Failed to create parsed instruction");
2982
2983        let result = IxUtils::reconstruct_spl_token_instruction(
2984            &solana_parsed,
2985            &IxUtils::build_account_keys_hashmap(&account_keys),
2986        );
2987
2988        assert!(result.is_ok());
2989        let compiled = result.unwrap();
2990        assert_eq!(compiled.program_id_index, 0);
2991        assert_eq!(compiled.accounts, vec![1, 2, 3]); // account, mint, authority indices
2992        assert_eq!(compiled.data, instruction.data);
2993    }
2994
2995    #[test]
2996    fn test_reconstruct_spl_token_close_account_instruction() {
2997        let account = Pubkey::new_unique();
2998        let destination = Pubkey::new_unique();
2999        let authority = Pubkey::new_unique();
3000        let token_program_id = spl_token_interface::ID;
3001        let account_keys = vec![token_program_id, account, destination, authority];
3002
3003        let instruction = spl_token_interface::instruction::close_account(
3004            &spl_token_interface::ID,
3005            &account,
3006            &destination,
3007            &authority,
3008            &[],
3009        )
3010        .expect("Failed to create close_account instruction");
3011
3012        let solana_parsed =
3013            create_parsed_spl_token_close_account(&account, &destination, &authority)
3014                .expect("Failed to create parsed instruction");
3015
3016        let result = IxUtils::reconstruct_spl_token_instruction(
3017            &solana_parsed,
3018            &IxUtils::build_account_keys_hashmap(&account_keys),
3019        );
3020
3021        assert!(result.is_ok());
3022        let compiled = result.unwrap();
3023        assert_eq!(compiled.program_id_index, 0);
3024        assert_eq!(compiled.accounts, vec![1, 2, 3]); // account, destination, authority indices
3025        assert_eq!(compiled.data, instruction.data);
3026    }
3027
3028    #[test]
3029    fn test_reconstruct_spl_token_approve_instruction() {
3030        let source = Pubkey::new_unique();
3031        let delegate = Pubkey::new_unique();
3032        let owner = Pubkey::new_unique();
3033        let token_program_id = spl_token_interface::ID;
3034        let account_keys = vec![token_program_id, source, delegate, owner];
3035        let amount = 1000000u64;
3036
3037        let instruction = spl_token_interface::instruction::approve(
3038            &spl_token_interface::ID,
3039            &source,
3040            &delegate,
3041            &owner,
3042            &[],
3043            amount,
3044        )
3045        .expect("Failed to create approve instruction");
3046
3047        let solana_parsed = create_parsed_spl_token_approve(&source, &delegate, &owner, amount)
3048            .expect("Failed to create parsed instruction");
3049
3050        let result = IxUtils::reconstruct_spl_token_instruction(
3051            &solana_parsed,
3052            &IxUtils::build_account_keys_hashmap(&account_keys),
3053        );
3054
3055        assert!(result.is_ok());
3056        let compiled = result.unwrap();
3057        assert_eq!(compiled.program_id_index, 0);
3058        assert_eq!(compiled.accounts, vec![1, 2, 3]); // source, delegate, owner indices
3059        assert_eq!(compiled.data, instruction.data);
3060    }
3061
3062    #[test]
3063    fn test_reconstruct_spl_token_approve_checked_instruction() {
3064        let source = Pubkey::new_unique();
3065        let delegate = Pubkey::new_unique();
3066        let owner = Pubkey::new_unique();
3067        let mint = Pubkey::new_unique();
3068        let token_program_id = spl_token_interface::ID;
3069        let account_keys = vec![token_program_id, source, mint, delegate, owner];
3070        let amount = 2500000u64;
3071        let decimals = 6u8;
3072
3073        let instruction = spl_token_interface::instruction::approve_checked(
3074            &spl_token_interface::ID,
3075            &source,
3076            &mint,
3077            &delegate,
3078            &owner,
3079            &[],
3080            amount,
3081            decimals,
3082        )
3083        .expect("Failed to create approve_checked instruction");
3084
3085        let solana_parsed = create_parsed_spl_token_approve_checked(
3086            &source, &mint, &delegate, &owner, amount, decimals,
3087        )
3088        .expect("Failed to create parsed instruction");
3089
3090        let result = IxUtils::reconstruct_spl_token_instruction(
3091            &solana_parsed,
3092            &IxUtils::build_account_keys_hashmap(&account_keys),
3093        );
3094
3095        assert!(result.is_ok());
3096        let compiled = result.unwrap();
3097        assert_eq!(compiled.program_id_index, 0);
3098        assert_eq!(compiled.accounts, vec![1, 2, 3, 4]); // source, mint, delegate, owner indices
3099        assert_eq!(compiled.data, instruction.data);
3100    }
3101
3102    #[test]
3103    fn test_reconstruct_token2022_transfer_instruction() {
3104        let source = Pubkey::new_unique();
3105        let destination = Pubkey::new_unique();
3106        let authority = Pubkey::new_unique();
3107        let token_program_id = spl_token_2022_interface::ID;
3108        let account_keys = vec![token_program_id, source, destination, authority];
3109        let amount = 1500000u64;
3110
3111        #[allow(deprecated)]
3112        let instruction = spl_token_2022_interface::instruction::transfer(
3113            &spl_token_2022_interface::ID,
3114            &source,
3115            &destination,
3116            &authority,
3117            &[],
3118            amount,
3119        )
3120        .expect("Failed to create transfer instruction");
3121
3122        let solana_parsed =
3123            create_parsed_token2022_transfer(&source, &destination, &authority, amount)
3124                .expect("Failed to create parsed instruction");
3125
3126        let result = IxUtils::reconstruct_spl_token_instruction(
3127            &solana_parsed,
3128            &IxUtils::build_account_keys_hashmap(&account_keys),
3129        );
3130
3131        assert!(result.is_ok());
3132        let compiled = result.unwrap();
3133        assert_eq!(compiled.program_id_index, 0);
3134        assert_eq!(compiled.accounts, vec![1, 2, 3]); // source, destination, authority indices
3135        assert_eq!(compiled.data, instruction.data);
3136    }
3137
3138    #[test]
3139    fn test_reconstruct_token2022_transfer_checked_instruction() {
3140        let source = Pubkey::new_unique();
3141        let destination = Pubkey::new_unique();
3142        let authority = Pubkey::new_unique();
3143        let mint = Pubkey::new_unique();
3144        let token_program_id = spl_token_2022_interface::ID;
3145        let account_keys = vec![token_program_id, source, mint, destination, authority];
3146        let amount = 3000000u64;
3147        let decimals = 6u8;
3148
3149        let instruction = spl_token_2022_interface::instruction::transfer_checked(
3150            &spl_token_2022_interface::ID,
3151            &source,
3152            &mint,
3153            &destination,
3154            &authority,
3155            &[],
3156            amount,
3157            decimals,
3158        )
3159        .expect("Failed to create transfer_checked instruction");
3160
3161        let solana_parsed = create_parsed_token2022_transfer_checked(
3162            &source,
3163            &mint,
3164            &destination,
3165            &authority,
3166            amount,
3167            decimals,
3168        )
3169        .expect("Failed to create parsed instruction");
3170
3171        let result = IxUtils::reconstruct_spl_token_instruction(
3172            &solana_parsed,
3173            &IxUtils::build_account_keys_hashmap(&account_keys),
3174        );
3175
3176        assert!(result.is_ok());
3177        let compiled = result.unwrap();
3178        assert_eq!(compiled.program_id_index, 0);
3179        assert_eq!(compiled.accounts, vec![1, 2, 3, 4]); // source, mint, destination, authority indices
3180        assert_eq!(compiled.data, instruction.data);
3181    }
3182
3183    #[test]
3184    fn test_reconstruct_token2022_burn_instruction() {
3185        let account = Pubkey::new_unique();
3186        let mint = Pubkey::new_unique();
3187        let authority = Pubkey::new_unique();
3188        let token_program_id = spl_token_2022_interface::ID;
3189        let account_keys = vec![token_program_id, account, mint, authority];
3190        let amount = 800000u64;
3191
3192        let instruction = spl_token_2022_interface::instruction::burn(
3193            &spl_token_2022_interface::ID,
3194            &account,
3195            &mint,
3196            &authority,
3197            &[],
3198            amount,
3199        )
3200        .expect("Failed to create burn instruction");
3201
3202        let solana_parsed = create_parsed_token2022_burn(&account, &mint, &authority, amount)
3203            .expect("Failed to create parsed instruction");
3204
3205        let result = IxUtils::reconstruct_spl_token_instruction(
3206            &solana_parsed,
3207            &IxUtils::build_account_keys_hashmap(&account_keys),
3208        );
3209
3210        assert!(result.is_ok());
3211        let compiled = result.unwrap();
3212        assert_eq!(compiled.program_id_index, 0);
3213        assert_eq!(compiled.accounts, vec![1, 3]); // account, authority indices (mint at index 2 is skipped)
3214        assert_eq!(compiled.data, instruction.data);
3215    }
3216
3217    #[test]
3218    fn test_reconstruct_token2022_burn_checked_instruction() {
3219        let account = Pubkey::new_unique();
3220        let authority = Pubkey::new_unique();
3221        let mint = Pubkey::new_unique();
3222        let token_program_id = spl_token_2022_interface::ID;
3223        let account_keys = vec![token_program_id, account, mint, authority];
3224        let amount = 900000u64;
3225        let decimals = 6u8;
3226
3227        let instruction = spl_token_2022_interface::instruction::burn_checked(
3228            &spl_token_2022_interface::ID,
3229            &account,
3230            &mint,
3231            &authority,
3232            &[],
3233            amount,
3234            decimals,
3235        )
3236        .expect("Failed to create burn_checked instruction");
3237
3238        let solana_parsed =
3239            create_parsed_token2022_burn_checked(&account, &mint, &authority, amount, decimals)
3240                .expect("Failed to create parsed instruction");
3241
3242        let result = IxUtils::reconstruct_spl_token_instruction(
3243            &solana_parsed,
3244            &IxUtils::build_account_keys_hashmap(&account_keys),
3245        );
3246
3247        assert!(result.is_ok());
3248        let compiled = result.unwrap();
3249        assert_eq!(compiled.program_id_index, 0);
3250        assert_eq!(compiled.accounts, vec![1, 2, 3]); // account, mint, authority indices
3251        assert_eq!(compiled.data, instruction.data);
3252    }
3253
3254    #[test]
3255    fn test_reconstruct_token2022_close_account_instruction() {
3256        let account = Pubkey::new_unique();
3257        let destination = Pubkey::new_unique();
3258        let authority = Pubkey::new_unique();
3259        let token_program_id = spl_token_2022_interface::ID;
3260        let account_keys = vec![token_program_id, account, destination, authority];
3261
3262        let instruction = spl_token_2022_interface::instruction::close_account(
3263            &spl_token_2022_interface::ID,
3264            &account,
3265            &destination,
3266            &authority,
3267            &[],
3268        )
3269        .expect("Failed to create close_account instruction");
3270
3271        let solana_parsed =
3272            create_parsed_token2022_close_account(&account, &destination, &authority)
3273                .expect("Failed to create parsed instruction");
3274
3275        let result = IxUtils::reconstruct_spl_token_instruction(
3276            &solana_parsed,
3277            &IxUtils::build_account_keys_hashmap(&account_keys),
3278        );
3279
3280        assert!(result.is_ok());
3281        let compiled = result.unwrap();
3282        assert_eq!(compiled.program_id_index, 0);
3283        assert_eq!(compiled.accounts, vec![1, 2, 3]); // account, destination, authority indices
3284        assert_eq!(compiled.data, instruction.data);
3285    }
3286
3287    #[test]
3288    fn test_reconstruct_token2022_approve_instruction() {
3289        let source = Pubkey::new_unique();
3290        let delegate = Pubkey::new_unique();
3291        let owner = Pubkey::new_unique();
3292        let token_program_id = spl_token_2022_interface::ID;
3293        let account_keys = vec![token_program_id, source, delegate, owner];
3294        let amount = 1200000u64;
3295
3296        let instruction = spl_token_2022_interface::instruction::approve(
3297            &spl_token_2022_interface::ID,
3298            &source,
3299            &delegate,
3300            &owner,
3301            &[],
3302            amount,
3303        )
3304        .expect("Failed to create approve instruction");
3305
3306        let solana_parsed = create_parsed_token2022_approve(&source, &delegate, &owner, amount)
3307            .expect("Failed to create parsed instruction");
3308
3309        let result = IxUtils::reconstruct_spl_token_instruction(
3310            &solana_parsed,
3311            &IxUtils::build_account_keys_hashmap(&account_keys),
3312        );
3313
3314        assert!(result.is_ok());
3315        let compiled = result.unwrap();
3316        assert_eq!(compiled.program_id_index, 0);
3317        assert_eq!(compiled.accounts, vec![1, 2, 3]); // source, delegate, owner indices
3318        assert_eq!(compiled.data, instruction.data);
3319    }
3320
3321    #[test]
3322    fn test_reconstruct_token2022_approve_checked_instruction() {
3323        let source = Pubkey::new_unique();
3324        let delegate = Pubkey::new_unique();
3325        let owner = Pubkey::new_unique();
3326        let mint = Pubkey::new_unique();
3327        let token_program_id = spl_token_2022_interface::ID;
3328        let account_keys = vec![token_program_id, source, mint, delegate, owner];
3329        let amount = 3500000u64;
3330        let decimals = 6u8;
3331
3332        let instruction = spl_token_2022_interface::instruction::approve_checked(
3333            &spl_token_2022_interface::ID,
3334            &source,
3335            &mint,
3336            &delegate,
3337            &owner,
3338            &[],
3339            amount,
3340            decimals,
3341        )
3342        .expect("Failed to create approve_checked instruction");
3343
3344        let solana_parsed = create_parsed_token2022_approve_checked(
3345            &source, &mint, &delegate, &owner, amount, decimals,
3346        )
3347        .expect("Failed to create parsed instruction");
3348
3349        let result = IxUtils::reconstruct_spl_token_instruction(
3350            &solana_parsed,
3351            &IxUtils::build_account_keys_hashmap(&account_keys),
3352        );
3353
3354        assert!(result.is_ok());
3355        let compiled = result.unwrap();
3356        assert_eq!(compiled.program_id_index, 0);
3357        assert_eq!(compiled.accounts, vec![1, 2, 3, 4]); // source, mint, delegate, owner indices
3358        assert_eq!(compiled.data, instruction.data);
3359    }
3360
3361    #[test]
3362    fn test_reconstruct_unsupported_program_creates_stub() {
3363        let unsupported_program = Pubkey::new_unique();
3364        let account_keys = vec![unsupported_program];
3365
3366        let parsed_instruction = solana_transaction_status_client_types::ParsedInstruction {
3367            program: "unsupported".to_string(),
3368            program_id: unsupported_program.to_string(),
3369            parsed: serde_json::json!({
3370                "type": "unknownInstruction",
3371                "info": {
3372                    "someField": "someValue"
3373                }
3374            }),
3375            stack_height: None,
3376        };
3377
3378        let ui_instruction = UiInstruction::Parsed(UiParsedInstruction::Parsed(parsed_instruction));
3379
3380        let result = IxUtils::reconstruct_instruction_from_ui(&ui_instruction, &account_keys);
3381
3382        assert!(result.is_some());
3383        let compiled = result.unwrap();
3384
3385        assert_eq!(compiled.program_id_index, 0);
3386        assert!(compiled.accounts.is_empty());
3387        assert!(compiled.data.is_empty());
3388    }
3389}