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::{
10 UiInstruction, UiParsedInstruction, UiPartiallyDecodedInstruction,
11};
12
13use crate::{
14 constant::instruction_indexes, error::KoraError, sanitize_error,
15 transaction::VersionedTransactionResolved,
16};
17
18#[derive(Debug, Clone, PartialEq, Eq, Hash)]
20pub enum ParsedSystemInstructionType {
21 SystemTransfer,
22 SystemCreateAccount,
23 SystemWithdrawNonceAccount,
24 SystemAssign,
25 SystemAllocate,
26 SystemInitializeNonceAccount,
27 SystemAdvanceNonceAccount,
28 SystemAuthorizeNonceAccount,
29 }
31
32#[derive(Debug, Clone, PartialEq, Eq, Hash)]
34pub enum ParsedSystemInstructionData {
35 SystemTransfer { lamports: u64, sender: Pubkey, receiver: Pubkey },
37 SystemCreateAccount { lamports: u64, payer: Pubkey },
39 SystemWithdrawNonceAccount { lamports: u64, nonce_authority: Pubkey, recipient: Pubkey },
41 SystemAssign { authority: Pubkey },
43 SystemAllocate { account: Pubkey },
45 SystemInitializeNonceAccount { nonce_account: Pubkey, nonce_authority: Pubkey },
47 SystemAdvanceNonceAccount { nonce_account: Pubkey, nonce_authority: Pubkey },
49 SystemAuthorizeNonceAccount { nonce_account: Pubkey, nonce_authority: Pubkey },
51 }
53
54#[derive(Debug, Clone, PartialEq, Eq, Hash)]
55pub enum ParsedSPLInstructionType {
56 SplTokenTransfer,
57 SplTokenBurn,
58 SplTokenCloseAccount,
59 SplTokenApprove,
60 SplTokenRevoke,
61 SplTokenSetAuthority,
62 SplTokenMintTo,
63 SplTokenInitializeMint,
64 SplTokenInitializeAccount,
65 SplTokenInitializeMultisig,
66 SplTokenFreezeAccount,
67 SplTokenThawAccount,
68}
69
70#[derive(Debug, Clone, PartialEq, Eq, Hash)]
71pub enum ParsedSPLInstructionData {
72 SplTokenTransfer {
74 amount: u64,
75 owner: Pubkey,
76 mint: Option<Pubkey>,
77 source_address: Pubkey,
78 destination_address: Pubkey,
79 is_2022: bool,
80 },
81 SplTokenBurn {
83 owner: Pubkey,
84 is_2022: bool,
85 },
86 SplTokenCloseAccount {
88 owner: Pubkey,
89 is_2022: bool,
90 },
91 SplTokenApprove {
93 owner: Pubkey,
94 is_2022: bool,
95 },
96 SplTokenRevoke {
98 owner: Pubkey,
99 is_2022: bool,
100 },
101 SplTokenSetAuthority {
103 authority: Pubkey,
104 is_2022: bool,
105 },
106 SplTokenMintTo {
108 mint_authority: Pubkey,
109 is_2022: bool,
110 },
111 SplTokenInitializeMint {
113 mint_authority: Pubkey,
114 is_2022: bool,
115 },
116 SplTokenInitializeAccount {
118 owner: Pubkey,
119 is_2022: bool,
120 },
121 SplTokenInitializeMultisig {
123 signers: Vec<Pubkey>,
124 is_2022: bool,
125 },
126 SplTokenFreezeAccount {
128 freeze_authority: Pubkey,
129 is_2022: bool,
130 },
131 SplTokenThawAccount {
133 freeze_authority: Pubkey,
134 is_2022: bool,
135 },
136}
137
138macro_rules! validate_number_accounts {
141 ($instruction:expr, $min_count:expr) => {
142 if $instruction.accounts.len() < $min_count {
143 log::error!("Instruction {:?} has less than {} accounts", $instruction, $min_count);
144 return Err(KoraError::InvalidTransaction(format!(
145 "Instruction doesn't have the required number of accounts",
146 )));
147 }
148 };
149}
150
151macro_rules! parse_system_instruction {
154 ($parsed:ident, $ix:ident, $const_mod:ident, $enum_variant:ident, $data_variant:ident { $($field:ident: $account_path:expr),* $(,)? }) => {
156 validate_number_accounts!($ix, instruction_indexes::$const_mod::REQUIRED_NUMBER_OF_ACCOUNTS);
157 $parsed
158 .entry(ParsedSystemInstructionType::$enum_variant)
159 .or_default()
160 .push(ParsedSystemInstructionData::$data_variant {
161 $($field: $ix.accounts[$account_path].pubkey,)*
162 });
163 };
164 ($parsed:ident, $ix:ident, $const_mod:ident, $enum_variant:ident, $data_variant:ident { $($data_field:ident: $data_val:expr),* ; $($field:ident: $account_path:expr),* $(,)? }) => {
166 validate_number_accounts!($ix, instruction_indexes::$const_mod::REQUIRED_NUMBER_OF_ACCOUNTS);
167 $parsed
168 .entry(ParsedSystemInstructionType::$enum_variant)
169 .or_default()
170 .push(ParsedSystemInstructionData::$data_variant {
171 $($data_field: $data_val,)*
172 $($field: $ix.accounts[$account_path].pubkey,)*
173 });
174 };
175}
176
177pub struct IxUtils;
178
179pub const PARSED_DATA_FIELD_TYPE: &str = "type";
180pub const PARSED_DATA_FIELD_INFO: &str = "info";
181
182pub const PARSED_DATA_FIELD_SOURCE: &str = "source";
183pub const PARSED_DATA_FIELD_DESTINATION: &str = "destination";
184pub const PARSED_DATA_FIELD_OWNER: &str = "owner";
185
186pub const PARSED_DATA_FIELD_TRANSFER: &str = "transfer";
187pub const PARSED_DATA_FIELD_CREATE_ACCOUNT: &str = "createAccount";
188pub const PARSED_DATA_FIELD_ASSIGN: &str = "assign";
189pub const PARSED_DATA_FIELD_TRANSFER_WITH_SEED: &str = "transferWithSeed";
190pub const PARSED_DATA_FIELD_CREATE_ACCOUNT_WITH_SEED: &str = "createAccountWithSeed";
191pub const PARSED_DATA_FIELD_ASSIGN_WITH_SEED: &str = "assignWithSeed";
192pub const PARSED_DATA_FIELD_WITHDRAW_NONCE_ACCOUNT: &str = "withdrawFromNonce";
193pub const PARSED_DATA_FIELD_ALLOCATE: &str = "allocate";
194pub const PARSED_DATA_FIELD_ALLOCATE_WITH_SEED: &str = "allocateWithSeed";
195pub const PARSED_DATA_FIELD_INITIALIZE_NONCE_ACCOUNT: &str = "initializeNonce";
196pub const PARSED_DATA_FIELD_ADVANCE_NONCE_ACCOUNT: &str = "advanceNonce";
197pub const PARSED_DATA_FIELD_AUTHORIZE_NONCE_ACCOUNT: &str = "authorizeNonce";
198pub const PARSED_DATA_FIELD_BURN: &str = "burn";
199pub const PARSED_DATA_FIELD_BURN_CHECKED: &str = "burnChecked";
200pub const PARSED_DATA_FIELD_CLOSE_ACCOUNT: &str = "closeAccount";
201pub const PARSED_DATA_FIELD_TRANSFER_CHECKED: &str = "transferChecked";
202pub const PARSED_DATA_FIELD_APPROVE: &str = "approve";
203pub const PARSED_DATA_FIELD_APPROVE_CHECKED: &str = "approveChecked";
204
205pub const PARSED_DATA_FIELD_AMOUNT: &str = "amount";
206pub const PARSED_DATA_FIELD_LAMPORTS: &str = "lamports";
207pub const PARSED_DATA_FIELD_DECIMALS: &str = "decimals";
208pub const PARSED_DATA_FIELD_UI_AMOUNT: &str = "uiAmount";
209pub const PARSED_DATA_FIELD_UI_AMOUNT_STRING: &str = "uiAmountString";
210pub const PARSED_DATA_FIELD_TOKEN_AMOUNT: &str = "tokenAmount";
211pub const PARSED_DATA_FIELD_ACCOUNT: &str = "account";
212pub const PARSED_DATA_FIELD_NEW_ACCOUNT: &str = "newAccount";
213pub const PARSED_DATA_FIELD_AUTHORITY: &str = "authority";
214pub const PARSED_DATA_FIELD_MINT: &str = "mint";
215pub const PARSED_DATA_FIELD_SPACE: &str = "space";
216pub const PARSED_DATA_FIELD_DELEGATE: &str = "delegate";
217pub const PARSED_DATA_FIELD_BASE: &str = "base";
218pub const PARSED_DATA_FIELD_SEED: &str = "seed";
219pub const PARSED_DATA_FIELD_SOURCE_BASE: &str = "sourceBase";
220pub const PARSED_DATA_FIELD_SOURCE_SEED: &str = "sourceSeed";
221pub const PARSED_DATA_FIELD_SOURCE_OWNER: &str = "sourceOwner";
222pub const PARSED_DATA_FIELD_NONCE_ACCOUNT: &str = "nonceAccount";
223pub const PARSED_DATA_FIELD_RECIPIENT: &str = "recipient";
224pub const PARSED_DATA_FIELD_NONCE_AUTHORITY: &str = "nonceAuthority";
225pub const PARSED_DATA_FIELD_NEW_AUTHORITY: &str = "newAuthority";
226
227pub const PARSED_DATA_FIELD_REVOKE: &str = "revoke";
229pub const PARSED_DATA_FIELD_SET_AUTHORITY: &str = "setAuthority";
230pub const PARSED_DATA_FIELD_MINT_TO: &str = "mintTo";
231pub const PARSED_DATA_FIELD_MINT_TO_CHECKED: &str = "mintToChecked";
232pub const PARSED_DATA_FIELD_INITIALIZE_MINT: &str = "initializeMint";
233pub const PARSED_DATA_FIELD_INITIALIZE_MINT2: &str = "initializeMint2";
234pub const PARSED_DATA_FIELD_INITIALIZE_ACCOUNT: &str = "initializeAccount";
235pub const PARSED_DATA_FIELD_INITIALIZE_ACCOUNT2: &str = "initializeAccount2";
236pub const PARSED_DATA_FIELD_INITIALIZE_ACCOUNT3: &str = "initializeAccount3";
237pub const PARSED_DATA_FIELD_INITIALIZE_MULTISIG: &str = "initializeMultisig";
238pub const PARSED_DATA_FIELD_INITIALIZE_MULTISIG2: &str = "initializeMultisig2";
239pub const PARSED_DATA_FIELD_FREEZE_ACCOUNT: &str = "freezeAccount";
240pub const PARSED_DATA_FIELD_THAW_ACCOUNT: &str = "thawAccount";
241pub const PARSED_DATA_FIELD_GET_ACCOUNT_DATA_SIZE: &str = "getAccountDataSize";
242pub const PARSED_DATA_FIELD_INITIALIZE_IMMUTABLE_OWNER: &str = "initializeImmutableOwner";
243
244pub const PARSED_DATA_FIELD_MINT_AUTHORITY: &str = "mintAuthority";
246pub const PARSED_DATA_FIELD_FREEZE_AUTHORITY: &str = "freezeAuthority";
247pub const PARSED_DATA_FIELD_AUTHORITY_TYPE: &str = "authorityType";
248pub const PARSED_DATA_FIELD_MULTISIG_ACCOUNT: &str = "multisig";
249pub const PARSED_DATA_FIELD_SIGNERS: &str = "signers";
250
251impl IxUtils {
252 fn get_field_as_str<'a>(
254 info: &'a serde_json::Value,
255 field_name: &str,
256 ) -> Result<&'a str, KoraError> {
257 info.get(field_name)
258 .ok_or_else(|| {
259 KoraError::SerializationError(format!("Missing field '{}'", field_name))
260 })?
261 .as_str()
262 .ok_or_else(|| {
263 KoraError::SerializationError(format!("Field '{}' is not a string", field_name))
264 })
265 }
266
267 fn get_field_as_pubkey(
269 info: &serde_json::Value,
270 field_name: &str,
271 ) -> Result<Pubkey, KoraError> {
272 let pubkey_str = Self::get_field_as_str(info, field_name)?;
273 pubkey_str.parse::<Pubkey>().map_err(|e| {
274 KoraError::SerializationError(format!(
275 "Field '{}' is not a valid pubkey: {}",
276 field_name, e
277 ))
278 })
279 }
280
281 fn get_field_as_u64(info: &serde_json::Value, field_name: &str) -> Result<u64, KoraError> {
283 let value = info.get(field_name).ok_or_else(|| {
284 KoraError::SerializationError(format!("Missing field '{}'", field_name))
285 })?;
286
287 if let Some(num) = value.as_u64() {
289 return Ok(num);
290 }
291
292 if let Some(str_val) = value.as_str() {
294 return str_val.parse::<u64>().map_err(|e| {
295 KoraError::SerializationError(format!(
296 "Field '{}' is not a valid u64: {}",
297 field_name, e
298 ))
299 });
300 }
301
302 Err(KoraError::SerializationError(format!(
303 "Field '{}' is neither a number nor a string",
304 field_name
305 )))
306 }
307
308 fn get_account_index(
310 account_keys_hashmap: &HashMap<Pubkey, u8>,
311 pubkey: &Pubkey,
312 ) -> Result<u8, KoraError> {
313 account_keys_hashmap.get(pubkey).copied().ok_or_else(|| {
314 KoraError::SerializationError(format!("{} not found in account keys", pubkey))
315 })
316 }
317
318 pub fn build_account_keys_hashmap(account_keys: &[Pubkey]) -> HashMap<Pubkey, u8> {
319 account_keys.iter().enumerate().map(|(idx, key)| (*key, idx as u8)).collect()
320 }
321
322 fn decode_base58_instruction_data(
323 encoded_data: &str,
324 error_context: &str,
325 ) -> Result<Vec<u8>, KoraError> {
326 bs58::decode(encoded_data).into_vec().map_err(|e| {
327 KoraError::SerializationError(format!(
328 "Failed to decode {error_context} from base58: {}",
329 sanitize_error!(e)
330 ))
331 })
332 }
333
334 fn get_or_insert_account_index(
335 account_keys_hashmap: &mut HashMap<Pubkey, u8>,
336 all_account_keys: &mut Vec<Pubkey>,
337 pubkey: Pubkey,
338 overflow_context: &str,
339 inserted_keys: &mut Vec<Pubkey>,
340 ) -> Result<u8, KoraError> {
341 if let Some(&idx) = account_keys_hashmap.get(&pubkey) {
342 return Ok(idx);
343 }
344
345 let idx = u8::try_from(all_account_keys.len()).map_err(|_| {
346 KoraError::SerializationError(format!(
347 "{overflow_context} exceeds u8 index limit (len={})",
348 all_account_keys.len()
349 ))
350 })?;
351
352 all_account_keys.push(pubkey);
353 account_keys_hashmap.insert(pubkey, idx);
354 inserted_keys.push(pubkey);
355
356 Ok(idx)
357 }
358
359 fn rollback_account_key_extensions(
360 account_keys_hashmap: &mut HashMap<Pubkey, u8>,
361 all_account_keys: &mut Vec<Pubkey>,
362 snapshot_len: usize,
363 inserted_keys: &[Pubkey],
364 ) {
365 all_account_keys.truncate(snapshot_len);
366 for key in inserted_keys {
367 account_keys_hashmap.remove(key);
368 }
369 }
370
371 pub fn get_account_key_if_present(ix: &Instruction, index: usize) -> Option<Pubkey> {
372 if ix.accounts.is_empty() {
373 return None;
374 }
375
376 if index >= ix.accounts.len() {
377 return None;
378 }
379
380 Some(ix.accounts[index].pubkey)
381 }
382
383 pub fn get_account_key_required(
384 account_keys: &[Pubkey],
385 index: usize,
386 ) -> Result<Pubkey, KoraError> {
387 account_keys.get(index).copied().ok_or_else(|| {
388 KoraError::SerializationError(format!("Account key at index {} not found", index))
389 })
390 }
391
392 fn parse_burn_owner_with_mint_fallback(instruction: &Instruction) -> Result<Pubkey, KoraError> {
393 if instruction.accounts.len()
397 >= instruction_indexes::spl_token_burn::REQUIRED_NUMBER_OF_ACCOUNTS
398 {
399 return Ok(
400 instruction.accounts[instruction_indexes::spl_token_burn::OWNER_INDEX].pubkey
401 );
402 }
403
404 if instruction.accounts.len() == 2 {
405 log::debug!(
406 "Burn instruction has 2 accounts (reconstructed without mint), using index 1 as authority"
407 );
408 return Ok(instruction.accounts[1].pubkey);
409 }
410
411 log::error!("Burn instruction has less than 2 accounts: {:?}", instruction);
412 Err(KoraError::InvalidTransaction(
413 "Burn instruction doesn't have the required number of accounts".to_string(),
414 ))
415 }
416
417 fn extract_multisig_signers(instruction: &Instruction, skip: usize) -> Vec<Pubkey> {
418 instruction.accounts.iter().skip(skip).map(|a| a.pubkey).collect()
419 }
420
421 fn push_parsed_spl_instruction(
422 parsed_instructions: &mut HashMap<ParsedSPLInstructionType, Vec<ParsedSPLInstructionData>>,
423 instruction_type: ParsedSPLInstructionType,
424 instruction_data: ParsedSPLInstructionData,
425 ) {
426 parsed_instructions.entry(instruction_type).or_default().push(instruction_data);
427 }
428
429 pub fn build_default_compiled_instruction(program_id_index: u8) -> CompiledInstruction {
430 CompiledInstruction { program_id_index, accounts: vec![], data: vec![] }
431 }
432
433 pub fn uncompile_instructions(
434 instructions: &[CompiledInstruction],
435 account_keys: &[Pubkey],
436 ) -> Result<Vec<Instruction>, KoraError> {
437 instructions
438 .iter()
439 .map(|ix| {
440 let program_id =
441 Self::get_account_key_required(account_keys, ix.program_id_index as usize)?;
442 let accounts: Result<Vec<AccountMeta>, KoraError> = ix
443 .accounts
444 .iter()
445 .map(|idx| {
446 Ok(AccountMeta {
447 pubkey: Self::get_account_key_required(account_keys, *idx as usize)?,
448 is_signer: false,
449 is_writable: true,
450 })
451 })
452 .collect();
453
454 Ok(Instruction { program_id, accounts: accounts?, data: ix.data.clone() })
455 })
456 .collect()
457 }
458
459 pub fn reconstruct_instruction_from_ui(
472 ui_instruction: &UiInstruction,
473 all_account_keys: &mut Vec<Pubkey>,
474 ) -> Result<CompiledInstruction, KoraError> {
475 let mut account_keys_hashmap = Self::build_account_keys_hashmap(all_account_keys);
476 Self::reconstruct_instruction_from_ui_with_account_key_cache(
477 ui_instruction,
478 all_account_keys,
479 &mut account_keys_hashmap,
480 )
481 }
482
483 pub fn reconstruct_instruction_from_ui_with_account_key_cache(
484 ui_instruction: &UiInstruction,
485 all_account_keys: &mut Vec<Pubkey>,
486 account_keys_hashmap: &mut HashMap<Pubkey, u8>,
487 ) -> Result<CompiledInstruction, KoraError> {
488 match ui_instruction {
489 UiInstruction::Compiled(compiled) => {
490 let data = Self::decode_base58_instruction_data(
491 &compiled.data,
492 "compiled instruction data",
493 )?;
494
495 Ok(CompiledInstruction {
496 program_id_index: compiled.program_id_index,
497 accounts: compiled.accounts.clone(),
498 data,
499 })
500 }
501 UiInstruction::Parsed(ui_parsed) => Self::reconstruct_parsed_instruction(
502 ui_parsed,
503 all_account_keys,
504 account_keys_hashmap,
505 ),
506 }
507 }
508
509 fn reconstruct_parsed_instruction(
510 ui_parsed: &UiParsedInstruction,
511 all_account_keys: &mut Vec<Pubkey>,
512 account_keys_hashmap: &mut HashMap<Pubkey, u8>,
513 ) -> Result<CompiledInstruction, KoraError> {
514 match ui_parsed {
515 UiParsedInstruction::Parsed(parsed) => {
516 if parsed.program_id == SYSTEM_PROGRAM_ID.to_string() {
518 Self::reconstruct_system_instruction(parsed, account_keys_hashmap)
519 } else if parsed.program_id == spl_token_interface::ID.to_string()
520 || parsed.program_id == spl_token_2022_interface::ID.to_string()
521 {
522 Self::reconstruct_spl_token_instruction(parsed, account_keys_hashmap)
523 } else {
524 let program_id = parsed.program_id.parse::<Pubkey>().map_err(|e| {
527 KoraError::SerializationError(format!(
528 "Invalid parsed instruction program_id '{}': {}",
529 parsed.program_id,
530 sanitize_error!(e)
531 ))
532 })?;
533 let program_id_index = *account_keys_hashmap.get(&program_id).ok_or_else(|| {
534 KoraError::SerializationError(format!(
535 "Unsupported parsed instruction program_id {} not found in account keys",
536 program_id
537 ))
538 })?;
539
540 Ok(Self::build_default_compiled_instruction(program_id_index))
541 }
542 }
543 UiParsedInstruction::PartiallyDecoded(partial) => {
544 Self::reconstruct_partially_decoded_instruction(
545 partial,
546 all_account_keys,
547 account_keys_hashmap,
548 )
549 }
550 }
551 }
552
553 fn reconstruct_partially_decoded_instruction(
554 partial: &UiPartiallyDecodedInstruction,
555 all_account_keys: &mut Vec<Pubkey>,
556 account_keys_hashmap: &mut HashMap<Pubkey, u8>,
557 ) -> Result<CompiledInstruction, KoraError> {
558 let snapshot_len = all_account_keys.len();
559 let mut inserted_keys = Vec::new();
560 let result = (|| -> Result<CompiledInstruction, KoraError> {
561 let program_id = partial.program_id.parse::<Pubkey>().map_err(|e| {
562 KoraError::SerializationError(format!(
563 "Invalid partially decoded instruction program_id '{}': {}",
564 partial.program_id,
565 sanitize_error!(e)
566 ))
567 })?;
568
569 let program_idx = Self::get_or_insert_account_index(
572 account_keys_hashmap,
573 all_account_keys,
574 program_id,
575 "CPI program key",
576 &mut inserted_keys,
577 )?;
578
579 let mut account_indices: Vec<u8> = Vec::with_capacity(partial.accounts.len());
582 for addr_str in &partial.accounts {
583 let pubkey = addr_str.parse::<Pubkey>().map_err(|e| {
584 KoraError::SerializationError(format!(
585 "Invalid partially decoded instruction account '{}': {}",
586 addr_str,
587 sanitize_error!(e)
588 ))
589 })?;
590
591 let idx = Self::get_or_insert_account_index(
594 account_keys_hashmap,
595 all_account_keys,
596 pubkey,
597 "CPI account key",
598 &mut inserted_keys,
599 )?;
600 account_indices.push(idx);
601 }
602
603 let data = Self::decode_base58_instruction_data(
604 &partial.data,
605 "partially decoded instruction data",
606 )?;
607
608 Ok(CompiledInstruction {
609 program_id_index: program_idx,
610 accounts: account_indices,
611 data,
612 })
613 })();
614
615 if result.is_err() {
616 Self::rollback_account_key_extensions(
617 account_keys_hashmap,
618 all_account_keys,
619 snapshot_len,
620 &inserted_keys,
621 );
622 }
623
624 result
625 }
626
627 fn reconstruct_system_instruction(
629 parsed: &solana_transaction_status_client_types::ParsedInstruction,
630 account_keys_hashmap: &HashMap<Pubkey, u8>,
631 ) -> Result<CompiledInstruction, KoraError> {
632 let program_id_index = Self::get_account_index(account_keys_hashmap, &SYSTEM_PROGRAM_ID)?;
633
634 let parsed_data = &parsed.parsed;
635 let instruction_type = Self::get_field_as_str(parsed_data, PARSED_DATA_FIELD_TYPE)?;
636 let info = parsed_data
637 .get(PARSED_DATA_FIELD_INFO)
638 .ok_or_else(|| KoraError::SerializationError("Missing 'info' field".to_string()))?;
639
640 match instruction_type {
641 PARSED_DATA_FIELD_TRANSFER => {
642 let source = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_SOURCE)?;
643 let destination = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_DESTINATION)?;
644 let lamports = Self::get_field_as_u64(info, PARSED_DATA_FIELD_LAMPORTS)?;
645
646 let source_idx = Self::get_account_index(account_keys_hashmap, &source)?;
647 let destination_idx = Self::get_account_index(account_keys_hashmap, &destination)?;
648
649 let transfer_ix = SystemInstruction::Transfer { lamports };
650 let data = bincode::serialize(&transfer_ix).map_err(|e| {
651 KoraError::SerializationError(format!(
652 "Failed to serialize Transfer instruction: {}",
653 e
654 ))
655 })?;
656
657 Ok(CompiledInstruction {
658 program_id_index,
659 accounts: vec![source_idx, destination_idx],
660 data,
661 })
662 }
663 PARSED_DATA_FIELD_CREATE_ACCOUNT => {
664 let source = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_SOURCE)?;
665 let new_account = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_NEW_ACCOUNT)?;
666 let owner = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_OWNER)?;
667 let lamports = Self::get_field_as_u64(info, PARSED_DATA_FIELD_LAMPORTS)?;
668 let space = Self::get_field_as_u64(info, PARSED_DATA_FIELD_SPACE)?;
669
670 let source_idx = Self::get_account_index(account_keys_hashmap, &source)?;
671 let new_account_idx = Self::get_account_index(account_keys_hashmap, &new_account)?;
672
673 let create_ix = SystemInstruction::CreateAccount { lamports, space, owner };
674 let data = bincode::serialize(&create_ix).map_err(|e| {
675 KoraError::SerializationError(format!(
676 "Failed to serialize CreateAccount instruction: {}",
677 e
678 ))
679 })?;
680
681 Ok(CompiledInstruction {
682 program_id_index,
683 accounts: vec![source_idx, new_account_idx],
684 data,
685 })
686 }
687 PARSED_DATA_FIELD_ASSIGN => {
688 let authority = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_ACCOUNT)?;
689 let owner = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_OWNER)?;
690
691 let authority_idx = Self::get_account_index(account_keys_hashmap, &authority)?;
692
693 let assign_ix = SystemInstruction::Assign { owner };
694 let data = bincode::serialize(&assign_ix).map_err(|e| {
695 KoraError::SerializationError(format!(
696 "Failed to serialize Assign instruction: {}",
697 e
698 ))
699 })?;
700
701 Ok(CompiledInstruction { program_id_index, accounts: vec![authority_idx], data })
702 }
703 PARSED_DATA_FIELD_TRANSFER_WITH_SEED => {
704 let source = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_SOURCE)?;
705 let destination = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_DESTINATION)?;
706 let lamports = Self::get_field_as_u64(info, PARSED_DATA_FIELD_LAMPORTS)?;
707 let source_base = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_SOURCE_BASE)?;
708 let source_seed =
709 Self::get_field_as_str(info, PARSED_DATA_FIELD_SOURCE_SEED)?.to_string();
710 let source_owner = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_SOURCE_OWNER)?;
711
712 let source_idx = Self::get_account_index(account_keys_hashmap, &source)?;
713 let destination_idx = Self::get_account_index(account_keys_hashmap, &destination)?;
714 let source_base_idx = Self::get_account_index(account_keys_hashmap, &source_base)?;
715
716 let transfer_ix = SystemInstruction::TransferWithSeed {
717 lamports,
718 from_seed: source_seed,
719 from_owner: source_owner,
720 };
721 let data = bincode::serialize(&transfer_ix).map_err(|e| {
722 KoraError::SerializationError(format!(
723 "Failed to serialize TransferWithSeed instruction: {}",
724 e
725 ))
726 })?;
727
728 Ok(CompiledInstruction {
729 program_id_index,
730 accounts: vec![source_idx, source_base_idx, destination_idx],
731 data,
732 })
733 }
734 PARSED_DATA_FIELD_CREATE_ACCOUNT_WITH_SEED => {
735 let source = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_SOURCE)?;
736 let new_account = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_NEW_ACCOUNT)?;
737 let base = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_BASE)?;
738 let seed = Self::get_field_as_str(info, PARSED_DATA_FIELD_SEED)?.to_string();
739 let owner = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_OWNER)?;
740 let lamports = Self::get_field_as_u64(info, PARSED_DATA_FIELD_LAMPORTS)?;
741 let space = Self::get_field_as_u64(info, PARSED_DATA_FIELD_SPACE)?;
742
743 let source_idx = Self::get_account_index(account_keys_hashmap, &source)?;
744 let new_account_idx = Self::get_account_index(account_keys_hashmap, &new_account)?;
745 let base_idx = Self::get_account_index(account_keys_hashmap, &base)?;
746
747 let create_ix =
748 SystemInstruction::CreateAccountWithSeed { base, seed, lamports, space, owner };
749 let data = bincode::serialize(&create_ix).map_err(|e| {
750 KoraError::SerializationError(format!(
751 "Failed to serialize CreateAccountWithSeed instruction: {}",
752 e
753 ))
754 })?;
755
756 Ok(CompiledInstruction {
757 program_id_index,
758 accounts: vec![source_idx, new_account_idx, base_idx],
759 data,
760 })
761 }
762 PARSED_DATA_FIELD_ASSIGN_WITH_SEED => {
763 let account = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_ACCOUNT)?;
764 let base = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_BASE)?;
765 let seed = Self::get_field_as_str(info, PARSED_DATA_FIELD_SEED)?.to_string();
766 let owner = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_OWNER)?;
767
768 let account_idx = Self::get_account_index(account_keys_hashmap, &account)?;
769 let base_idx = Self::get_account_index(account_keys_hashmap, &base)?;
770
771 let assign_ix = SystemInstruction::AssignWithSeed { base, seed, owner };
772 let data = bincode::serialize(&assign_ix).map_err(|e| {
773 KoraError::SerializationError(format!(
774 "Failed to serialize AssignWithSeed instruction: {}",
775 e
776 ))
777 })?;
778
779 Ok(CompiledInstruction {
780 program_id_index,
781 accounts: vec![account_idx, base_idx],
782 data,
783 })
784 }
785 PARSED_DATA_FIELD_WITHDRAW_NONCE_ACCOUNT => {
786 let nonce_account =
787 Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_NONCE_ACCOUNT)?;
788 let recipient = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_DESTINATION)?;
789 let nonce_authority =
790 Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_NONCE_AUTHORITY)?;
791 let lamports = Self::get_field_as_u64(info, PARSED_DATA_FIELD_LAMPORTS)?;
792
793 let nonce_account_idx =
794 Self::get_account_index(account_keys_hashmap, &nonce_account)?;
795 let recipient_idx = Self::get_account_index(account_keys_hashmap, &recipient)?;
796 let nonce_authority_idx =
797 Self::get_account_index(account_keys_hashmap, &nonce_authority)?;
798
799 let withdraw_ix = SystemInstruction::WithdrawNonceAccount(lamports);
800 let data = bincode::serialize(&withdraw_ix).map_err(|e| {
801 KoraError::SerializationError(format!(
802 "Failed to serialize WithdrawNonceAccount instruction: {}",
803 e
804 ))
805 })?;
806
807 Ok(CompiledInstruction {
808 program_id_index,
809 accounts: vec![nonce_account_idx, recipient_idx, nonce_authority_idx],
810 data,
811 })
812 }
813 PARSED_DATA_FIELD_ALLOCATE => {
814 let account = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_ACCOUNT)?;
815 let space = Self::get_field_as_u64(info, PARSED_DATA_FIELD_SPACE)?;
816
817 let account_idx = Self::get_account_index(account_keys_hashmap, &account)?;
818
819 let allocate_ix = SystemInstruction::Allocate { space };
820 let data = bincode::serialize(&allocate_ix).map_err(|e| {
821 KoraError::SerializationError(format!(
822 "Failed to serialize Allocate instruction: {}",
823 e
824 ))
825 })?;
826
827 Ok(CompiledInstruction { program_id_index, accounts: vec![account_idx], data })
828 }
829 PARSED_DATA_FIELD_ALLOCATE_WITH_SEED => {
830 let account = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_ACCOUNT)?;
831 let base = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_BASE)?;
832 let seed = Self::get_field_as_str(info, PARSED_DATA_FIELD_SEED)?.to_string();
833 let space = Self::get_field_as_u64(info, PARSED_DATA_FIELD_SPACE)?;
834 let owner = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_OWNER)?;
835
836 let account_idx = Self::get_account_index(account_keys_hashmap, &account)?;
837 let base_idx = Self::get_account_index(account_keys_hashmap, &base)?;
838
839 let allocate_ix = SystemInstruction::AllocateWithSeed { base, seed, space, owner };
840 let data = bincode::serialize(&allocate_ix).map_err(|e| {
841 KoraError::SerializationError(format!(
842 "Failed to serialize AllocateWithSeed instruction: {}",
843 e
844 ))
845 })?;
846
847 Ok(CompiledInstruction {
848 program_id_index,
849 accounts: vec![account_idx, base_idx],
850 data,
851 })
852 }
853 PARSED_DATA_FIELD_INITIALIZE_NONCE_ACCOUNT => {
854 let nonce_account =
855 Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_NONCE_ACCOUNT)?;
856 let nonce_authority =
857 Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_NONCE_AUTHORITY)?;
858
859 let nonce_account_idx =
860 Self::get_account_index(account_keys_hashmap, &nonce_account)?;
861
862 let initialize_ix = SystemInstruction::InitializeNonceAccount(nonce_authority);
863 let data = bincode::serialize(&initialize_ix).map_err(|e| {
864 KoraError::SerializationError(format!(
865 "Failed to serialize InitializeNonceAccount instruction: {}",
866 e
867 ))
868 })?;
869
870 Ok(CompiledInstruction {
873 program_id_index,
874 accounts: vec![nonce_account_idx],
875 data,
876 })
877 }
878 PARSED_DATA_FIELD_ADVANCE_NONCE_ACCOUNT => {
879 let nonce_account =
880 Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_NONCE_ACCOUNT)?;
881 let nonce_authority =
882 Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_NONCE_AUTHORITY)?;
883
884 let nonce_account_idx =
885 Self::get_account_index(account_keys_hashmap, &nonce_account)?;
886 let nonce_authority_idx =
887 Self::get_account_index(account_keys_hashmap, &nonce_authority)?;
888
889 let advance_ix = SystemInstruction::AdvanceNonceAccount;
890 let data = bincode::serialize(&advance_ix).map_err(|e| {
891 KoraError::SerializationError(format!(
892 "Failed to serialize AdvanceNonceAccount instruction: {}",
893 e
894 ))
895 })?;
896
897 Ok(CompiledInstruction {
900 program_id_index,
901 accounts: vec![nonce_account_idx, nonce_authority_idx],
902 data,
903 })
904 }
905 PARSED_DATA_FIELD_AUTHORIZE_NONCE_ACCOUNT => {
906 let nonce_account =
907 Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_NONCE_ACCOUNT)?;
908 let nonce_authority =
909 Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_NONCE_AUTHORITY)?;
910 let new_authority =
911 Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_NEW_AUTHORITY)?;
912
913 let nonce_account_idx =
914 Self::get_account_index(account_keys_hashmap, &nonce_account)?;
915 let nonce_authority_idx =
916 Self::get_account_index(account_keys_hashmap, &nonce_authority)?;
917
918 let authorize_ix = SystemInstruction::AuthorizeNonceAccount(new_authority);
919 let data = bincode::serialize(&authorize_ix).map_err(|e| {
920 KoraError::SerializationError(format!(
921 "Failed to serialize AuthorizeNonceAccount instruction: {}",
922 e
923 ))
924 })?;
925
926 Ok(CompiledInstruction {
928 program_id_index,
929 accounts: vec![nonce_account_idx, nonce_authority_idx],
930 data,
931 })
932 }
933 _ => {
934 Err(KoraError::InvalidTransaction(format!(
935 "Unrecognized system instruction type '{}' in CPI — cannot validate fee payer policy",
936 instruction_type
937 )))
938 }
939 }
940 }
941
942 fn reconstruct_spl_token_instruction(
944 parsed: &solana_transaction_status_client_types::ParsedInstruction,
945 account_keys_hashmap: &HashMap<Pubkey, u8>,
946 ) -> Result<CompiledInstruction, KoraError> {
947 let program_id = parsed.program_id.parse::<Pubkey>().map_err(|e| {
948 KoraError::SerializationError(format!("Invalid program ID: {}", sanitize_error!(e)))
949 })?;
950 let program_id_index = Self::get_account_index(account_keys_hashmap, &program_id)?;
951 let is_spl_token_program = program_id == spl_token_interface::ID;
952
953 let parsed_data = &parsed.parsed;
954 let instruction_type = Self::get_field_as_str(parsed_data, PARSED_DATA_FIELD_TYPE)?;
955 let info = parsed_data
956 .get(PARSED_DATA_FIELD_INFO)
957 .ok_or_else(|| KoraError::SerializationError("Missing 'info' field".to_string()))?;
958
959 match instruction_type {
960 PARSED_DATA_FIELD_TRANSFER => {
961 let source = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_SOURCE)?;
962 let destination = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_DESTINATION)?;
963 let authority = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_AUTHORITY)?;
964 let amount = Self::get_field_as_u64(info, PARSED_DATA_FIELD_AMOUNT)?;
965
966 let source_idx = Self::get_account_index(account_keys_hashmap, &source)?;
967 let destination_idx = Self::get_account_index(account_keys_hashmap, &destination)?;
968 let authority_idx = Self::get_account_index(account_keys_hashmap, &authority)?;
969
970 let data = if is_spl_token_program {
971 spl_token_interface::instruction::TokenInstruction::Transfer { amount }.pack()
972 } else {
973 #[allow(deprecated)]
974 spl_token_2022_interface::instruction::TokenInstruction::Transfer { amount }
975 .pack()
976 };
977
978 Ok(CompiledInstruction {
979 program_id_index,
980 accounts: vec![source_idx, destination_idx, authority_idx],
981 data,
982 })
983 }
984 PARSED_DATA_FIELD_TRANSFER_CHECKED => {
985 let source = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_SOURCE)?;
986 let destination = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_DESTINATION)?;
987 let authority = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_AUTHORITY)?;
988 let mint = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_MINT)?;
989
990 let token_amount = info.get(PARSED_DATA_FIELD_TOKEN_AMOUNT).ok_or_else(|| {
991 KoraError::SerializationError("Missing 'tokenAmount' field".to_string())
992 })?;
993 let amount = Self::get_field_as_u64(token_amount, PARSED_DATA_FIELD_AMOUNT)?;
994 let decimals =
995 Self::get_field_as_u64(token_amount, PARSED_DATA_FIELD_DECIMALS)? as u8;
996
997 let source_idx = Self::get_account_index(account_keys_hashmap, &source)?;
998 let mint_idx = Self::get_account_index(account_keys_hashmap, &mint)?;
999 let destination_idx = Self::get_account_index(account_keys_hashmap, &destination)?;
1000 let authority_idx = Self::get_account_index(account_keys_hashmap, &authority)?;
1001
1002 let data = if is_spl_token_program {
1003 spl_token_interface::instruction::TokenInstruction::TransferChecked {
1004 amount,
1005 decimals,
1006 }
1007 .pack()
1008 } else {
1009 spl_token_2022_interface::instruction::TokenInstruction::TransferChecked {
1010 amount,
1011 decimals,
1012 }
1013 .pack()
1014 };
1015
1016 Ok(CompiledInstruction {
1017 program_id_index,
1018 accounts: vec![source_idx, mint_idx, destination_idx, authority_idx],
1019 data,
1020 })
1021 }
1022 PARSED_DATA_FIELD_BURN | PARSED_DATA_FIELD_BURN_CHECKED => {
1023 let account = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_ACCOUNT)?;
1024 let authority = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_AUTHORITY)?;
1025
1026 let (amount, decimals) = if instruction_type == PARSED_DATA_FIELD_BURN_CHECKED {
1027 let token_amount =
1028 info.get(PARSED_DATA_FIELD_TOKEN_AMOUNT).ok_or_else(|| {
1029 KoraError::SerializationError(
1030 "Missing 'tokenAmount' field for burnChecked".to_string(),
1031 )
1032 })?;
1033 let amount = Self::get_field_as_u64(token_amount, PARSED_DATA_FIELD_AMOUNT)?;
1034 let decimals =
1035 Self::get_field_as_u64(token_amount, PARSED_DATA_FIELD_DECIMALS)? as u8;
1036 (amount, Some(decimals))
1037 } else {
1038 let amount =
1039 Self::get_field_as_u64(info, PARSED_DATA_FIELD_AMOUNT).unwrap_or(0);
1040 (amount, None)
1041 };
1042
1043 let account_idx = Self::get_account_index(account_keys_hashmap, &account)?;
1044 let authority_idx = Self::get_account_index(account_keys_hashmap, &authority)?;
1045
1046 let accounts = if instruction_type == PARSED_DATA_FIELD_BURN_CHECKED {
1047 let mint = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_MINT)?;
1048 let mint_idx = Self::get_account_index(account_keys_hashmap, &mint)?;
1049 vec![account_idx, mint_idx, authority_idx]
1050 } else if let Ok(mint) = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_MINT) {
1051 if let Ok(mint_idx) = Self::get_account_index(account_keys_hashmap, &mint) {
1054 vec![account_idx, mint_idx, authority_idx]
1055 } else {
1056 vec![account_idx, authority_idx]
1057 }
1058 } else {
1059 vec![account_idx, authority_idx]
1061 };
1062
1063 let data = if instruction_type == PARSED_DATA_FIELD_BURN_CHECKED {
1064 let decimals = decimals.unwrap(); if is_spl_token_program {
1066 spl_token_interface::instruction::TokenInstruction::BurnChecked {
1067 amount,
1068 decimals,
1069 }
1070 .pack()
1071 } else {
1072 spl_token_2022_interface::instruction::TokenInstruction::BurnChecked {
1073 amount,
1074 decimals,
1075 }
1076 .pack()
1077 }
1078 } else if is_spl_token_program {
1079 spl_token_interface::instruction::TokenInstruction::Burn { amount }.pack()
1080 } else {
1081 spl_token_2022_interface::instruction::TokenInstruction::Burn { amount }.pack()
1082 };
1083
1084 Ok(CompiledInstruction { program_id_index, accounts, data })
1085 }
1086 PARSED_DATA_FIELD_CLOSE_ACCOUNT => {
1087 let account = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_ACCOUNT)?;
1088 let destination = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_DESTINATION)?;
1089 let authority = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_OWNER)?;
1090
1091 let account_idx = Self::get_account_index(account_keys_hashmap, &account)?;
1092 let destination_idx = Self::get_account_index(account_keys_hashmap, &destination)?;
1093 let authority_idx = Self::get_account_index(account_keys_hashmap, &authority)?;
1094
1095 let data = if is_spl_token_program {
1096 spl_token_interface::instruction::TokenInstruction::CloseAccount.pack()
1097 } else {
1098 spl_token_2022_interface::instruction::TokenInstruction::CloseAccount.pack()
1099 };
1100
1101 Ok(CompiledInstruction {
1102 program_id_index,
1103 accounts: vec![account_idx, destination_idx, authority_idx],
1104 data,
1105 })
1106 }
1107 PARSED_DATA_FIELD_APPROVE => {
1108 let source = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_SOURCE)?;
1109 let delegate = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_DELEGATE)?;
1110 let owner = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_OWNER)?;
1111 let amount = Self::get_field_as_u64(info, PARSED_DATA_FIELD_AMOUNT)?;
1112
1113 let source_idx = Self::get_account_index(account_keys_hashmap, &source)?;
1114 let delegate_idx = Self::get_account_index(account_keys_hashmap, &delegate)?;
1115 let owner_idx = Self::get_account_index(account_keys_hashmap, &owner)?;
1116
1117 let data = if is_spl_token_program {
1118 spl_token_interface::instruction::TokenInstruction::Approve { amount }.pack()
1119 } else {
1120 spl_token_2022_interface::instruction::TokenInstruction::Approve { amount }
1121 .pack()
1122 };
1123
1124 Ok(CompiledInstruction {
1125 program_id_index,
1126 accounts: vec![source_idx, delegate_idx, owner_idx],
1127 data,
1128 })
1129 }
1130 PARSED_DATA_FIELD_APPROVE_CHECKED => {
1131 let source = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_SOURCE)?;
1132 let delegate = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_DELEGATE)?;
1133 let owner = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_OWNER)?;
1134 let mint = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_MINT)?;
1135
1136 let token_amount = info.get(PARSED_DATA_FIELD_TOKEN_AMOUNT).ok_or_else(|| {
1137 KoraError::SerializationError("Missing 'tokenAmount' field".to_string())
1138 })?;
1139 let amount = Self::get_field_as_u64(token_amount, PARSED_DATA_FIELD_AMOUNT)?;
1140 let decimals =
1141 Self::get_field_as_u64(token_amount, PARSED_DATA_FIELD_DECIMALS)? as u8;
1142
1143 let source_idx = Self::get_account_index(account_keys_hashmap, &source)?;
1144 let mint_idx = Self::get_account_index(account_keys_hashmap, &mint)?;
1145 let delegate_idx = Self::get_account_index(account_keys_hashmap, &delegate)?;
1146 let owner_idx = Self::get_account_index(account_keys_hashmap, &owner)?;
1147
1148 let data = if is_spl_token_program {
1149 spl_token_interface::instruction::TokenInstruction::ApproveChecked {
1150 amount,
1151 decimals,
1152 }
1153 .pack()
1154 } else {
1155 spl_token_2022_interface::instruction::TokenInstruction::ApproveChecked {
1156 amount,
1157 decimals,
1158 }
1159 .pack()
1160 };
1161
1162 Ok(CompiledInstruction {
1163 program_id_index,
1164 accounts: vec![source_idx, mint_idx, delegate_idx, owner_idx],
1165 data,
1166 })
1167 }
1168 PARSED_DATA_FIELD_REVOKE => {
1169 let source = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_SOURCE)?;
1170 let owner = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_OWNER)?;
1171
1172 let source_idx = Self::get_account_index(account_keys_hashmap, &source)?;
1173 let owner_idx = Self::get_account_index(account_keys_hashmap, &owner)?;
1174
1175 let data = if is_spl_token_program {
1176 spl_token_interface::instruction::TokenInstruction::Revoke.pack()
1177 } else {
1178 spl_token_2022_interface::instruction::TokenInstruction::Revoke.pack()
1179 };
1180
1181 Ok(CompiledInstruction {
1182 program_id_index,
1183 accounts: vec![source_idx, owner_idx],
1184 data,
1185 })
1186 }
1187 PARSED_DATA_FIELD_SET_AUTHORITY => {
1188 let account = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_ACCOUNT)?;
1189 let current_authority =
1190 Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_AUTHORITY)?;
1191
1192 let account_idx = Self::get_account_index(account_keys_hashmap, &account)?;
1193 let current_authority_idx =
1194 Self::get_account_index(account_keys_hashmap, ¤t_authority)?;
1195
1196 let data = vec![6];
1199
1200 Ok(CompiledInstruction {
1201 program_id_index,
1202 accounts: vec![account_idx, current_authority_idx],
1203 data,
1204 })
1205 }
1206 PARSED_DATA_FIELD_MINT_TO => {
1207 let mint = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_MINT)?;
1208 let account = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_ACCOUNT)?;
1209 let mint_authority =
1210 Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_MINT_AUTHORITY)?;
1211 let amount = Self::get_field_as_u64(info, PARSED_DATA_FIELD_AMOUNT)?;
1212
1213 let mint_idx = Self::get_account_index(account_keys_hashmap, &mint)?;
1214 let account_idx = Self::get_account_index(account_keys_hashmap, &account)?;
1215 let mint_authority_idx =
1216 Self::get_account_index(account_keys_hashmap, &mint_authority)?;
1217
1218 let data = if is_spl_token_program {
1219 spl_token_interface::instruction::TokenInstruction::MintTo { amount }.pack()
1220 } else {
1221 spl_token_2022_interface::instruction::TokenInstruction::MintTo { amount }
1222 .pack()
1223 };
1224
1225 Ok(CompiledInstruction {
1226 program_id_index,
1227 accounts: vec![mint_idx, account_idx, mint_authority_idx],
1228 data,
1229 })
1230 }
1231 PARSED_DATA_FIELD_MINT_TO_CHECKED => {
1232 let mint = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_MINT)?;
1233 let account = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_ACCOUNT)?;
1234 let mint_authority =
1235 Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_MINT_AUTHORITY)?;
1236
1237 let token_amount = info.get(PARSED_DATA_FIELD_TOKEN_AMOUNT).ok_or_else(|| {
1238 KoraError::SerializationError("Missing 'tokenAmount' field".to_string())
1239 })?;
1240 let amount = Self::get_field_as_u64(token_amount, PARSED_DATA_FIELD_AMOUNT)?;
1241 let decimals =
1242 Self::get_field_as_u64(token_amount, PARSED_DATA_FIELD_DECIMALS)? as u8;
1243
1244 let mint_idx = Self::get_account_index(account_keys_hashmap, &mint)?;
1245 let account_idx = Self::get_account_index(account_keys_hashmap, &account)?;
1246 let mint_authority_idx =
1247 Self::get_account_index(account_keys_hashmap, &mint_authority)?;
1248
1249 let data = if is_spl_token_program {
1250 spl_token_interface::instruction::TokenInstruction::MintToChecked {
1251 amount,
1252 decimals,
1253 }
1254 .pack()
1255 } else {
1256 spl_token_2022_interface::instruction::TokenInstruction::MintToChecked {
1257 amount,
1258 decimals,
1259 }
1260 .pack()
1261 };
1262
1263 Ok(CompiledInstruction {
1264 program_id_index,
1265 accounts: vec![mint_idx, account_idx, mint_authority_idx],
1266 data,
1267 })
1268 }
1269 PARSED_DATA_FIELD_INITIALIZE_MINT | PARSED_DATA_FIELD_INITIALIZE_MINT2 => {
1270 let mint = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_MINT)?;
1271 let _mint_authority =
1273 Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_MINT_AUTHORITY)?;
1274
1275 let mint_idx = Self::get_account_index(account_keys_hashmap, &mint)?;
1276
1277 let data = if instruction_type == PARSED_DATA_FIELD_INITIALIZE_MINT {
1279 vec![0] } else {
1281 vec![20] };
1283
1284 Ok(CompiledInstruction { program_id_index, accounts: vec![mint_idx], data })
1285 }
1286 PARSED_DATA_FIELD_INITIALIZE_ACCOUNT
1287 | PARSED_DATA_FIELD_INITIALIZE_ACCOUNT2
1288 | PARSED_DATA_FIELD_INITIALIZE_ACCOUNT3 => {
1289 let account = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_ACCOUNT)?;
1290 let mint = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_MINT)?;
1291 let owner = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_OWNER)?;
1292
1293 let account_idx = Self::get_account_index(account_keys_hashmap, &account)?;
1294 let mint_idx = Self::get_account_index(account_keys_hashmap, &mint)?;
1295
1296 let (data, accounts) = match instruction_type {
1298 PARSED_DATA_FIELD_INITIALIZE_ACCOUNT => {
1299 let owner_idx = Self::get_account_index(account_keys_hashmap, &owner)?;
1302 (vec![1], vec![account_idx, mint_idx, owner_idx])
1303 }
1304 PARSED_DATA_FIELD_INITIALIZE_ACCOUNT2 => {
1305 (vec![16], vec![account_idx, mint_idx])
1307 }
1308 PARSED_DATA_FIELD_INITIALIZE_ACCOUNT3 => {
1309 (vec![18], vec![account_idx, mint_idx])
1311 }
1312 _ => unreachable!(),
1313 };
1314
1315 Ok(CompiledInstruction { program_id_index, accounts, data })
1316 }
1317 PARSED_DATA_FIELD_INITIALIZE_MULTISIG | PARSED_DATA_FIELD_INITIALIZE_MULTISIG2 => {
1318 let multisig = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_MULTISIG_ACCOUNT)?;
1319 let multisig_idx = Self::get_account_index(account_keys_hashmap, &multisig)?;
1320
1321 let _signers_value = info.get(PARSED_DATA_FIELD_SIGNERS).ok_or_else(|| {
1323 KoraError::SerializationError("Missing 'signers' field".to_string())
1324 })?;
1325
1326 let data = if instruction_type == PARSED_DATA_FIELD_INITIALIZE_MULTISIG {
1328 vec![2] } else {
1330 vec![19] };
1332
1333 Ok(CompiledInstruction { program_id_index, accounts: vec![multisig_idx], data })
1334 }
1335 PARSED_DATA_FIELD_FREEZE_ACCOUNT => {
1336 let account = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_ACCOUNT)?;
1337 let mint = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_MINT)?;
1338 let freeze_authority =
1339 Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_FREEZE_AUTHORITY)?;
1340
1341 let account_idx = Self::get_account_index(account_keys_hashmap, &account)?;
1342 let mint_idx = Self::get_account_index(account_keys_hashmap, &mint)?;
1343 let freeze_authority_idx =
1344 Self::get_account_index(account_keys_hashmap, &freeze_authority)?;
1345
1346 let data = if is_spl_token_program {
1347 spl_token_interface::instruction::TokenInstruction::FreezeAccount.pack()
1348 } else {
1349 spl_token_2022_interface::instruction::TokenInstruction::FreezeAccount.pack()
1350 };
1351
1352 Ok(CompiledInstruction {
1353 program_id_index,
1354 accounts: vec![account_idx, mint_idx, freeze_authority_idx],
1355 data,
1356 })
1357 }
1358 PARSED_DATA_FIELD_THAW_ACCOUNT => {
1359 let account = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_ACCOUNT)?;
1360 let mint = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_MINT)?;
1361 let freeze_authority =
1362 Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_FREEZE_AUTHORITY)?;
1363
1364 let account_idx = Self::get_account_index(account_keys_hashmap, &account)?;
1365 let mint_idx = Self::get_account_index(account_keys_hashmap, &mint)?;
1366 let freeze_authority_idx =
1367 Self::get_account_index(account_keys_hashmap, &freeze_authority)?;
1368
1369 let data = if is_spl_token_program {
1370 spl_token_interface::instruction::TokenInstruction::ThawAccount.pack()
1371 } else {
1372 spl_token_2022_interface::instruction::TokenInstruction::ThawAccount.pack()
1373 };
1374
1375 Ok(CompiledInstruction {
1376 program_id_index,
1377 accounts: vec![account_idx, mint_idx, freeze_authority_idx],
1378 data,
1379 })
1380 }
1381 PARSED_DATA_FIELD_GET_ACCOUNT_DATA_SIZE => {
1382 let mint = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_MINT)?;
1383 let mint_idx = Self::get_account_index(account_keys_hashmap, &mint)?;
1384
1385 let data = if is_spl_token_program {
1386 spl_token_interface::instruction::TokenInstruction::GetAccountDataSize.pack()
1387 } else {
1388 spl_token_2022_interface::instruction::TokenInstruction::GetAccountDataSize {
1389 extension_types: vec![],
1390 }
1391 .pack()
1392 };
1393
1394 Ok(CompiledInstruction {
1395 program_id_index,
1396 accounts: vec![mint_idx],
1397 data,
1398 })
1399 }
1400 PARSED_DATA_FIELD_INITIALIZE_IMMUTABLE_OWNER => {
1401 let account = Self::get_field_as_pubkey(info, PARSED_DATA_FIELD_ACCOUNT)?;
1402 let account_idx = Self::get_account_index(account_keys_hashmap, &account)?;
1403
1404 let data = if is_spl_token_program {
1405 spl_token_interface::instruction::TokenInstruction::InitializeImmutableOwner
1406 .pack()
1407 } else {
1408 spl_token_2022_interface::instruction::TokenInstruction::InitializeImmutableOwner
1409 .pack()
1410 };
1411
1412 Ok(CompiledInstruction {
1413 program_id_index,
1414 accounts: vec![account_idx],
1415 data,
1416 })
1417 }
1418 _ => {
1419 Err(KoraError::InvalidTransaction(format!(
1420 "Unrecognized SPL Token instruction type '{}' in CPI — cannot validate fee payer policy",
1421 instruction_type
1422 )))
1423 }
1424 }
1425 }
1426
1427 pub fn parse_system_instructions(
1428 transaction: &VersionedTransactionResolved,
1429 ) -> Result<HashMap<ParsedSystemInstructionType, Vec<ParsedSystemInstructionData>>, KoraError>
1430 {
1431 let mut parsed_instructions: HashMap<
1432 ParsedSystemInstructionType,
1433 Vec<ParsedSystemInstructionData>,
1434 > = HashMap::new();
1435
1436 for instruction in transaction.all_instructions.iter() {
1437 let program_id = instruction.program_id;
1438
1439 if program_id == SYSTEM_PROGRAM_ID {
1441 match bincode::deserialize::<SystemInstruction>(&instruction.data) {
1442 Ok(SystemInstruction::CreateAccount { lamports, .. })
1443 | Ok(SystemInstruction::CreateAccountWithSeed { lamports, .. }) => {
1444 parse_system_instruction!(parsed_instructions, instruction, system_create_account, SystemCreateAccount, SystemCreateAccount {
1445 lamports: lamports;
1446 payer: instruction_indexes::system_create_account::PAYER_INDEX
1447 });
1448 }
1449 Ok(SystemInstruction::Transfer { lamports }) => {
1450 parse_system_instruction!(parsed_instructions, instruction, system_transfer, SystemTransfer, SystemTransfer {
1451 lamports: lamports;
1452 sender: instruction_indexes::system_transfer::SENDER_INDEX,
1453 receiver: instruction_indexes::system_transfer::RECEIVER_INDEX
1454 });
1455 }
1456 Ok(SystemInstruction::TransferWithSeed { lamports, .. }) => {
1457 validate_number_accounts!(instruction, instruction_indexes::system_transfer_with_seed::REQUIRED_NUMBER_OF_ACCOUNTS);
1459 parsed_instructions
1460 .entry(ParsedSystemInstructionType::SystemTransfer)
1461 .or_default()
1462 .push(ParsedSystemInstructionData::SystemTransfer {
1463 lamports,
1464 sender: instruction.accounts[instruction_indexes::system_transfer_with_seed::SENDER_INDEX].pubkey,
1465 receiver: instruction.accounts[instruction_indexes::system_transfer_with_seed::RECEIVER_INDEX].pubkey,
1466 });
1467 }
1468 Ok(SystemInstruction::WithdrawNonceAccount(lamports)) => {
1469 parse_system_instruction!(parsed_instructions, instruction, system_withdraw_nonce_account, SystemWithdrawNonceAccount, SystemWithdrawNonceAccount {
1470 lamports: lamports;
1471 nonce_authority: instruction_indexes::system_withdraw_nonce_account::NONCE_AUTHORITY_INDEX,
1472 recipient: instruction_indexes::system_withdraw_nonce_account::RECIPIENT_INDEX
1473 });
1474 }
1475 Ok(SystemInstruction::Assign { .. }) => {
1476 parse_system_instruction!(
1477 parsed_instructions,
1478 instruction,
1479 system_assign,
1480 SystemAssign,
1481 SystemAssign {
1482 authority: instruction_indexes::system_assign::AUTHORITY_INDEX
1483 }
1484 );
1485 }
1486 Ok(SystemInstruction::AssignWithSeed { .. }) => {
1487 validate_number_accounts!(instruction, instruction_indexes::system_assign_with_seed::REQUIRED_NUMBER_OF_ACCOUNTS);
1489 parsed_instructions
1490 .entry(ParsedSystemInstructionType::SystemAssign)
1491 .or_default()
1492 .push(ParsedSystemInstructionData::SystemAssign {
1493 authority: instruction.accounts
1494 [instruction_indexes::system_assign_with_seed::AUTHORITY_INDEX]
1495 .pubkey,
1496 });
1497 }
1498 Ok(SystemInstruction::Allocate { .. }) => {
1499 parse_system_instruction!(
1500 parsed_instructions,
1501 instruction,
1502 system_allocate,
1503 SystemAllocate,
1504 SystemAllocate {
1505 account: instruction_indexes::system_allocate::ACCOUNT_INDEX
1506 }
1507 );
1508 }
1509 Ok(SystemInstruction::AllocateWithSeed { .. }) => {
1510 validate_number_accounts!(instruction, instruction_indexes::system_allocate_with_seed::REQUIRED_NUMBER_OF_ACCOUNTS);
1512 parsed_instructions
1513 .entry(ParsedSystemInstructionType::SystemAllocate)
1514 .or_default()
1515 .push(ParsedSystemInstructionData::SystemAllocate {
1516 account: instruction.accounts
1517 [instruction_indexes::system_allocate_with_seed::ACCOUNT_INDEX]
1518 .pubkey,
1519 });
1520 }
1521 Ok(SystemInstruction::InitializeNonceAccount(authority)) => {
1522 parse_system_instruction!(parsed_instructions, instruction, system_initialize_nonce_account, SystemInitializeNonceAccount, SystemInitializeNonceAccount {
1523 nonce_authority: authority;
1524 nonce_account: instruction_indexes::system_initialize_nonce_account::NONCE_ACCOUNT_INDEX
1525 });
1526 }
1527 Ok(SystemInstruction::AdvanceNonceAccount) => {
1528 parse_system_instruction!(parsed_instructions, instruction, system_advance_nonce_account, SystemAdvanceNonceAccount, SystemAdvanceNonceAccount {
1529 nonce_account: instruction_indexes::system_advance_nonce_account::NONCE_ACCOUNT_INDEX,
1530 nonce_authority: instruction_indexes::system_advance_nonce_account::NONCE_AUTHORITY_INDEX
1531 });
1532 }
1533 Ok(SystemInstruction::AuthorizeNonceAccount(_new_authority)) => {
1534 parse_system_instruction!(parsed_instructions, instruction, system_authorize_nonce_account, SystemAuthorizeNonceAccount, SystemAuthorizeNonceAccount {
1535 nonce_account: instruction_indexes::system_authorize_nonce_account::NONCE_ACCOUNT_INDEX,
1536 nonce_authority: instruction_indexes::system_authorize_nonce_account::NONCE_AUTHORITY_INDEX
1537 });
1538 }
1539 Ok(SystemInstruction::UpgradeNonceAccount) => {
1542 }
1544 _ => {}
1545 }
1546 }
1547 }
1548 Ok(parsed_instructions)
1549 }
1550
1551 pub fn parse_token_instructions(
1552 transaction: &VersionedTransactionResolved,
1553 ) -> Result<HashMap<ParsedSPLInstructionType, Vec<ParsedSPLInstructionData>>, KoraError> {
1554 let mut parsed_instructions: HashMap<
1555 ParsedSPLInstructionType,
1556 Vec<ParsedSPLInstructionData>,
1557 > = HashMap::new();
1558
1559 for instruction in &transaction.all_instructions {
1560 let program_id = instruction.program_id;
1561
1562 if program_id == spl_token_interface::ID {
1563 if let Ok(spl_ix) =
1564 spl_token_interface::instruction::TokenInstruction::unpack(&instruction.data)
1565 {
1566 match spl_ix {
1567 spl_token_interface::instruction::TokenInstruction::Transfer { amount } => {
1568 validate_number_accounts!(instruction, instruction_indexes::spl_token_transfer::REQUIRED_NUMBER_OF_ACCOUNTS);
1569
1570 parsed_instructions
1571 .entry(ParsedSPLInstructionType::SplTokenTransfer)
1572 .or_default()
1573 .push(ParsedSPLInstructionData::SplTokenTransfer {
1574 owner: instruction.accounts[instruction_indexes::spl_token_transfer::OWNER_INDEX].pubkey,
1575 amount,
1576 mint: None,
1577 source_address: instruction.accounts[instruction_indexes::spl_token_transfer::SOURCE_ADDRESS_INDEX].pubkey,
1578 destination_address: instruction.accounts[instruction_indexes::spl_token_transfer::DESTINATION_ADDRESS_INDEX].pubkey,
1579 is_2022: false,
1580 });
1581 }
1582 spl_token_interface::instruction::TokenInstruction::TransferChecked {
1583 amount,
1584 ..
1585 } => {
1586 validate_number_accounts!(instruction, instruction_indexes::spl_token_transfer_checked::REQUIRED_NUMBER_OF_ACCOUNTS);
1587
1588 parsed_instructions
1589 .entry(ParsedSPLInstructionType::SplTokenTransfer)
1590 .or_default()
1591 .push(ParsedSPLInstructionData::SplTokenTransfer {
1592 owner: instruction.accounts[instruction_indexes::spl_token_transfer_checked::OWNER_INDEX].pubkey,
1593 amount,
1594 mint: Some(instruction.accounts[instruction_indexes::spl_token_transfer_checked::MINT_INDEX].pubkey),
1595 source_address: instruction.accounts[instruction_indexes::spl_token_transfer_checked::SOURCE_ADDRESS_INDEX].pubkey,
1596 destination_address: instruction.accounts[instruction_indexes::spl_token_transfer_checked::DESTINATION_ADDRESS_INDEX].pubkey,
1597 is_2022: false,
1598 });
1599 }
1600 spl_token_interface::instruction::TokenInstruction::Burn { .. } => {
1601 let owner = Self::parse_burn_owner_with_mint_fallback(instruction)?;
1602 Self::push_parsed_spl_instruction(
1603 &mut parsed_instructions,
1604 ParsedSPLInstructionType::SplTokenBurn,
1605 ParsedSPLInstructionData::SplTokenBurn {
1606 owner,
1607 is_2022: false,
1608 },
1609 );
1610 }
1611 spl_token_interface::instruction::TokenInstruction::BurnChecked { .. } => {
1612 validate_number_accounts!(
1613 instruction,
1614 instruction_indexes::spl_token_burn::REQUIRED_NUMBER_OF_ACCOUNTS
1615 );
1616
1617 parsed_instructions
1618 .entry(ParsedSPLInstructionType::SplTokenBurn)
1619 .or_default()
1620 .push(ParsedSPLInstructionData::SplTokenBurn {
1621 owner: instruction.accounts
1622 [instruction_indexes::spl_token_burn::OWNER_INDEX]
1623 .pubkey,
1624 is_2022: false,
1625 });
1626 }
1627 spl_token_interface::instruction::TokenInstruction::CloseAccount { .. } => {
1628 validate_number_accounts!(instruction, instruction_indexes::spl_token_close_account::REQUIRED_NUMBER_OF_ACCOUNTS);
1629
1630 parsed_instructions
1631 .entry(ParsedSPLInstructionType::SplTokenCloseAccount)
1632 .or_default()
1633 .push(ParsedSPLInstructionData::SplTokenCloseAccount {
1634 owner: instruction.accounts
1635 [instruction_indexes::spl_token_close_account::OWNER_INDEX]
1636 .pubkey,
1637 is_2022: false,
1638 });
1639 }
1640 spl_token_interface::instruction::TokenInstruction::Approve { .. } => {
1641 validate_number_accounts!(
1642 instruction,
1643 instruction_indexes::spl_token_approve::REQUIRED_NUMBER_OF_ACCOUNTS
1644 );
1645
1646 parsed_instructions
1647 .entry(ParsedSPLInstructionType::SplTokenApprove)
1648 .or_default()
1649 .push(ParsedSPLInstructionData::SplTokenApprove {
1650 owner: instruction.accounts
1651 [instruction_indexes::spl_token_approve::OWNER_INDEX]
1652 .pubkey,
1653 is_2022: false,
1654 });
1655 }
1656 spl_token_interface::instruction::TokenInstruction::ApproveChecked { .. } => {
1657 validate_number_accounts!(instruction, instruction_indexes::spl_token_approve_checked::REQUIRED_NUMBER_OF_ACCOUNTS);
1658
1659 parsed_instructions
1660 .entry(ParsedSPLInstructionType::SplTokenApprove)
1661 .or_default()
1662 .push(ParsedSPLInstructionData::SplTokenApprove {
1663 owner: instruction.accounts[instruction_indexes::spl_token_approve_checked::OWNER_INDEX].pubkey,
1664 is_2022: false,
1665 });
1666 }
1667 spl_token_interface::instruction::TokenInstruction::Revoke => {
1668 validate_number_accounts!(
1669 instruction,
1670 instruction_indexes::spl_token_revoke::REQUIRED_NUMBER_OF_ACCOUNTS
1671 );
1672
1673 parsed_instructions
1674 .entry(ParsedSPLInstructionType::SplTokenRevoke)
1675 .or_default()
1676 .push(ParsedSPLInstructionData::SplTokenRevoke {
1677 owner: instruction.accounts
1678 [instruction_indexes::spl_token_revoke::OWNER_INDEX]
1679 .pubkey,
1680 is_2022: false,
1681 });
1682 }
1683 spl_token_interface::instruction::TokenInstruction::SetAuthority { .. } => {
1684 validate_number_accounts!(instruction, instruction_indexes::spl_token_set_authority::REQUIRED_NUMBER_OF_ACCOUNTS);
1685
1686 parsed_instructions
1687 .entry(ParsedSPLInstructionType::SplTokenSetAuthority)
1688 .or_default()
1689 .push(ParsedSPLInstructionData::SplTokenSetAuthority {
1690 authority: instruction.accounts[instruction_indexes::spl_token_set_authority::CURRENT_AUTHORITY_INDEX].pubkey,
1691 is_2022: false,
1692 });
1693 }
1694 spl_token_interface::instruction::TokenInstruction::MintTo { .. } => {
1695 validate_number_accounts!(
1696 instruction,
1697 instruction_indexes::spl_token_mint_to::REQUIRED_NUMBER_OF_ACCOUNTS
1698 );
1699
1700 parsed_instructions
1701 .entry(ParsedSPLInstructionType::SplTokenMintTo)
1702 .or_default()
1703 .push(ParsedSPLInstructionData::SplTokenMintTo {
1704 mint_authority: instruction.accounts[instruction_indexes::spl_token_mint_to::MINT_AUTHORITY_INDEX].pubkey,
1705 is_2022: false,
1706 });
1707 }
1708 spl_token_interface::instruction::TokenInstruction::MintToChecked { .. } => {
1709 validate_number_accounts!(instruction, instruction_indexes::spl_token_mint_to_checked::REQUIRED_NUMBER_OF_ACCOUNTS);
1710
1711 parsed_instructions
1712 .entry(ParsedSPLInstructionType::SplTokenMintTo)
1713 .or_default()
1714 .push(ParsedSPLInstructionData::SplTokenMintTo {
1715 mint_authority: instruction.accounts[instruction_indexes::spl_token_mint_to_checked::MINT_AUTHORITY_INDEX].pubkey,
1716 is_2022: false,
1717 });
1718 }
1719 spl_token_interface::instruction::TokenInstruction::InitializeMint {
1720 mint_authority,
1721 ..
1722 } => {
1723 validate_number_accounts!(instruction, instruction_indexes::spl_token_initialize_mint::REQUIRED_NUMBER_OF_ACCOUNTS);
1724
1725 parsed_instructions
1726 .entry(ParsedSPLInstructionType::SplTokenInitializeMint)
1727 .or_default()
1728 .push(ParsedSPLInstructionData::SplTokenInitializeMint {
1729 mint_authority,
1730 is_2022: false,
1731 });
1732 }
1733 spl_token_interface::instruction::TokenInstruction::InitializeMint2 {
1734 mint_authority,
1735 ..
1736 } => {
1737 validate_number_accounts!(instruction, instruction_indexes::spl_token_initialize_mint2::REQUIRED_NUMBER_OF_ACCOUNTS);
1738
1739 parsed_instructions
1740 .entry(ParsedSPLInstructionType::SplTokenInitializeMint)
1741 .or_default()
1742 .push(ParsedSPLInstructionData::SplTokenInitializeMint {
1743 mint_authority,
1744 is_2022: false,
1745 });
1746 }
1747 spl_token_interface::instruction::TokenInstruction::InitializeAccount => {
1748 validate_number_accounts!(instruction, instruction_indexes::spl_token_initialize_account::REQUIRED_NUMBER_OF_ACCOUNTS);
1749
1750 parsed_instructions
1751 .entry(ParsedSPLInstructionType::SplTokenInitializeAccount)
1752 .or_default()
1753 .push(ParsedSPLInstructionData::SplTokenInitializeAccount {
1754 owner: instruction.accounts[instruction_indexes::spl_token_initialize_account::OWNER_INDEX].pubkey,
1755 is_2022: false,
1756 });
1757 }
1758 spl_token_interface::instruction::TokenInstruction::InitializeAccount2 { owner } => {
1759 validate_number_accounts!(instruction, instruction_indexes::spl_token_initialize_account2::REQUIRED_NUMBER_OF_ACCOUNTS);
1760
1761 parsed_instructions
1762 .entry(ParsedSPLInstructionType::SplTokenInitializeAccount)
1763 .or_default()
1764 .push(ParsedSPLInstructionData::SplTokenInitializeAccount {
1765 owner,
1766 is_2022: false,
1767 });
1768 }
1769 spl_token_interface::instruction::TokenInstruction::InitializeAccount3 { owner } => {
1770 validate_number_accounts!(instruction, instruction_indexes::spl_token_initialize_account3::REQUIRED_NUMBER_OF_ACCOUNTS);
1771
1772 parsed_instructions
1773 .entry(ParsedSPLInstructionType::SplTokenInitializeAccount)
1774 .or_default()
1775 .push(ParsedSPLInstructionData::SplTokenInitializeAccount {
1776 owner,
1777 is_2022: false,
1778 });
1779 }
1780 spl_token_interface::instruction::TokenInstruction::InitializeMultisig { .. } => {
1781 validate_number_accounts!(instruction, instruction_indexes::spl_token_initialize_multisig::REQUIRED_NUMBER_OF_ACCOUNTS);
1782
1783 let signers = Self::extract_multisig_signers(instruction, 2);
1785
1786 parsed_instructions
1787 .entry(ParsedSPLInstructionType::SplTokenInitializeMultisig)
1788 .or_default()
1789 .push(ParsedSPLInstructionData::SplTokenInitializeMultisig {
1790 signers,
1791 is_2022: false,
1792 });
1793 }
1794 spl_token_interface::instruction::TokenInstruction::InitializeMultisig2 {
1795 ..
1796 } => {
1797 validate_number_accounts!(instruction, instruction_indexes::spl_token_initialize_multisig2::REQUIRED_NUMBER_OF_ACCOUNTS);
1798
1799 let signers = Self::extract_multisig_signers(instruction, 1);
1801
1802 parsed_instructions
1803 .entry(ParsedSPLInstructionType::SplTokenInitializeMultisig)
1804 .or_default()
1805 .push(ParsedSPLInstructionData::SplTokenInitializeMultisig {
1806 signers,
1807 is_2022: false,
1808 });
1809 }
1810 spl_token_interface::instruction::TokenInstruction::FreezeAccount => {
1811 validate_number_accounts!(instruction, instruction_indexes::spl_token_freeze_account::REQUIRED_NUMBER_OF_ACCOUNTS);
1812
1813 parsed_instructions
1814 .entry(ParsedSPLInstructionType::SplTokenFreezeAccount)
1815 .or_default()
1816 .push(ParsedSPLInstructionData::SplTokenFreezeAccount {
1817 freeze_authority: instruction.accounts[instruction_indexes::spl_token_freeze_account::FREEZE_AUTHORITY_INDEX].pubkey,
1818 is_2022: false,
1819 });
1820 }
1821 spl_token_interface::instruction::TokenInstruction::ThawAccount => {
1822 validate_number_accounts!(instruction, instruction_indexes::spl_token_thaw_account::REQUIRED_NUMBER_OF_ACCOUNTS);
1823
1824 parsed_instructions
1825 .entry(ParsedSPLInstructionType::SplTokenThawAccount)
1826 .or_default()
1827 .push(ParsedSPLInstructionData::SplTokenThawAccount {
1828 freeze_authority: instruction.accounts[instruction_indexes::spl_token_thaw_account::FREEZE_AUTHORITY_INDEX].pubkey,
1829 is_2022: false,
1830 });
1831 }
1832 _ => {}
1833 };
1834 }
1835 } else if program_id == spl_token_2022_interface::ID {
1836 if let Ok(spl_ix) = spl_token_2022_interface::instruction::TokenInstruction::unpack(
1837 &instruction.data,
1838 ) {
1839 match spl_ix {
1840 #[allow(deprecated)]
1841 spl_token_2022_interface::instruction::TokenInstruction::Transfer { amount } => {
1842 validate_number_accounts!(instruction, instruction_indexes::spl_token_transfer::REQUIRED_NUMBER_OF_ACCOUNTS);
1843
1844 parsed_instructions
1845 .entry(ParsedSPLInstructionType::SplTokenTransfer)
1846 .or_default()
1847 .push(ParsedSPLInstructionData::SplTokenTransfer {
1848 owner: instruction.accounts[instruction_indexes::spl_token_transfer::OWNER_INDEX].pubkey,
1849 amount,
1850 mint: None,
1851 source_address: instruction.accounts[instruction_indexes::spl_token_transfer::SOURCE_ADDRESS_INDEX].pubkey,
1852 destination_address: instruction.accounts[instruction_indexes::spl_token_transfer::DESTINATION_ADDRESS_INDEX].pubkey,
1853 is_2022: true,
1854 });
1855 }
1856 spl_token_2022_interface::instruction::TokenInstruction::TransferChecked {
1857 amount,
1858 ..
1859 } => {
1860 validate_number_accounts!(instruction, instruction_indexes::spl_token_transfer_checked::REQUIRED_NUMBER_OF_ACCOUNTS);
1861
1862 parsed_instructions
1863 .entry(ParsedSPLInstructionType::SplTokenTransfer)
1864 .or_default()
1865 .push(ParsedSPLInstructionData::SplTokenTransfer {
1866 owner: instruction.accounts[instruction_indexes::spl_token_transfer_checked::OWNER_INDEX].pubkey,
1867 amount,
1868 mint: Some(instruction.accounts[instruction_indexes::spl_token_transfer_checked::MINT_INDEX].pubkey),
1869 source_address: instruction.accounts[instruction_indexes::spl_token_transfer_checked::SOURCE_ADDRESS_INDEX].pubkey,
1870 destination_address: instruction.accounts[instruction_indexes::spl_token_transfer_checked::DESTINATION_ADDRESS_INDEX].pubkey,
1871 is_2022: true,
1872 });
1873 }
1874 spl_token_2022_interface::instruction::TokenInstruction::Burn { .. } => {
1875 let owner = Self::parse_burn_owner_with_mint_fallback(instruction)?;
1876 Self::push_parsed_spl_instruction(
1877 &mut parsed_instructions,
1878 ParsedSPLInstructionType::SplTokenBurn,
1879 ParsedSPLInstructionData::SplTokenBurn {
1880 owner,
1881 is_2022: true,
1882 },
1883 );
1884 }
1885 spl_token_2022_interface::instruction::TokenInstruction::BurnChecked { .. } => {
1886 validate_number_accounts!(
1887 instruction,
1888 instruction_indexes::spl_token_burn::REQUIRED_NUMBER_OF_ACCOUNTS
1889 );
1890
1891 parsed_instructions
1892 .entry(ParsedSPLInstructionType::SplTokenBurn)
1893 .or_default()
1894 .push(ParsedSPLInstructionData::SplTokenBurn {
1895 owner: instruction.accounts
1896 [instruction_indexes::spl_token_burn::OWNER_INDEX]
1897 .pubkey,
1898 is_2022: true,
1899 });
1900 }
1901 spl_token_2022_interface::instruction::TokenInstruction::CloseAccount { .. } => {
1902 validate_number_accounts!(instruction, instruction_indexes::spl_token_close_account::REQUIRED_NUMBER_OF_ACCOUNTS);
1903
1904 parsed_instructions
1905 .entry(ParsedSPLInstructionType::SplTokenCloseAccount)
1906 .or_default()
1907 .push(ParsedSPLInstructionData::SplTokenCloseAccount {
1908 owner: instruction.accounts
1909 [instruction_indexes::spl_token_close_account::OWNER_INDEX]
1910 .pubkey,
1911 is_2022: true,
1912 });
1913 }
1914 spl_token_2022_interface::instruction::TokenInstruction::Approve { .. } => {
1915 validate_number_accounts!(
1916 instruction,
1917 instruction_indexes::spl_token_approve::REQUIRED_NUMBER_OF_ACCOUNTS
1918 );
1919
1920 parsed_instructions
1921 .entry(ParsedSPLInstructionType::SplTokenApprove)
1922 .or_default()
1923 .push(ParsedSPLInstructionData::SplTokenApprove {
1924 owner: instruction.accounts
1925 [instruction_indexes::spl_token_approve::OWNER_INDEX]
1926 .pubkey,
1927 is_2022: true,
1928 });
1929 }
1930 spl_token_2022_interface::instruction::TokenInstruction::ApproveChecked {
1931 ..
1932 } => {
1933 validate_number_accounts!(instruction, instruction_indexes::spl_token_approve_checked::REQUIRED_NUMBER_OF_ACCOUNTS);
1934
1935 parsed_instructions
1936 .entry(ParsedSPLInstructionType::SplTokenApprove)
1937 .or_default()
1938 .push(ParsedSPLInstructionData::SplTokenApprove {
1939 owner: instruction.accounts[instruction_indexes::spl_token_approve_checked::OWNER_INDEX].pubkey,
1940 is_2022: true,
1941 });
1942 }
1943 spl_token_2022_interface::instruction::TokenInstruction::Revoke => {
1944 validate_number_accounts!(
1945 instruction,
1946 instruction_indexes::spl_token_revoke::REQUIRED_NUMBER_OF_ACCOUNTS
1947 );
1948
1949 parsed_instructions
1950 .entry(ParsedSPLInstructionType::SplTokenRevoke)
1951 .or_default()
1952 .push(ParsedSPLInstructionData::SplTokenRevoke {
1953 owner: instruction.accounts
1954 [instruction_indexes::spl_token_revoke::OWNER_INDEX]
1955 .pubkey,
1956 is_2022: true,
1957 });
1958 }
1959 spl_token_2022_interface::instruction::TokenInstruction::SetAuthority { .. } => {
1960 validate_number_accounts!(instruction, instruction_indexes::spl_token_set_authority::REQUIRED_NUMBER_OF_ACCOUNTS);
1961
1962 parsed_instructions
1963 .entry(ParsedSPLInstructionType::SplTokenSetAuthority)
1964 .or_default()
1965 .push(ParsedSPLInstructionData::SplTokenSetAuthority {
1966 authority: instruction.accounts[instruction_indexes::spl_token_set_authority::CURRENT_AUTHORITY_INDEX].pubkey,
1967 is_2022: true,
1968 });
1969 }
1970 spl_token_2022_interface::instruction::TokenInstruction::MintTo { .. } => {
1971 validate_number_accounts!(
1972 instruction,
1973 instruction_indexes::spl_token_mint_to::REQUIRED_NUMBER_OF_ACCOUNTS
1974 );
1975
1976 parsed_instructions
1977 .entry(ParsedSPLInstructionType::SplTokenMintTo)
1978 .or_default()
1979 .push(ParsedSPLInstructionData::SplTokenMintTo {
1980 mint_authority: instruction.accounts[instruction_indexes::spl_token_mint_to::MINT_AUTHORITY_INDEX].pubkey,
1981 is_2022: true,
1982 });
1983 }
1984 spl_token_2022_interface::instruction::TokenInstruction::MintToChecked { .. } => {
1985 validate_number_accounts!(instruction, instruction_indexes::spl_token_mint_to_checked::REQUIRED_NUMBER_OF_ACCOUNTS);
1986
1987 parsed_instructions
1988 .entry(ParsedSPLInstructionType::SplTokenMintTo)
1989 .or_default()
1990 .push(ParsedSPLInstructionData::SplTokenMintTo {
1991 mint_authority: instruction.accounts[instruction_indexes::spl_token_mint_to_checked::MINT_AUTHORITY_INDEX].pubkey,
1992 is_2022: true,
1993 });
1994 }
1995 spl_token_2022_interface::instruction::TokenInstruction::InitializeMint {
1996 mint_authority,
1997 ..
1998 } => {
1999 validate_number_accounts!(instruction, instruction_indexes::spl_token_initialize_mint::REQUIRED_NUMBER_OF_ACCOUNTS);
2000
2001 parsed_instructions
2002 .entry(ParsedSPLInstructionType::SplTokenInitializeMint)
2003 .or_default()
2004 .push(ParsedSPLInstructionData::SplTokenInitializeMint {
2005 mint_authority,
2006 is_2022: true,
2007 });
2008 }
2009 spl_token_2022_interface::instruction::TokenInstruction::InitializeMint2 {
2010 mint_authority,
2011 ..
2012 } => {
2013 validate_number_accounts!(instruction, instruction_indexes::spl_token_initialize_mint2::REQUIRED_NUMBER_OF_ACCOUNTS);
2014
2015 parsed_instructions
2016 .entry(ParsedSPLInstructionType::SplTokenInitializeMint)
2017 .or_default()
2018 .push(ParsedSPLInstructionData::SplTokenInitializeMint {
2019 mint_authority,
2020 is_2022: true,
2021 });
2022 }
2023 spl_token_2022_interface::instruction::TokenInstruction::InitializeAccount => {
2024 validate_number_accounts!(instruction, instruction_indexes::spl_token_initialize_account::REQUIRED_NUMBER_OF_ACCOUNTS);
2025
2026 parsed_instructions
2027 .entry(ParsedSPLInstructionType::SplTokenInitializeAccount)
2028 .or_default()
2029 .push(ParsedSPLInstructionData::SplTokenInitializeAccount {
2030 owner: instruction.accounts[instruction_indexes::spl_token_initialize_account::OWNER_INDEX].pubkey,
2031 is_2022: true,
2032 });
2033 }
2034 spl_token_2022_interface::instruction::TokenInstruction::InitializeAccount2 {
2035 owner,
2036 } => {
2037 validate_number_accounts!(instruction, instruction_indexes::spl_token_initialize_account2::REQUIRED_NUMBER_OF_ACCOUNTS);
2038
2039 parsed_instructions
2040 .entry(ParsedSPLInstructionType::SplTokenInitializeAccount)
2041 .or_default()
2042 .push(ParsedSPLInstructionData::SplTokenInitializeAccount {
2043 owner,
2044 is_2022: true,
2045 });
2046 }
2047 spl_token_2022_interface::instruction::TokenInstruction::InitializeAccount3 {
2048 owner,
2049 } => {
2050 validate_number_accounts!(instruction, instruction_indexes::spl_token_initialize_account3::REQUIRED_NUMBER_OF_ACCOUNTS);
2051
2052 parsed_instructions
2053 .entry(ParsedSPLInstructionType::SplTokenInitializeAccount)
2054 .or_default()
2055 .push(ParsedSPLInstructionData::SplTokenInitializeAccount {
2056 owner,
2057 is_2022: true,
2058 });
2059 }
2060 spl_token_2022_interface::instruction::TokenInstruction::InitializeMultisig {
2061 ..
2062 } => {
2063 validate_number_accounts!(instruction, instruction_indexes::spl_token_initialize_multisig::REQUIRED_NUMBER_OF_ACCOUNTS);
2064
2065 let signers = Self::extract_multisig_signers(instruction, 2);
2067
2068 parsed_instructions
2069 .entry(ParsedSPLInstructionType::SplTokenInitializeMultisig)
2070 .or_default()
2071 .push(ParsedSPLInstructionData::SplTokenInitializeMultisig {
2072 signers,
2073 is_2022: true,
2074 });
2075 }
2076 spl_token_2022_interface::instruction::TokenInstruction::InitializeMultisig2 {
2077 ..
2078 } => {
2079 validate_number_accounts!(instruction, instruction_indexes::spl_token_initialize_multisig2::REQUIRED_NUMBER_OF_ACCOUNTS);
2080
2081 let signers = Self::extract_multisig_signers(instruction, 1);
2083
2084 parsed_instructions
2085 .entry(ParsedSPLInstructionType::SplTokenInitializeMultisig)
2086 .or_default()
2087 .push(ParsedSPLInstructionData::SplTokenInitializeMultisig {
2088 signers,
2089 is_2022: true,
2090 });
2091 }
2092 spl_token_2022_interface::instruction::TokenInstruction::FreezeAccount => {
2093 validate_number_accounts!(instruction, instruction_indexes::spl_token_freeze_account::REQUIRED_NUMBER_OF_ACCOUNTS);
2094
2095 parsed_instructions
2096 .entry(ParsedSPLInstructionType::SplTokenFreezeAccount)
2097 .or_default()
2098 .push(ParsedSPLInstructionData::SplTokenFreezeAccount {
2099 freeze_authority: instruction.accounts[instruction_indexes::spl_token_freeze_account::FREEZE_AUTHORITY_INDEX].pubkey,
2100 is_2022: true,
2101 });
2102 }
2103 spl_token_2022_interface::instruction::TokenInstruction::ThawAccount => {
2104 validate_number_accounts!(instruction, instruction_indexes::spl_token_thaw_account::REQUIRED_NUMBER_OF_ACCOUNTS);
2105
2106 parsed_instructions
2107 .entry(ParsedSPLInstructionType::SplTokenThawAccount)
2108 .or_default()
2109 .push(ParsedSPLInstructionData::SplTokenThawAccount {
2110 freeze_authority: instruction.accounts[instruction_indexes::spl_token_thaw_account::FREEZE_AUTHORITY_INDEX].pubkey,
2111 is_2022: true,
2112 });
2113 }
2114 _ => {}
2115 };
2116 }
2117 }
2118 }
2119 Ok(parsed_instructions)
2120 }
2121}
2122
2123#[cfg(test)]
2124mod tests {
2125
2126 use super::*;
2127 use solana_sdk::message::{AccountKeys, Message};
2128 use solana_transaction_status::parse_instruction;
2129
2130 fn create_parsed_system_transfer(
2131 source: &Pubkey,
2132 destination: &Pubkey,
2133 lamports: u64,
2134 ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2135 {
2136 let solana_instruction =
2137 solana_system_interface::instruction::transfer(source, destination, lamports);
2138
2139 let message = Message::new(&[solana_instruction], None);
2140 let compiled_instruction = &message.instructions[0];
2141
2142 let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2143
2144 let parsed = parse_instruction::parse(
2145 &SYSTEM_PROGRAM_ID,
2146 compiled_instruction,
2147 &account_keys_for_parsing,
2148 None,
2149 )?;
2150
2151 Ok(parsed)
2152 }
2153
2154 fn create_parsed_system_transfer_with_seed(
2155 source: &Pubkey,
2156 destination: &Pubkey,
2157 lamports: u64,
2158 source_base: &Pubkey,
2159 seed: &str,
2160 source_owner: &Pubkey,
2161 ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2162 {
2163 let solana_instruction = solana_system_interface::instruction::transfer_with_seed(
2164 source,
2165 source_base,
2166 seed.to_string(),
2167 source_owner,
2168 destination,
2169 lamports,
2170 );
2171
2172 let message = Message::new(&[solana_instruction], None);
2173 let compiled_instruction = &message.instructions[0];
2174
2175 let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2176
2177 let parsed = parse_instruction::parse(
2178 &SYSTEM_PROGRAM_ID,
2179 compiled_instruction,
2180 &account_keys_for_parsing,
2181 None,
2182 )?;
2183
2184 Ok(parsed)
2185 }
2186
2187 fn create_parsed_system_create_account(
2188 source: &Pubkey,
2189 new_account: &Pubkey,
2190 lamports: u64,
2191 space: u64,
2192 owner: &Pubkey,
2193 ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2194 {
2195 let solana_instruction = solana_system_interface::instruction::create_account(
2196 source,
2197 new_account,
2198 lamports,
2199 space,
2200 owner,
2201 );
2202
2203 let message = Message::new(&[solana_instruction], None);
2204 let compiled_instruction = &message.instructions[0];
2205
2206 let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2207
2208 let parsed = parse_instruction::parse(
2209 &SYSTEM_PROGRAM_ID,
2210 compiled_instruction,
2211 &account_keys_for_parsing,
2212 None,
2213 )?;
2214
2215 Ok(parsed)
2216 }
2217
2218 fn create_parsed_system_create_account_with_seed(
2219 source: &Pubkey,
2220 new_account: &Pubkey,
2221 base: &Pubkey,
2222 seed: &str,
2223 lamports: u64,
2224 space: u64,
2225 owner: &Pubkey,
2226 ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2227 {
2228 let solana_instruction = solana_system_interface::instruction::create_account_with_seed(
2229 source,
2230 new_account,
2231 base,
2232 seed,
2233 lamports,
2234 space,
2235 owner,
2236 );
2237
2238 let message = Message::new(&[solana_instruction], None);
2239 let compiled_instruction = &message.instructions[0];
2240
2241 let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2242
2243 let parsed = parse_instruction::parse(
2244 &SYSTEM_PROGRAM_ID,
2245 compiled_instruction,
2246 &account_keys_for_parsing,
2247 None,
2248 )?;
2249
2250 Ok(parsed)
2251 }
2252
2253 fn create_parsed_system_assign(
2254 account: &Pubkey,
2255 owner: &Pubkey,
2256 ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2257 {
2258 let solana_instruction = solana_system_interface::instruction::assign(account, owner);
2259
2260 let message = Message::new(&[solana_instruction], None);
2261 let compiled_instruction = &message.instructions[0];
2262
2263 let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2264
2265 let parsed = parse_instruction::parse(
2266 &SYSTEM_PROGRAM_ID,
2267 compiled_instruction,
2268 &account_keys_for_parsing,
2269 None,
2270 )?;
2271
2272 Ok(parsed)
2273 }
2274
2275 fn create_parsed_system_assign_with_seed(
2276 account: &Pubkey,
2277 base: &Pubkey,
2278 seed: &str,
2279 owner: &Pubkey,
2280 ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2281 {
2282 let solana_instruction =
2283 solana_system_interface::instruction::assign_with_seed(account, base, seed, owner);
2284
2285 let message = Message::new(&[solana_instruction], None);
2286 let compiled_instruction = &message.instructions[0];
2287
2288 let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2289
2290 let parsed = parse_instruction::parse(
2291 &SYSTEM_PROGRAM_ID,
2292 compiled_instruction,
2293 &account_keys_for_parsing,
2294 None,
2295 )?;
2296
2297 Ok(parsed)
2298 }
2299
2300 fn create_parsed_system_withdraw_nonce_account(
2301 nonce_account: &Pubkey,
2302 nonce_authority: &Pubkey,
2303 recipient: &Pubkey,
2304 lamports: u64,
2305 ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2306 {
2307 let solana_instruction = solana_system_interface::instruction::withdraw_nonce_account(
2308 nonce_account,
2309 nonce_authority,
2310 recipient,
2311 lamports,
2312 );
2313
2314 let message = Message::new(&[solana_instruction], None);
2315 let compiled_instruction = &message.instructions[0];
2316
2317 let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2318
2319 let parsed = parse_instruction::parse(
2320 &SYSTEM_PROGRAM_ID,
2321 compiled_instruction,
2322 &account_keys_for_parsing,
2323 None,
2324 )?;
2325
2326 Ok(parsed)
2327 }
2328
2329 fn create_parsed_spl_token_transfer(
2330 source: &Pubkey,
2331 destination: &Pubkey,
2332 authority: &Pubkey,
2333 amount: u64,
2334 ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2335 {
2336 let solana_instruction = spl_token_interface::instruction::transfer(
2337 &spl_token_interface::ID,
2338 source,
2339 destination,
2340 authority,
2341 &[],
2342 amount,
2343 )?;
2344
2345 let message = Message::new(&[solana_instruction], None);
2346 let compiled_instruction = &message.instructions[0];
2347
2348 let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2349
2350 let parsed = parse_instruction::parse(
2351 &spl_token_interface::ID,
2352 compiled_instruction,
2353 &account_keys_for_parsing,
2354 None,
2355 )?;
2356
2357 Ok(parsed)
2358 }
2359
2360 fn create_parsed_spl_token_transfer_checked(
2361 source: &Pubkey,
2362 mint: &Pubkey,
2363 destination: &Pubkey,
2364 authority: &Pubkey,
2365 amount: u64,
2366 decimals: u8,
2367 ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2368 {
2369 let solana_instruction = spl_token_interface::instruction::transfer_checked(
2370 &spl_token_interface::ID,
2371 source,
2372 mint,
2373 destination,
2374 authority,
2375 &[],
2376 amount,
2377 decimals,
2378 )?;
2379
2380 let message = Message::new(&[solana_instruction], None);
2381 let compiled_instruction = &message.instructions[0];
2382
2383 let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2384
2385 let parsed = parse_instruction::parse(
2386 &spl_token_interface::ID,
2387 compiled_instruction,
2388 &account_keys_for_parsing,
2389 None,
2390 )?;
2391
2392 Ok(parsed)
2393 }
2394
2395 fn create_parsed_spl_token_burn(
2396 account: &Pubkey,
2397 mint: &Pubkey,
2398 authority: &Pubkey,
2399 amount: u64,
2400 ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2401 {
2402 let solana_instruction = spl_token_interface::instruction::burn(
2403 &spl_token_interface::ID,
2404 account,
2405 mint,
2406 authority,
2407 &[],
2408 amount,
2409 )?;
2410
2411 let message = Message::new(&[solana_instruction], None);
2412 let compiled_instruction = &message.instructions[0];
2413
2414 let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2415
2416 let parsed = parse_instruction::parse(
2417 &spl_token_interface::ID,
2418 compiled_instruction,
2419 &account_keys_for_parsing,
2420 None,
2421 )?;
2422
2423 Ok(parsed)
2424 }
2425
2426 fn create_parsed_spl_token_burn_checked(
2427 account: &Pubkey,
2428 mint: &Pubkey,
2429 authority: &Pubkey,
2430 amount: u64,
2431 decimals: u8,
2432 ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2433 {
2434 let solana_instruction = spl_token_interface::instruction::burn_checked(
2435 &spl_token_interface::ID,
2436 account,
2437 mint,
2438 authority,
2439 &[],
2440 amount,
2441 decimals,
2442 )?;
2443
2444 let message = Message::new(&[solana_instruction], None);
2445 let compiled_instruction = &message.instructions[0];
2446
2447 let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2448
2449 let parsed = parse_instruction::parse(
2450 &spl_token_interface::ID,
2451 compiled_instruction,
2452 &account_keys_for_parsing,
2453 None,
2454 )?;
2455
2456 Ok(parsed)
2457 }
2458
2459 fn create_parsed_spl_token_close_account(
2460 account: &Pubkey,
2461 destination: &Pubkey,
2462 authority: &Pubkey,
2463 ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2464 {
2465 let solana_instruction = spl_token_interface::instruction::close_account(
2466 &spl_token_interface::ID,
2467 account,
2468 destination,
2469 authority,
2470 &[],
2471 )?;
2472
2473 let message = Message::new(&[solana_instruction], None);
2474 let compiled_instruction = &message.instructions[0];
2475
2476 let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2477
2478 let parsed = parse_instruction::parse(
2479 &spl_token_interface::ID,
2480 compiled_instruction,
2481 &account_keys_for_parsing,
2482 None,
2483 )?;
2484
2485 Ok(parsed)
2486 }
2487
2488 fn create_parsed_spl_token_approve(
2489 source: &Pubkey,
2490 delegate: &Pubkey,
2491 authority: &Pubkey,
2492 amount: u64,
2493 ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2494 {
2495 let solana_instruction = spl_token_interface::instruction::approve(
2496 &spl_token_interface::ID,
2497 source,
2498 delegate,
2499 authority,
2500 &[],
2501 amount,
2502 )?;
2503
2504 let message = Message::new(&[solana_instruction], None);
2505 let compiled_instruction = &message.instructions[0];
2506
2507 let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2508
2509 let parsed = parse_instruction::parse(
2510 &spl_token_interface::ID,
2511 compiled_instruction,
2512 &account_keys_for_parsing,
2513 None,
2514 )?;
2515
2516 Ok(parsed)
2517 }
2518
2519 fn create_parsed_spl_token_approve_checked(
2520 source: &Pubkey,
2521 mint: &Pubkey,
2522 delegate: &Pubkey,
2523 authority: &Pubkey,
2524 amount: u64,
2525 decimals: u8,
2526 ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2527 {
2528 let solana_instruction = spl_token_interface::instruction::approve_checked(
2529 &spl_token_interface::ID,
2530 source,
2531 mint,
2532 delegate,
2533 authority,
2534 &[],
2535 amount,
2536 decimals,
2537 )?;
2538
2539 let message = Message::new(&[solana_instruction], None);
2540 let compiled_instruction = &message.instructions[0];
2541
2542 let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2543
2544 let parsed = parse_instruction::parse(
2545 &spl_token_interface::ID,
2546 compiled_instruction,
2547 &account_keys_for_parsing,
2548 None,
2549 )?;
2550
2551 Ok(parsed)
2552 }
2553
2554 fn create_parsed_token2022_transfer(
2555 source: &Pubkey,
2556 destination: &Pubkey,
2557 authority: &Pubkey,
2558 amount: u64,
2559 ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2560 {
2561 #[allow(deprecated)]
2562 let solana_instruction = spl_token_2022_interface::instruction::transfer(
2563 &spl_token_2022_interface::ID,
2564 source,
2565 destination,
2566 authority,
2567 &[],
2568 amount,
2569 )?;
2570
2571 let message = Message::new(&[solana_instruction], None);
2572 let compiled_instruction = &message.instructions[0];
2573
2574 let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2575
2576 let parsed = parse_instruction::parse(
2577 &spl_token_2022_interface::ID,
2578 compiled_instruction,
2579 &account_keys_for_parsing,
2580 None,
2581 )?;
2582
2583 Ok(parsed)
2584 }
2585
2586 fn create_parsed_token2022_transfer_checked(
2587 source: &Pubkey,
2588 mint: &Pubkey,
2589 destination: &Pubkey,
2590 authority: &Pubkey,
2591 amount: u64,
2592 decimals: u8,
2593 ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2594 {
2595 let solana_instruction = spl_token_2022_interface::instruction::transfer_checked(
2596 &spl_token_2022_interface::ID,
2597 source,
2598 mint,
2599 destination,
2600 authority,
2601 &[],
2602 amount,
2603 decimals,
2604 )?;
2605
2606 let message = Message::new(&[solana_instruction], None);
2607 let compiled_instruction = &message.instructions[0];
2608
2609 let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2610
2611 let parsed = parse_instruction::parse(
2612 &spl_token_2022_interface::ID,
2613 compiled_instruction,
2614 &account_keys_for_parsing,
2615 None,
2616 )?;
2617
2618 Ok(parsed)
2619 }
2620
2621 fn create_parsed_token2022_burn(
2622 account: &Pubkey,
2623 mint: &Pubkey,
2624 authority: &Pubkey,
2625 amount: u64,
2626 ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2627 {
2628 let solana_instruction = spl_token_2022_interface::instruction::burn(
2629 &spl_token_2022_interface::ID,
2630 account,
2631 mint,
2632 authority,
2633 &[],
2634 amount,
2635 )?;
2636
2637 let message = Message::new(&[solana_instruction], None);
2638 let compiled_instruction = &message.instructions[0];
2639
2640 let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2641
2642 let parsed = parse_instruction::parse(
2643 &spl_token_2022_interface::ID,
2644 compiled_instruction,
2645 &account_keys_for_parsing,
2646 None,
2647 )?;
2648
2649 Ok(parsed)
2650 }
2651
2652 fn create_parsed_token2022_burn_checked(
2653 account: &Pubkey,
2654 mint: &Pubkey,
2655 authority: &Pubkey,
2656 amount: u64,
2657 decimals: u8,
2658 ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2659 {
2660 let solana_instruction = spl_token_2022_interface::instruction::burn_checked(
2661 &spl_token_2022_interface::ID,
2662 account,
2663 mint,
2664 authority,
2665 &[],
2666 amount,
2667 decimals,
2668 )?;
2669
2670 let message = Message::new(&[solana_instruction], None);
2671 let compiled_instruction = &message.instructions[0];
2672
2673 let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2674
2675 let parsed = parse_instruction::parse(
2676 &spl_token_2022_interface::ID,
2677 compiled_instruction,
2678 &account_keys_for_parsing,
2679 None,
2680 )?;
2681
2682 Ok(parsed)
2683 }
2684
2685 fn create_parsed_token2022_close_account(
2686 account: &Pubkey,
2687 destination: &Pubkey,
2688 authority: &Pubkey,
2689 ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2690 {
2691 let solana_instruction = spl_token_2022_interface::instruction::close_account(
2692 &spl_token_2022_interface::ID,
2693 account,
2694 destination,
2695 authority,
2696 &[],
2697 )?;
2698
2699 let message = Message::new(&[solana_instruction], None);
2700 let compiled_instruction = &message.instructions[0];
2701
2702 let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2703
2704 let parsed = parse_instruction::parse(
2705 &spl_token_2022_interface::ID,
2706 compiled_instruction,
2707 &account_keys_for_parsing,
2708 None,
2709 )?;
2710
2711 Ok(parsed)
2712 }
2713
2714 fn create_parsed_token2022_approve(
2715 source: &Pubkey,
2716 delegate: &Pubkey,
2717 authority: &Pubkey,
2718 amount: u64,
2719 ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2720 {
2721 let solana_instruction = spl_token_2022_interface::instruction::approve(
2722 &spl_token_2022_interface::ID,
2723 source,
2724 delegate,
2725 authority,
2726 &[],
2727 amount,
2728 )?;
2729
2730 let message = Message::new(&[solana_instruction], None);
2731 let compiled_instruction = &message.instructions[0];
2732
2733 let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2734
2735 let parsed = parse_instruction::parse(
2736 &spl_token_2022_interface::ID,
2737 compiled_instruction,
2738 &account_keys_for_parsing,
2739 None,
2740 )?;
2741
2742 Ok(parsed)
2743 }
2744
2745 fn create_parsed_token2022_approve_checked(
2746 source: &Pubkey,
2747 mint: &Pubkey,
2748 delegate: &Pubkey,
2749 authority: &Pubkey,
2750 amount: u64,
2751 decimals: u8,
2752 ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2753 {
2754 let solana_instruction = spl_token_2022_interface::instruction::approve_checked(
2755 &spl_token_2022_interface::ID,
2756 source,
2757 mint,
2758 delegate,
2759 authority,
2760 &[],
2761 amount,
2762 decimals,
2763 )?;
2764
2765 let message = Message::new(&[solana_instruction], None);
2766 let compiled_instruction = &message.instructions[0];
2767
2768 let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2769
2770 let parsed = parse_instruction::parse(
2771 &spl_token_2022_interface::ID,
2772 compiled_instruction,
2773 &account_keys_for_parsing,
2774 None,
2775 )?;
2776
2777 Ok(parsed)
2778 }
2779
2780 fn create_parsed_token2022_get_account_data_size(
2781 mint: &Pubkey,
2782 ) -> Result<solana_transaction_status_client_types::ParsedInstruction, Box<dyn std::error::Error>>
2783 {
2784 let solana_instruction = spl_token_2022_interface::instruction::get_account_data_size(
2785 &spl_token_2022_interface::ID,
2786 mint,
2787 &[],
2788 )?;
2789
2790 let message = Message::new(&[solana_instruction], None);
2791 let compiled_instruction = &message.instructions[0];
2792
2793 let account_keys_for_parsing = AccountKeys::new(&message.account_keys, None);
2794
2795 let parsed = parse_instruction::parse(
2796 &spl_token_2022_interface::ID,
2797 compiled_instruction,
2798 &account_keys_for_parsing,
2799 None,
2800 )?;
2801
2802 Ok(parsed)
2803 }
2804
2805 #[test]
2806 fn test_get_field_as_u64() {
2807 let valid_number = serde_json::json!({
2809 "amount": 1000
2810 });
2811 assert_eq!(IxUtils::get_field_as_u64(&valid_number, "amount").unwrap(), 1000);
2812
2813 let valid_string_number = serde_json::json!({
2815 "amount": "2000"
2816 });
2817 assert_eq!(IxUtils::get_field_as_u64(&valid_string_number, "amount").unwrap(), 2000);
2818
2819 let missing_field = serde_json::json!({
2821 "other": 3000
2822 });
2823 let err = IxUtils::get_field_as_u64(&missing_field, "amount").unwrap_err();
2824 assert!(matches!(err, crate::error::KoraError::SerializationError(_)));
2825
2826 let invalid_string = serde_json::json!({
2828 "amount": "invalid"
2829 });
2830 let err = IxUtils::get_field_as_u64(&invalid_string, "amount").unwrap_err();
2831 assert!(matches!(err, crate::error::KoraError::SerializationError(_)));
2832
2833 let null_value = serde_json::json!({
2835 "amount": null
2836 });
2837 let err = IxUtils::get_field_as_u64(&null_value, "amount").unwrap_err();
2838 assert!(matches!(err, crate::error::KoraError::SerializationError(_)));
2839
2840 let array_value = serde_json::json!({
2842 "amount": [1, 2, 3]
2843 });
2844 let err = IxUtils::get_field_as_u64(&array_value, "amount").unwrap_err();
2845 assert!(matches!(err, crate::error::KoraError::SerializationError(_)));
2846 }
2847
2848 #[test]
2849 fn test_parse_system_instructions_transfer() {
2850 use crate::transaction::versioned_transaction::VersionedTransactionResolved;
2851 use solana_message::{Message, VersionedMessage};
2852 use solana_sdk::{
2853 signature::{Keypair, Signer},
2854 transaction::VersionedTransaction,
2855 };
2856 use solana_system_interface::instruction::transfer;
2857
2858 let sender = Keypair::new();
2859 let receiver = Pubkey::new_unique();
2860 let lamports = 1000u64;
2861
2862 let instruction = transfer(&sender.pubkey(), &receiver, lamports);
2863
2864 let message =
2865 VersionedMessage::Legacy(Message::new(&[instruction], Some(&sender.pubkey())));
2866 let tx = VersionedTransaction::try_new(message, &[&sender]).unwrap();
2867
2868 let resolved_tx = VersionedTransactionResolved::from_kora_built_transaction(&tx)
2869 .expect("Failed to create resolved transaction");
2870
2871 let parsed_instructions = IxUtils::parse_system_instructions(&resolved_tx)
2872 .expect("Failed to parse system instructions");
2873
2874 let transfers = parsed_instructions
2875 .get(&ParsedSystemInstructionType::SystemTransfer)
2876 .expect("Expected SystemTransfer instructions");
2877
2878 assert_eq!(transfers.len(), 1);
2879
2880 match &transfers[0] {
2881 ParsedSystemInstructionData::SystemTransfer {
2882 lamports: parsed_lamports,
2883 sender: parsed_sender,
2884 receiver: parsed_receiver,
2885 } => {
2886 assert_eq!(*parsed_lamports, lamports);
2887 assert_eq!(*parsed_sender, sender.pubkey());
2888 assert_eq!(*parsed_receiver, receiver);
2889 }
2890 _ => panic!("Expected SystemTransfer variant"),
2891 }
2892 }
2893
2894 #[test]
2895 fn test_parse_token_instructions_transfer() {
2896 use crate::transaction::versioned_transaction::VersionedTransactionResolved;
2897 use solana_message::{Message, VersionedMessage};
2898 use solana_sdk::{
2899 signature::{Keypair, Signer},
2900 transaction::VersionedTransaction,
2901 };
2902 use spl_token_interface::instruction::transfer;
2903
2904 let owner = Keypair::new();
2905 let source_address = Pubkey::new_unique();
2906 let destination_address = Pubkey::new_unique();
2907 let amount = 5000u64;
2908
2909 let instruction = transfer(
2910 &spl_token_interface::ID,
2911 &source_address,
2912 &destination_address,
2913 &owner.pubkey(),
2914 &[],
2915 amount,
2916 )
2917 .expect("Failed to create transfer instruction");
2918
2919 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&owner.pubkey())));
2920 let tx = VersionedTransaction::try_new(message, &[&owner]).unwrap();
2921
2922 let resolved_tx = VersionedTransactionResolved::from_kora_built_transaction(&tx)
2923 .expect("Failed to create resolved transaction");
2924
2925 let parsed_instructions = IxUtils::parse_token_instructions(&resolved_tx)
2926 .expect("Failed to parse token instructions");
2927
2928 let transfers = parsed_instructions
2929 .get(&ParsedSPLInstructionType::SplTokenTransfer)
2930 .expect("Expected SplTokenTransfer instructions");
2931
2932 assert_eq!(transfers.len(), 1);
2933
2934 match &transfers[0] {
2935 ParsedSPLInstructionData::SplTokenTransfer {
2936 amount: parsed_amount,
2937 owner: parsed_owner,
2938 mint,
2939 source_address: parsed_source,
2940 destination_address: parsed_destination,
2941 is_2022,
2942 } => {
2943 assert_eq!(*parsed_amount, amount);
2944 assert_eq!(*parsed_owner, owner.pubkey());
2945 assert_eq!(*parsed_source, source_address);
2946 assert_eq!(*parsed_destination, destination_address);
2947 assert!(!is_2022);
2948 assert!(mint.is_none());
2949 }
2950 _ => panic!("Expected SplTokenTransfer variant"),
2951 }
2952 }
2953
2954 #[test]
2955 fn test_parse_token_2022_instructions_transfer_checked() {
2956 use crate::transaction::versioned_transaction::VersionedTransactionResolved;
2957 use solana_message::{Message, VersionedMessage};
2958 use solana_sdk::{
2959 signature::{Keypair, Signer},
2960 transaction::VersionedTransaction,
2961 };
2962 use spl_token_2022_interface::instruction::transfer_checked;
2963
2964 let owner = Keypair::new();
2965 let source_address = Pubkey::new_unique();
2966 let mint = Pubkey::new_unique();
2967 let destination_address = Pubkey::new_unique();
2968 let amount = 7500u64;
2969 let decimals = 6u8;
2970
2971 let instruction = transfer_checked(
2972 &spl_token_2022_interface::ID,
2973 &source_address,
2974 &mint,
2975 &destination_address,
2976 &owner.pubkey(),
2977 &[],
2978 amount,
2979 decimals,
2980 )
2981 .expect("Failed to create transfer_checked instruction");
2982
2983 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&owner.pubkey())));
2984 let tx = VersionedTransaction::try_new(message, &[&owner]).unwrap();
2985
2986 let resolved_tx = VersionedTransactionResolved::from_kora_built_transaction(&tx)
2987 .expect("Failed to create resolved transaction");
2988
2989 let parsed_instructions = IxUtils::parse_token_instructions(&resolved_tx)
2990 .expect("Failed to parse token instructions");
2991
2992 let transfers = parsed_instructions
2993 .get(&ParsedSPLInstructionType::SplTokenTransfer)
2994 .expect("Expected SplTokenTransfer instructions");
2995
2996 assert_eq!(transfers.len(), 1);
2997
2998 match &transfers[0] {
2999 ParsedSPLInstructionData::SplTokenTransfer {
3000 amount: parsed_amount,
3001 owner: parsed_owner,
3002 mint: parsed_mint,
3003 source_address: parsed_source,
3004 destination_address: parsed_destination,
3005 is_2022,
3006 } => {
3007 assert_eq!(*parsed_amount, amount);
3008 assert_eq!(*parsed_owner, owner.pubkey());
3009 assert_eq!(*parsed_source, source_address);
3010 assert_eq!(*parsed_destination, destination_address);
3011 assert!(*is_2022);
3012 assert_eq!(parsed_mint.unwrap(), mint);
3013 }
3014 _ => panic!("Expected SplTokenTransfer variant"),
3015 }
3016 }
3017
3018 #[test]
3019 fn test_parse_token_instructions_burn_checked_requires_three_accounts() {
3020 use crate::transaction::versioned_transaction::VersionedTransactionResolved;
3021 use solana_message::{Message, VersionedMessage};
3022 use solana_sdk::{
3023 instruction::{AccountMeta, Instruction},
3024 signature::{Keypair, Signer},
3025 transaction::VersionedTransaction,
3026 };
3027
3028 let payer = Keypair::new();
3029 let source = Pubkey::new_unique();
3030 let authority = Pubkey::new_unique();
3031 let burn_checked_data = spl_token_interface::instruction::TokenInstruction::BurnChecked {
3032 amount: 10,
3033 decimals: 6,
3034 }
3035 .pack();
3036
3037 let malformed_burn_checked = Instruction {
3038 program_id: spl_token_interface::ID,
3039 accounts: vec![
3040 AccountMeta::new(source, false),
3041 AccountMeta::new_readonly(authority, false),
3042 ],
3043 data: burn_checked_data,
3044 };
3045
3046 let message = VersionedMessage::Legacy(Message::new(
3047 &[malformed_burn_checked],
3048 Some(&payer.pubkey()),
3049 ));
3050 let tx = VersionedTransaction::try_new(message, &[&payer]).unwrap();
3051 let resolved_tx = VersionedTransactionResolved::from_kora_built_transaction(&tx)
3052 .expect("Failed to create resolved transaction");
3053
3054 let result = IxUtils::parse_token_instructions(&resolved_tx);
3055 assert!(result.is_err(), "BurnChecked with 2 accounts must be rejected");
3056 }
3057
3058 #[test]
3059 fn test_parse_token_2022_instructions_burn_checked_requires_three_accounts() {
3060 use crate::transaction::versioned_transaction::VersionedTransactionResolved;
3061 use solana_message::{Message, VersionedMessage};
3062 use solana_sdk::{
3063 instruction::{AccountMeta, Instruction},
3064 signature::{Keypair, Signer},
3065 transaction::VersionedTransaction,
3066 };
3067
3068 let payer = Keypair::new();
3069 let source = Pubkey::new_unique();
3070 let authority = Pubkey::new_unique();
3071 let burn_checked_data =
3072 spl_token_2022_interface::instruction::TokenInstruction::BurnChecked {
3073 amount: 10,
3074 decimals: 6,
3075 }
3076 .pack();
3077
3078 let malformed_burn_checked = Instruction {
3079 program_id: spl_token_2022_interface::ID,
3080 accounts: vec![
3081 AccountMeta::new(source, false),
3082 AccountMeta::new_readonly(authority, false),
3083 ],
3084 data: burn_checked_data,
3085 };
3086
3087 let message = VersionedMessage::Legacy(Message::new(
3088 &[malformed_burn_checked],
3089 Some(&payer.pubkey()),
3090 ));
3091 let tx = VersionedTransaction::try_new(message, &[&payer]).unwrap();
3092 let resolved_tx = VersionedTransactionResolved::from_kora_built_transaction(&tx)
3093 .expect("Failed to create resolved transaction");
3094
3095 let result = IxUtils::parse_token_instructions(&resolved_tx);
3096 assert!(result.is_err(), "Token-2022 BurnChecked with 2 accounts must be rejected");
3097 }
3098
3099 #[test]
3100 fn test_uncompile_instructions() {
3101 let program_id = Pubkey::new_unique();
3102 let account1 = Pubkey::new_unique();
3103 let account2 = Pubkey::new_unique();
3104
3105 let account_keys = vec![program_id, account1, account2];
3106 let compiled_ix = CompiledInstruction {
3107 program_id_index: 0,
3108 accounts: vec![1, 2], data: vec![1, 2, 3],
3110 };
3111
3112 let instructions = IxUtils::uncompile_instructions(&[compiled_ix], &account_keys).unwrap();
3113
3114 assert_eq!(instructions.len(), 1);
3115 let uncompiled = &instructions[0];
3116 assert_eq!(uncompiled.program_id, program_id);
3117 assert_eq!(uncompiled.accounts.len(), 2);
3118 assert_eq!(uncompiled.accounts[0].pubkey, account1);
3119 assert_eq!(uncompiled.accounts[1].pubkey, account2);
3120 assert_eq!(uncompiled.data, vec![1, 2, 3]);
3121 }
3122
3123 #[test]
3124 fn test_reconstruct_instruction_from_ui_compiled() {
3125 let program_id = Pubkey::new_unique();
3126 let account1 = Pubkey::new_unique();
3127 let mut account_keys = vec![program_id, account1];
3128
3129 let ui_compiled = solana_transaction_status_client_types::UiCompiledInstruction {
3130 program_id_index: 0,
3131 accounts: vec![1],
3132 data: bs58::encode(&[1, 2, 3]).into_string(),
3133 stack_height: None,
3134 };
3135
3136 let result = IxUtils::reconstruct_instruction_from_ui(
3137 &UiInstruction::Compiled(ui_compiled),
3138 &mut account_keys,
3139 );
3140
3141 assert!(result.is_ok());
3142 let compiled = result.unwrap();
3143 assert_eq!(compiled.program_id_index, 0);
3144 assert_eq!(compiled.accounts, vec![1]);
3145 assert_eq!(compiled.data, vec![1, 2, 3]);
3146 }
3147
3148 #[test]
3149 fn test_reconstruct_partially_decoded_instruction() {
3150 let program_id = Pubkey::new_unique();
3151 let account1 = Pubkey::new_unique();
3152 let account2 = Pubkey::new_unique();
3153 let mut account_keys = vec![program_id, account1, account2];
3154
3155 let partial = solana_transaction_status_client_types::UiPartiallyDecodedInstruction {
3156 program_id: program_id.to_string(),
3157 accounts: vec![account1.to_string(), account2.to_string()],
3158 data: bs58::encode(&[5, 6, 7]).into_string(),
3159 stack_height: None,
3160 };
3161
3162 let ui_parsed = UiParsedInstruction::PartiallyDecoded(partial);
3163
3164 let result = IxUtils::reconstruct_instruction_from_ui(
3165 &UiInstruction::Parsed(ui_parsed),
3166 &mut account_keys,
3167 );
3168
3169 assert!(result.is_ok());
3170 let compiled = result.unwrap();
3171 assert_eq!(compiled.program_id_index, 0);
3172 assert_eq!(compiled.accounts, vec![1, 2]); assert_eq!(compiled.data, vec![5, 6, 7]);
3174 }
3175
3176 #[test]
3177 fn test_reconstruct_partially_decoded_with_cpi_pda_accounts() {
3178 let program_id = Pubkey::new_unique();
3181 let known_account = Pubkey::new_unique();
3182 let cpi_pda_account = Pubkey::new_unique(); let mut account_keys = vec![program_id, known_account];
3184
3185 let partial = solana_transaction_status_client_types::UiPartiallyDecodedInstruction {
3186 program_id: program_id.to_string(),
3187 accounts: vec![known_account.to_string(), cpi_pda_account.to_string()],
3188 data: bs58::encode(&[8, 9, 10]).into_string(),
3189 stack_height: None,
3190 };
3191
3192 let ui_parsed = UiParsedInstruction::PartiallyDecoded(partial);
3193
3194 let result = IxUtils::reconstruct_instruction_from_ui(
3195 &UiInstruction::Parsed(ui_parsed),
3196 &mut account_keys,
3197 );
3198
3199 assert!(result.is_ok(), "Should succeed even with unknown CPI PDA account");
3200 let compiled = result.unwrap();
3201 assert_eq!(compiled.program_id_index, 0);
3202 assert_eq!(compiled.accounts.len(), 2);
3204 assert_eq!(compiled.accounts[0], 1); assert_eq!(compiled.accounts[1], 2); assert_eq!(compiled.data, vec![8, 9, 10]);
3207
3208 assert_eq!(account_keys.len(), 3);
3210 assert_eq!(account_keys[2], cpi_pda_account);
3211 }
3212
3213 #[test]
3214 fn test_reconstruct_partially_decoded_with_unknown_program_id() {
3215 let known_account = Pubkey::new_unique();
3218 let cpi_program = Pubkey::new_unique(); let cpi_account = Pubkey::new_unique(); let mut account_keys = vec![known_account];
3221
3222 let partial = solana_transaction_status_client_types::UiPartiallyDecodedInstruction {
3223 program_id: cpi_program.to_string(),
3224 accounts: vec![known_account.to_string(), cpi_account.to_string()],
3225 data: bs58::encode(&[1]).into_string(),
3226 stack_height: None,
3227 };
3228
3229 let ui_parsed = UiParsedInstruction::PartiallyDecoded(partial);
3230
3231 let result = IxUtils::reconstruct_instruction_from_ui(
3232 &UiInstruction::Parsed(ui_parsed),
3233 &mut account_keys,
3234 );
3235
3236 assert!(result.is_ok(), "Should succeed even with unknown program ID");
3237 let compiled = result.unwrap();
3238 assert_eq!(compiled.program_id_index, 1);
3240 assert_eq!(compiled.accounts[0], 0); assert_eq!(compiled.accounts[1], 2); assert_eq!(account_keys.len(), 3);
3243 }
3244
3245 #[test]
3246 fn test_reconstruct_partially_decoded_rejects_program_index_overflow() {
3247 let mut account_keys: Vec<Pubkey> = (0..256).map(|_| Pubkey::new_unique()).collect();
3248 let cpi_program = Pubkey::new_unique(); let partial = solana_transaction_status_client_types::UiPartiallyDecodedInstruction {
3251 program_id: cpi_program.to_string(),
3252 accounts: vec![account_keys[0].to_string()],
3253 data: bs58::encode(&[1]).into_string(),
3254 stack_height: None,
3255 };
3256
3257 let ui_parsed = UiParsedInstruction::PartiallyDecoded(partial);
3258
3259 let result = IxUtils::reconstruct_instruction_from_ui(
3260 &UiInstruction::Parsed(ui_parsed),
3261 &mut account_keys,
3262 );
3263
3264 assert!(result.is_err(), "should fail when program index would overflow u8");
3265 assert_eq!(account_keys.len(), 256, "account keys should remain unchanged on failure");
3266 }
3267
3268 #[test]
3269 fn test_reconstruct_partially_decoded_rejects_account_index_overflow() {
3270 let mut account_keys: Vec<Pubkey> = (0..256).map(|_| Pubkey::new_unique()).collect();
3271 let program_id = account_keys[0];
3272 let known_account = account_keys[1];
3273 let cpi_account = Pubkey::new_unique(); let partial = solana_transaction_status_client_types::UiPartiallyDecodedInstruction {
3276 program_id: program_id.to_string(),
3277 accounts: vec![known_account.to_string(), cpi_account.to_string()],
3278 data: bs58::encode(&[2]).into_string(),
3279 stack_height: None,
3280 };
3281
3282 let ui_parsed = UiParsedInstruction::PartiallyDecoded(partial);
3283
3284 let result = IxUtils::reconstruct_instruction_from_ui(
3285 &UiInstruction::Parsed(ui_parsed),
3286 &mut account_keys,
3287 );
3288
3289 assert!(result.is_err(), "should fail when account index would overflow u8");
3290 assert_eq!(account_keys.len(), 256, "account keys should remain unchanged on failure");
3291 }
3292
3293 #[test]
3294 fn test_reconstruct_partially_decoded_rolls_back_on_mid_loop_overflow() {
3295 let mut account_keys: Vec<Pubkey> = (0..254).map(|_| Pubkey::new_unique()).collect();
3296 let program_id = account_keys[0];
3297 let known_account = account_keys[1];
3298 let cpi_account_1 = Pubkey::new_unique();
3299 let cpi_account_2 = Pubkey::new_unique();
3300 let cpi_account_3 = Pubkey::new_unique(); let partial = solana_transaction_status_client_types::UiPartiallyDecodedInstruction {
3303 program_id: program_id.to_string(),
3304 accounts: vec![
3305 known_account.to_string(),
3306 cpi_account_1.to_string(),
3307 cpi_account_2.to_string(),
3308 cpi_account_3.to_string(),
3309 ],
3310 data: bs58::encode(&[3]).into_string(),
3311 stack_height: None,
3312 };
3313
3314 let ui_parsed = UiParsedInstruction::PartiallyDecoded(partial);
3315
3316 let result = IxUtils::reconstruct_instruction_from_ui(
3317 &UiInstruction::Parsed(ui_parsed),
3318 &mut account_keys,
3319 );
3320
3321 assert!(result.is_err(), "should fail when account index overflows mid-loop");
3322 assert_eq!(account_keys.len(), 254, "account keys should roll back to snapshot length");
3323 }
3324
3325 #[test]
3326 fn test_reconstruct_system_transfer_instruction() {
3327 let source = Pubkey::new_unique();
3328 let destination = Pubkey::new_unique();
3329 let system_program_id = SYSTEM_PROGRAM_ID;
3330 let account_keys = vec![system_program_id, source, destination];
3331 let lamports = 1000000u64;
3332
3333 let transfer_instruction =
3334 solana_system_interface::instruction::transfer(&source, &destination, lamports);
3335
3336 let solana_parsed_transfer = create_parsed_system_transfer(&source, &destination, lamports)
3337 .expect("Failed to create authentic parsed instruction");
3338
3339 let result = IxUtils::reconstruct_system_instruction(
3340 &solana_parsed_transfer,
3341 &IxUtils::build_account_keys_hashmap(&account_keys),
3342 );
3343
3344 assert!(result.is_ok());
3345 let compiled = result.unwrap();
3346 assert_eq!(compiled.program_id_index, 0);
3347 assert_eq!(compiled.accounts, vec![1, 2]); assert_eq!(compiled.data, transfer_instruction.data);
3349 }
3350
3351 #[test]
3352 fn test_reconstruct_system_transfer_with_seed_instruction() {
3353 let source = Pubkey::new_unique();
3354 let destination = Pubkey::new_unique();
3355 let source_base = Pubkey::new_unique();
3356 let source_owner = Pubkey::new_unique();
3357 let system_program_id = SYSTEM_PROGRAM_ID;
3358 let account_keys = vec![system_program_id, source, source_base, destination];
3359 let lamports = 5000000u64;
3360
3361 let instruction = solana_system_interface::instruction::transfer_with_seed(
3362 &source,
3363 &source_base,
3364 "test_seed".to_string(),
3365 &source_owner,
3366 &destination,
3367 lamports,
3368 );
3369
3370 let solana_parsed = create_parsed_system_transfer_with_seed(
3371 &source,
3372 &destination,
3373 lamports,
3374 &source_base,
3375 "test_seed",
3376 &source_owner,
3377 )
3378 .expect("Failed to create parsed instruction");
3379
3380 let result = IxUtils::reconstruct_system_instruction(
3381 &solana_parsed,
3382 &IxUtils::build_account_keys_hashmap(&account_keys),
3383 );
3384
3385 assert!(result.is_ok());
3386 let compiled = result.unwrap();
3387 assert_eq!(compiled.program_id_index, 0);
3388 assert_eq!(compiled.accounts, vec![1, 2, 3]); assert_eq!(compiled.data, instruction.data);
3390 }
3391
3392 #[test]
3393 fn test_reconstruct_system_create_account_instruction() {
3394 let source = Pubkey::new_unique();
3395 let new_account = Pubkey::new_unique();
3396 let owner = Pubkey::new_unique();
3397 let system_program_id = SYSTEM_PROGRAM_ID;
3398 let account_keys = vec![system_program_id, source, new_account];
3399 let lamports = 2000000u64;
3400 let space = 165u64;
3401
3402 let instruction = solana_system_interface::instruction::create_account(
3403 &source,
3404 &new_account,
3405 lamports,
3406 space,
3407 &owner,
3408 );
3409
3410 let solana_parsed =
3411 create_parsed_system_create_account(&source, &new_account, lamports, space, &owner)
3412 .expect("Failed to create parsed instruction");
3413
3414 let result = IxUtils::reconstruct_system_instruction(
3415 &solana_parsed,
3416 &IxUtils::build_account_keys_hashmap(&account_keys),
3417 );
3418
3419 assert!(result.is_ok());
3420 let compiled = result.unwrap();
3421 assert_eq!(compiled.program_id_index, 0);
3422 assert_eq!(compiled.accounts, vec![1, 2]); assert_eq!(compiled.data, instruction.data);
3424 }
3425
3426 #[test]
3427 fn test_reconstruct_system_create_account_with_seed_instruction() {
3428 let source = Pubkey::new_unique();
3429 let new_account = Pubkey::new_unique();
3430 let base = Pubkey::new_unique();
3431 let owner = Pubkey::new_unique();
3432 let system_program_id = SYSTEM_PROGRAM_ID;
3433 let account_keys = vec![system_program_id, source, new_account, base];
3434 let lamports = 3000000u64;
3435 let space = 200u64;
3436
3437 let instruction = solana_system_interface::instruction::create_account_with_seed(
3438 &source,
3439 &new_account,
3440 &base,
3441 "test_seed_create",
3442 lamports,
3443 space,
3444 &owner,
3445 );
3446
3447 let solana_parsed = create_parsed_system_create_account_with_seed(
3448 &source,
3449 &new_account,
3450 &base,
3451 "test_seed_create",
3452 lamports,
3453 space,
3454 &owner,
3455 )
3456 .expect("Failed to create parsed instruction");
3457
3458 let result = IxUtils::reconstruct_system_instruction(
3459 &solana_parsed,
3460 &IxUtils::build_account_keys_hashmap(&account_keys),
3461 );
3462
3463 assert!(result.is_ok());
3464 let compiled = result.unwrap();
3465 assert_eq!(compiled.program_id_index, 0);
3466 assert_eq!(compiled.accounts, vec![1, 2, 3]); assert_eq!(compiled.data, instruction.data);
3468 }
3469
3470 #[test]
3471 fn test_reconstruct_system_assign_instruction() {
3472 let account = Pubkey::new_unique();
3473 let owner = Pubkey::new_unique();
3474 let system_program_id = SYSTEM_PROGRAM_ID;
3475 let account_keys = vec![system_program_id, account];
3476
3477 let instruction = solana_system_interface::instruction::assign(&account, &owner);
3478
3479 let solana_parsed = create_parsed_system_assign(&account, &owner)
3480 .expect("Failed to create parsed instruction");
3481
3482 let result = IxUtils::reconstruct_system_instruction(
3483 &solana_parsed,
3484 &IxUtils::build_account_keys_hashmap(&account_keys),
3485 );
3486
3487 assert!(result.is_ok());
3488 let compiled = result.unwrap();
3489 assert_eq!(compiled.program_id_index, 0);
3490 assert_eq!(compiled.accounts, vec![1]); assert_eq!(compiled.data, instruction.data);
3492 }
3493
3494 #[test]
3495 fn test_reconstruct_system_assign_with_seed_instruction() {
3496 let account = Pubkey::new_unique();
3497 let base = Pubkey::new_unique();
3498 let owner = Pubkey::new_unique();
3499 let system_program_id = SYSTEM_PROGRAM_ID;
3500 let account_keys = vec![system_program_id, account, base];
3501
3502 let instruction = solana_system_interface::instruction::assign_with_seed(
3503 &account,
3504 &base,
3505 "test_assign_seed",
3506 &owner,
3507 );
3508
3509 let solana_parsed =
3510 create_parsed_system_assign_with_seed(&account, &base, "test_assign_seed", &owner)
3511 .expect("Failed to create parsed instruction");
3512
3513 let result = IxUtils::reconstruct_system_instruction(
3514 &solana_parsed,
3515 &IxUtils::build_account_keys_hashmap(&account_keys),
3516 );
3517
3518 assert!(result.is_ok());
3519 let compiled = result.unwrap();
3520 assert_eq!(compiled.program_id_index, 0);
3521 assert_eq!(compiled.accounts, vec![1, 2]); assert_eq!(compiled.data, instruction.data);
3523 }
3524
3525 #[test]
3526 fn test_reconstruct_system_withdraw_nonce_account_instruction() {
3527 let nonce_account = Pubkey::new_unique();
3528 let recipient = Pubkey::new_unique();
3529 let nonce_authority = Pubkey::new_unique();
3530 let system_program_id = SYSTEM_PROGRAM_ID;
3531 let account_keys = vec![system_program_id, nonce_account, recipient, nonce_authority];
3532 let lamports = 1500000u64;
3533
3534 let instruction = solana_system_interface::instruction::withdraw_nonce_account(
3535 &nonce_account,
3536 &nonce_authority,
3537 &recipient,
3538 lamports,
3539 );
3540
3541 let solana_parsed = create_parsed_system_withdraw_nonce_account(
3542 &nonce_account,
3543 &nonce_authority,
3544 &recipient,
3545 lamports,
3546 )
3547 .expect("Failed to create parsed instruction");
3548
3549 let result = IxUtils::reconstruct_system_instruction(
3550 &solana_parsed,
3551 &IxUtils::build_account_keys_hashmap(&account_keys),
3552 );
3553
3554 assert!(result.is_ok());
3555 let compiled = result.unwrap();
3556 assert_eq!(compiled.program_id_index, 0);
3557 assert_eq!(compiled.accounts, vec![1, 2, 3]); assert_eq!(compiled.data, instruction.data);
3559 }
3560
3561 #[test]
3562 fn test_reconstruct_spl_token_transfer_instruction() {
3563 let source = Pubkey::new_unique();
3564 let destination = Pubkey::new_unique();
3565 let authority = Pubkey::new_unique();
3566 let token_program_id = spl_token_interface::ID;
3567 let account_keys = vec![token_program_id, source, destination, authority];
3568 let amount = 1000000u64;
3569
3570 let transfer_instruction = spl_token_interface::instruction::transfer(
3571 &spl_token_interface::ID,
3572 &source,
3573 &destination,
3574 &authority,
3575 &[],
3576 amount,
3577 )
3578 .expect("Failed to create transfer instruction");
3579
3580 let solana_parsed_transfer =
3581 create_parsed_spl_token_transfer(&source, &destination, &authority, amount)
3582 .expect("Failed to create parsed instruction");
3583
3584 let result = IxUtils::reconstruct_spl_token_instruction(
3585 &solana_parsed_transfer,
3586 &IxUtils::build_account_keys_hashmap(&account_keys),
3587 );
3588
3589 assert!(result.is_ok());
3590 let compiled = result.unwrap();
3591 assert_eq!(compiled.program_id_index, 0);
3592 assert_eq!(compiled.accounts, vec![1, 2, 3]); assert_eq!(compiled.data, transfer_instruction.data);
3594 }
3595
3596 #[test]
3597 fn test_reconstruct_spl_token_transfer_checked_instruction() {
3598 let source = Pubkey::new_unique();
3599 let destination = Pubkey::new_unique();
3600 let authority = Pubkey::new_unique();
3601 let mint = Pubkey::new_unique();
3602 let token_program_id = spl_token_interface::ID;
3603 let account_keys = vec![token_program_id, source, mint, destination, authority];
3604 let amount = 2000000u64;
3605 let decimals = 6u8;
3606
3607 let instruction = spl_token_interface::instruction::transfer_checked(
3608 &spl_token_interface::ID,
3609 &source,
3610 &mint,
3611 &destination,
3612 &authority,
3613 &[],
3614 amount,
3615 decimals,
3616 )
3617 .expect("Failed to create transfer_checked instruction");
3618
3619 let solana_parsed = create_parsed_spl_token_transfer_checked(
3620 &source,
3621 &mint,
3622 &destination,
3623 &authority,
3624 amount,
3625 decimals,
3626 )
3627 .expect("Failed to create parsed instruction");
3628
3629 let result = IxUtils::reconstruct_spl_token_instruction(
3630 &solana_parsed,
3631 &IxUtils::build_account_keys_hashmap(&account_keys),
3632 );
3633
3634 assert!(result.is_ok());
3635 let compiled = result.unwrap();
3636 assert_eq!(compiled.program_id_index, 0);
3637 assert_eq!(compiled.accounts, vec![1, 2, 3, 4]); assert_eq!(compiled.data, instruction.data);
3639 }
3640
3641 #[test]
3642 fn test_reconstruct_spl_token_burn_instruction() {
3643 let account = Pubkey::new_unique();
3644 let mint = Pubkey::new_unique();
3645 let authority = Pubkey::new_unique();
3646 let token_program_id = spl_token_interface::ID;
3647 let account_keys = vec![token_program_id, account, mint, authority];
3648 let amount = 500000u64;
3649
3650 let instruction = spl_token_interface::instruction::burn(
3651 &spl_token_interface::ID,
3652 &account,
3653 &mint,
3654 &authority,
3655 &[],
3656 amount,
3657 )
3658 .expect("Failed to create burn instruction");
3659
3660 let solana_parsed = create_parsed_spl_token_burn(&account, &mint, &authority, amount)
3661 .expect("Failed to create parsed instruction");
3662
3663 let result = IxUtils::reconstruct_spl_token_instruction(
3664 &solana_parsed,
3665 &IxUtils::build_account_keys_hashmap(&account_keys),
3666 );
3667
3668 assert!(result.is_ok());
3669 let compiled = result.unwrap();
3670 assert_eq!(compiled.program_id_index, 0);
3671 assert_eq!(compiled.accounts, vec![1, 2, 3]); assert_eq!(compiled.data, instruction.data);
3673 }
3674
3675 #[test]
3676 fn test_reconstruct_spl_token_burn_instruction_falls_back_without_mint_index() {
3677 let account = Pubkey::new_unique();
3678 let mint = Pubkey::new_unique();
3679 let authority = Pubkey::new_unique();
3680 let token_program_id = spl_token_interface::ID;
3681 let account_keys = vec![token_program_id, account, authority];
3683 let amount = 500000u64;
3684
3685 let instruction = spl_token_interface::instruction::burn(
3686 &spl_token_interface::ID,
3687 &account,
3688 &mint,
3689 &authority,
3690 &[],
3691 amount,
3692 )
3693 .expect("Failed to create burn instruction");
3694
3695 let solana_parsed = create_parsed_spl_token_burn(&account, &mint, &authority, amount)
3696 .expect("Failed to create parsed instruction");
3697
3698 let result = IxUtils::reconstruct_spl_token_instruction(
3699 &solana_parsed,
3700 &IxUtils::build_account_keys_hashmap(&account_keys),
3701 );
3702
3703 assert!(result.is_ok());
3704 let compiled = result.unwrap();
3705 assert_eq!(compiled.program_id_index, 0);
3706 assert_eq!(compiled.accounts, vec![1, 2]); assert_eq!(compiled.data, instruction.data);
3708 }
3709
3710 #[test]
3711 fn test_reconstruct_spl_token_burn_checked_instruction() {
3712 let account = Pubkey::new_unique();
3713 let authority = Pubkey::new_unique();
3714 let mint = Pubkey::new_unique();
3715 let token_program_id = spl_token_interface::ID;
3716 let account_keys = vec![token_program_id, account, mint, authority];
3717 let amount = 750000u64;
3718 let decimals = 6u8;
3719
3720 let instruction = spl_token_interface::instruction::burn_checked(
3721 &spl_token_interface::ID,
3722 &account,
3723 &mint,
3724 &authority,
3725 &[],
3726 amount,
3727 decimals,
3728 )
3729 .expect("Failed to create burn_checked instruction");
3730
3731 let solana_parsed =
3732 create_parsed_spl_token_burn_checked(&account, &mint, &authority, amount, decimals)
3733 .expect("Failed to create parsed instruction");
3734
3735 let result = IxUtils::reconstruct_spl_token_instruction(
3736 &solana_parsed,
3737 &IxUtils::build_account_keys_hashmap(&account_keys),
3738 );
3739
3740 assert!(result.is_ok());
3741 let compiled = result.unwrap();
3742 assert_eq!(compiled.program_id_index, 0);
3743 assert_eq!(compiled.accounts, vec![1, 2, 3]); assert_eq!(compiled.data, instruction.data);
3745 }
3746
3747 #[test]
3748 fn test_reconstruct_spl_token_close_account_instruction() {
3749 let account = Pubkey::new_unique();
3750 let destination = Pubkey::new_unique();
3751 let authority = Pubkey::new_unique();
3752 let token_program_id = spl_token_interface::ID;
3753 let account_keys = vec![token_program_id, account, destination, authority];
3754
3755 let instruction = spl_token_interface::instruction::close_account(
3756 &spl_token_interface::ID,
3757 &account,
3758 &destination,
3759 &authority,
3760 &[],
3761 )
3762 .expect("Failed to create close_account instruction");
3763
3764 let solana_parsed =
3765 create_parsed_spl_token_close_account(&account, &destination, &authority)
3766 .expect("Failed to create parsed instruction");
3767
3768 let result = IxUtils::reconstruct_spl_token_instruction(
3769 &solana_parsed,
3770 &IxUtils::build_account_keys_hashmap(&account_keys),
3771 );
3772
3773 assert!(result.is_ok());
3774 let compiled = result.unwrap();
3775 assert_eq!(compiled.program_id_index, 0);
3776 assert_eq!(compiled.accounts, vec![1, 2, 3]); assert_eq!(compiled.data, instruction.data);
3778 }
3779
3780 #[test]
3781 fn test_reconstruct_spl_token_approve_instruction() {
3782 let source = Pubkey::new_unique();
3783 let delegate = Pubkey::new_unique();
3784 let owner = Pubkey::new_unique();
3785 let token_program_id = spl_token_interface::ID;
3786 let account_keys = vec![token_program_id, source, delegate, owner];
3787 let amount = 1000000u64;
3788
3789 let instruction = spl_token_interface::instruction::approve(
3790 &spl_token_interface::ID,
3791 &source,
3792 &delegate,
3793 &owner,
3794 &[],
3795 amount,
3796 )
3797 .expect("Failed to create approve instruction");
3798
3799 let solana_parsed = create_parsed_spl_token_approve(&source, &delegate, &owner, amount)
3800 .expect("Failed to create parsed instruction");
3801
3802 let result = IxUtils::reconstruct_spl_token_instruction(
3803 &solana_parsed,
3804 &IxUtils::build_account_keys_hashmap(&account_keys),
3805 );
3806
3807 assert!(result.is_ok());
3808 let compiled = result.unwrap();
3809 assert_eq!(compiled.program_id_index, 0);
3810 assert_eq!(compiled.accounts, vec![1, 2, 3]); assert_eq!(compiled.data, instruction.data);
3812 }
3813
3814 #[test]
3815 fn test_reconstruct_spl_token_approve_checked_instruction() {
3816 let source = Pubkey::new_unique();
3817 let delegate = Pubkey::new_unique();
3818 let owner = Pubkey::new_unique();
3819 let mint = Pubkey::new_unique();
3820 let token_program_id = spl_token_interface::ID;
3821 let account_keys = vec![token_program_id, source, mint, delegate, owner];
3822 let amount = 2500000u64;
3823 let decimals = 6u8;
3824
3825 let instruction = spl_token_interface::instruction::approve_checked(
3826 &spl_token_interface::ID,
3827 &source,
3828 &mint,
3829 &delegate,
3830 &owner,
3831 &[],
3832 amount,
3833 decimals,
3834 )
3835 .expect("Failed to create approve_checked instruction");
3836
3837 let solana_parsed = create_parsed_spl_token_approve_checked(
3838 &source, &mint, &delegate, &owner, amount, decimals,
3839 )
3840 .expect("Failed to create parsed instruction");
3841
3842 let result = IxUtils::reconstruct_spl_token_instruction(
3843 &solana_parsed,
3844 &IxUtils::build_account_keys_hashmap(&account_keys),
3845 );
3846
3847 assert!(result.is_ok());
3848 let compiled = result.unwrap();
3849 assert_eq!(compiled.program_id_index, 0);
3850 assert_eq!(compiled.accounts, vec![1, 2, 3, 4]); assert_eq!(compiled.data, instruction.data);
3852 }
3853
3854 #[test]
3855 fn test_reconstruct_token2022_transfer_instruction() {
3856 let source = Pubkey::new_unique();
3857 let destination = Pubkey::new_unique();
3858 let authority = Pubkey::new_unique();
3859 let token_program_id = spl_token_2022_interface::ID;
3860 let account_keys = vec![token_program_id, source, destination, authority];
3861 let amount = 1500000u64;
3862
3863 #[allow(deprecated)]
3864 let instruction = spl_token_2022_interface::instruction::transfer(
3865 &spl_token_2022_interface::ID,
3866 &source,
3867 &destination,
3868 &authority,
3869 &[],
3870 amount,
3871 )
3872 .expect("Failed to create transfer instruction");
3873
3874 let solana_parsed =
3875 create_parsed_token2022_transfer(&source, &destination, &authority, amount)
3876 .expect("Failed to create parsed instruction");
3877
3878 let result = IxUtils::reconstruct_spl_token_instruction(
3879 &solana_parsed,
3880 &IxUtils::build_account_keys_hashmap(&account_keys),
3881 );
3882
3883 assert!(result.is_ok());
3884 let compiled = result.unwrap();
3885 assert_eq!(compiled.program_id_index, 0);
3886 assert_eq!(compiled.accounts, vec![1, 2, 3]); assert_eq!(compiled.data, instruction.data);
3888 }
3889
3890 #[test]
3891 fn test_reconstruct_token2022_transfer_checked_instruction() {
3892 let source = Pubkey::new_unique();
3893 let destination = Pubkey::new_unique();
3894 let authority = Pubkey::new_unique();
3895 let mint = Pubkey::new_unique();
3896 let token_program_id = spl_token_2022_interface::ID;
3897 let account_keys = vec![token_program_id, source, mint, destination, authority];
3898 let amount = 3000000u64;
3899 let decimals = 6u8;
3900
3901 let instruction = spl_token_2022_interface::instruction::transfer_checked(
3902 &spl_token_2022_interface::ID,
3903 &source,
3904 &mint,
3905 &destination,
3906 &authority,
3907 &[],
3908 amount,
3909 decimals,
3910 )
3911 .expect("Failed to create transfer_checked instruction");
3912
3913 let solana_parsed = create_parsed_token2022_transfer_checked(
3914 &source,
3915 &mint,
3916 &destination,
3917 &authority,
3918 amount,
3919 decimals,
3920 )
3921 .expect("Failed to create parsed instruction");
3922
3923 let result = IxUtils::reconstruct_spl_token_instruction(
3924 &solana_parsed,
3925 &IxUtils::build_account_keys_hashmap(&account_keys),
3926 );
3927
3928 assert!(result.is_ok());
3929 let compiled = result.unwrap();
3930 assert_eq!(compiled.program_id_index, 0);
3931 assert_eq!(compiled.accounts, vec![1, 2, 3, 4]); assert_eq!(compiled.data, instruction.data);
3933 }
3934
3935 #[test]
3936 fn test_reconstruct_token2022_get_account_data_size_instruction() {
3937 let mint = Pubkey::new_unique();
3938 let token_program_id = spl_token_2022_interface::ID;
3939 let account_keys = vec![token_program_id, mint];
3940
3941 let instruction = spl_token_2022_interface::instruction::get_account_data_size(
3942 &spl_token_2022_interface::ID,
3943 &mint,
3944 &[],
3945 )
3946 .expect("Failed to create get_account_data_size instruction");
3947
3948 let solana_parsed = create_parsed_token2022_get_account_data_size(&mint)
3949 .expect("Failed to create parsed instruction");
3950
3951 let result = IxUtils::reconstruct_spl_token_instruction(
3952 &solana_parsed,
3953 &IxUtils::build_account_keys_hashmap(&account_keys),
3954 );
3955
3956 assert!(result.is_ok());
3957 let compiled = result.unwrap();
3958 assert_eq!(compiled.program_id_index, 0);
3959 assert_eq!(compiled.accounts, vec![1]); assert_eq!(compiled.data, instruction.data);
3961 }
3962
3963 #[test]
3964 fn test_reconstruct_token2022_burn_instruction() {
3965 let account = Pubkey::new_unique();
3966 let mint = Pubkey::new_unique();
3967 let authority = Pubkey::new_unique();
3968 let token_program_id = spl_token_2022_interface::ID;
3969 let account_keys = vec![token_program_id, account, mint, authority];
3970 let amount = 800000u64;
3971
3972 let instruction = spl_token_2022_interface::instruction::burn(
3973 &spl_token_2022_interface::ID,
3974 &account,
3975 &mint,
3976 &authority,
3977 &[],
3978 amount,
3979 )
3980 .expect("Failed to create burn instruction");
3981
3982 let solana_parsed = create_parsed_token2022_burn(&account, &mint, &authority, amount)
3983 .expect("Failed to create parsed instruction");
3984
3985 let result = IxUtils::reconstruct_spl_token_instruction(
3986 &solana_parsed,
3987 &IxUtils::build_account_keys_hashmap(&account_keys),
3988 );
3989
3990 assert!(result.is_ok());
3991 let compiled = result.unwrap();
3992 assert_eq!(compiled.program_id_index, 0);
3993 assert_eq!(compiled.accounts, vec![1, 2, 3]); assert_eq!(compiled.data, instruction.data);
3995 }
3996
3997 #[test]
3998 fn test_reconstruct_token2022_burn_checked_instruction() {
3999 let account = Pubkey::new_unique();
4000 let authority = Pubkey::new_unique();
4001 let mint = Pubkey::new_unique();
4002 let token_program_id = spl_token_2022_interface::ID;
4003 let account_keys = vec![token_program_id, account, mint, authority];
4004 let amount = 900000u64;
4005 let decimals = 6u8;
4006
4007 let instruction = spl_token_2022_interface::instruction::burn_checked(
4008 &spl_token_2022_interface::ID,
4009 &account,
4010 &mint,
4011 &authority,
4012 &[],
4013 amount,
4014 decimals,
4015 )
4016 .expect("Failed to create burn_checked instruction");
4017
4018 let solana_parsed =
4019 create_parsed_token2022_burn_checked(&account, &mint, &authority, amount, decimals)
4020 .expect("Failed to create parsed instruction");
4021
4022 let result = IxUtils::reconstruct_spl_token_instruction(
4023 &solana_parsed,
4024 &IxUtils::build_account_keys_hashmap(&account_keys),
4025 );
4026
4027 assert!(result.is_ok());
4028 let compiled = result.unwrap();
4029 assert_eq!(compiled.program_id_index, 0);
4030 assert_eq!(compiled.accounts, vec![1, 2, 3]); assert_eq!(compiled.data, instruction.data);
4032 }
4033
4034 #[test]
4035 fn test_reconstruct_token2022_close_account_instruction() {
4036 let account = Pubkey::new_unique();
4037 let destination = Pubkey::new_unique();
4038 let authority = Pubkey::new_unique();
4039 let token_program_id = spl_token_2022_interface::ID;
4040 let account_keys = vec![token_program_id, account, destination, authority];
4041
4042 let instruction = spl_token_2022_interface::instruction::close_account(
4043 &spl_token_2022_interface::ID,
4044 &account,
4045 &destination,
4046 &authority,
4047 &[],
4048 )
4049 .expect("Failed to create close_account instruction");
4050
4051 let solana_parsed =
4052 create_parsed_token2022_close_account(&account, &destination, &authority)
4053 .expect("Failed to create parsed instruction");
4054
4055 let result = IxUtils::reconstruct_spl_token_instruction(
4056 &solana_parsed,
4057 &IxUtils::build_account_keys_hashmap(&account_keys),
4058 );
4059
4060 assert!(result.is_ok());
4061 let compiled = result.unwrap();
4062 assert_eq!(compiled.program_id_index, 0);
4063 assert_eq!(compiled.accounts, vec![1, 2, 3]); assert_eq!(compiled.data, instruction.data);
4065 }
4066
4067 #[test]
4068 fn test_reconstruct_token2022_approve_instruction() {
4069 let source = Pubkey::new_unique();
4070 let delegate = Pubkey::new_unique();
4071 let owner = Pubkey::new_unique();
4072 let token_program_id = spl_token_2022_interface::ID;
4073 let account_keys = vec![token_program_id, source, delegate, owner];
4074 let amount = 1200000u64;
4075
4076 let instruction = spl_token_2022_interface::instruction::approve(
4077 &spl_token_2022_interface::ID,
4078 &source,
4079 &delegate,
4080 &owner,
4081 &[],
4082 amount,
4083 )
4084 .expect("Failed to create approve instruction");
4085
4086 let solana_parsed = create_parsed_token2022_approve(&source, &delegate, &owner, amount)
4087 .expect("Failed to create parsed instruction");
4088
4089 let result = IxUtils::reconstruct_spl_token_instruction(
4090 &solana_parsed,
4091 &IxUtils::build_account_keys_hashmap(&account_keys),
4092 );
4093
4094 assert!(result.is_ok());
4095 let compiled = result.unwrap();
4096 assert_eq!(compiled.program_id_index, 0);
4097 assert_eq!(compiled.accounts, vec![1, 2, 3]); assert_eq!(compiled.data, instruction.data);
4099 }
4100
4101 #[test]
4102 fn test_reconstruct_token2022_approve_checked_instruction() {
4103 let source = Pubkey::new_unique();
4104 let delegate = Pubkey::new_unique();
4105 let owner = Pubkey::new_unique();
4106 let mint = Pubkey::new_unique();
4107 let token_program_id = spl_token_2022_interface::ID;
4108 let account_keys = vec![token_program_id, source, mint, delegate, owner];
4109 let amount = 3500000u64;
4110 let decimals = 6u8;
4111
4112 let instruction = spl_token_2022_interface::instruction::approve_checked(
4113 &spl_token_2022_interface::ID,
4114 &source,
4115 &mint,
4116 &delegate,
4117 &owner,
4118 &[],
4119 amount,
4120 decimals,
4121 )
4122 .expect("Failed to create approve_checked instruction");
4123
4124 let solana_parsed = create_parsed_token2022_approve_checked(
4125 &source, &mint, &delegate, &owner, amount, decimals,
4126 )
4127 .expect("Failed to create parsed instruction");
4128
4129 let result = IxUtils::reconstruct_spl_token_instruction(
4130 &solana_parsed,
4131 &IxUtils::build_account_keys_hashmap(&account_keys),
4132 );
4133
4134 assert!(result.is_ok());
4135 let compiled = result.unwrap();
4136 assert_eq!(compiled.program_id_index, 0);
4137 assert_eq!(compiled.accounts, vec![1, 2, 3, 4]); assert_eq!(compiled.data, instruction.data);
4139 }
4140
4141 #[test]
4142 fn test_dispatch_routes_spl_token_via_program_id() {
4143 let source = Pubkey::new_unique();
4144 let destination = Pubkey::new_unique();
4145 let authority = Pubkey::new_unique();
4146 let token_program_id = spl_token_interface::ID;
4147 let mut account_keys = vec![token_program_id, source, destination, authority];
4148 let amount = 1000000u64;
4149
4150 let parsed = create_parsed_spl_token_transfer(&source, &destination, &authority, amount)
4151 .expect("Failed to create parsed instruction");
4152
4153 let ui_instruction = UiInstruction::Parsed(UiParsedInstruction::Parsed(parsed));
4154
4155 let result = IxUtils::reconstruct_instruction_from_ui(&ui_instruction, &mut account_keys);
4156
4157 assert!(result.is_ok(), "SPL token transfer should be dispatched via program_id");
4158 let compiled = result.unwrap();
4159 assert_eq!(compiled.program_id_index, 0);
4160 assert_eq!(compiled.accounts, vec![1, 2, 3]);
4161 }
4162
4163 #[test]
4164 fn test_dispatch_routes_token2022_via_program_id() {
4165 let source = Pubkey::new_unique();
4166 let destination = Pubkey::new_unique();
4167 let authority = Pubkey::new_unique();
4168 let token_program_id = spl_token_2022_interface::ID;
4169 let mut account_keys = vec![token_program_id, source, destination, authority];
4170 let amount = 1000000u64;
4171
4172 let parsed = create_parsed_token2022_transfer(&source, &destination, &authority, amount)
4173 .expect("Failed to create parsed instruction");
4174
4175 let ui_instruction = UiInstruction::Parsed(UiParsedInstruction::Parsed(parsed));
4176
4177 let result = IxUtils::reconstruct_instruction_from_ui(&ui_instruction, &mut account_keys);
4178
4179 assert!(result.is_ok(), "Token2022 transfer should be dispatched via program_id");
4180 let compiled = result.unwrap();
4181 assert_eq!(compiled.program_id_index, 0);
4182 assert_eq!(compiled.accounts, vec![1, 2, 3]);
4183 }
4184
4185 #[test]
4186 fn test_dispatch_routes_system_program_via_program_id() {
4187 let source = Pubkey::new_unique();
4188 let destination = Pubkey::new_unique();
4189 let system_program_id = SYSTEM_PROGRAM_ID;
4190 let mut account_keys = vec![system_program_id, source, destination];
4191 let lamports = 1000000u64;
4192
4193 let parsed = create_parsed_system_transfer(&source, &destination, lamports)
4194 .expect("Failed to create parsed instruction");
4195
4196 let ui_instruction = UiInstruction::Parsed(UiParsedInstruction::Parsed(parsed));
4197
4198 let result = IxUtils::reconstruct_instruction_from_ui(&ui_instruction, &mut account_keys);
4199
4200 assert!(result.is_ok(), "System transfer should be dispatched via program_id");
4201 let compiled = result.unwrap();
4202 assert_eq!(compiled.program_id_index, 0);
4203 }
4204
4205 #[test]
4206 fn test_reconstruct_unsupported_program_creates_stub() {
4207 let unsupported_program = Pubkey::new_unique();
4208 let mut account_keys = vec![unsupported_program];
4209
4210 let parsed_instruction = solana_transaction_status_client_types::ParsedInstruction {
4211 program: "unsupported".to_string(),
4212 program_id: unsupported_program.to_string(),
4213 parsed: serde_json::json!({
4214 "type": "unknownInstruction",
4215 "info": {
4216 "someField": "someValue"
4217 }
4218 }),
4219 stack_height: None,
4220 };
4221
4222 let ui_instruction = UiInstruction::Parsed(UiParsedInstruction::Parsed(parsed_instruction));
4223
4224 let result = IxUtils::reconstruct_instruction_from_ui(&ui_instruction, &mut account_keys);
4225
4226 assert!(result.is_ok());
4227 let compiled = result.unwrap();
4228
4229 assert_eq!(compiled.program_id_index, 0);
4230 assert!(compiled.accounts.is_empty());
4231 assert!(compiled.data.is_empty());
4232 }
4233}