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