1#![allow(clippy::arithmetic_side_effects)]
2
3use std::collections::HashSet;
4
5use agave_reserved_account_keys::ReservedAccountKeys;
6use base64::Engine;
7use base64::prelude::BASE64_STANDARD;
8use solana_clock::Slot;
9use solana_clock::UnixTimestamp;
10use solana_hash::Hash;
11use solana_instruction::TRANSACTION_LEVEL_STACK_HEIGHT;
12use solana_message::AccountKeys;
13use solana_message::Message;
14use solana_message::VersionedMessage;
15use solana_message::compiled_instruction::CompiledInstruction;
16use solana_message::v0::LoadedAddresses;
17use solana_message::v0::LoadedMessage;
18use solana_message::v0::{self};
19use solana_pubkey::Pubkey;
20pub use solana_reward_info::RewardType;
21use solana_signature::Signature;
22use solana_transaction::Transaction;
23use solana_transaction::versioned::TransactionVersion;
24use solana_transaction::versioned::VersionedTransaction;
25use solana_transaction_error::TransactionError;
26pub use solana_transaction_status_client_types_wasm::ConfirmedTransactionStatusWithSignature;
27pub use solana_transaction_status_client_types_wasm::EncodeError;
28pub use solana_transaction_status_client_types_wasm::EncodedConfirmedBlock;
29pub use solana_transaction_status_client_types_wasm::EncodedConfirmedTransactionWithStatusMeta;
30pub use solana_transaction_status_client_types_wasm::EncodedTransaction;
31pub use solana_transaction_status_client_types_wasm::EncodedTransactionWithStatusMeta;
32pub use solana_transaction_status_client_types_wasm::InnerInstruction;
33pub use solana_transaction_status_client_types_wasm::InnerInstructions;
34pub use solana_transaction_status_client_types_wasm::Reward;
35pub use solana_transaction_status_client_types_wasm::Rewards;
36pub use solana_transaction_status_client_types_wasm::TransactionBinaryEncoding;
37pub use solana_transaction_status_client_types_wasm::TransactionConfirmationStatus;
38pub use solana_transaction_status_client_types_wasm::TransactionDetails;
39pub use solana_transaction_status_client_types_wasm::TransactionStatus;
40pub use solana_transaction_status_client_types_wasm::TransactionStatusMeta;
41pub use solana_transaction_status_client_types_wasm::TransactionTokenBalance;
42pub use solana_transaction_status_client_types_wasm::UiAccountsList;
43pub use solana_transaction_status_client_types_wasm::UiAddressTableLookup;
44pub use solana_transaction_status_client_types_wasm::UiCompiledInstruction;
45pub use solana_transaction_status_client_types_wasm::UiConfirmedBlock;
46pub use solana_transaction_status_client_types_wasm::UiInnerInstructions;
47pub use solana_transaction_status_client_types_wasm::UiInstruction;
48pub use solana_transaction_status_client_types_wasm::UiLoadedAddresses;
49pub use solana_transaction_status_client_types_wasm::UiMessage;
50pub use solana_transaction_status_client_types_wasm::UiParsedInstruction;
51pub use solana_transaction_status_client_types_wasm::UiParsedMessage;
52pub use solana_transaction_status_client_types_wasm::UiPartiallyDecodedInstruction;
53pub use solana_transaction_status_client_types_wasm::UiRawMessage;
54pub use solana_transaction_status_client_types_wasm::UiReturnDataEncoding;
55pub use solana_transaction_status_client_types_wasm::UiTransaction;
56pub use solana_transaction_status_client_types_wasm::UiTransactionEncoding;
57pub use solana_transaction_status_client_types_wasm::UiTransactionReturnData;
58pub use solana_transaction_status_client_types_wasm::UiTransactionStatusMeta;
59pub use solana_transaction_status_client_types_wasm::option_serializer;
60use thiserror::Error;
61
62pub use crate::extract_memos::extract_and_fmt_memos;
63use crate::parse_accounts::parse_legacy_message_accounts;
64use crate::parse_accounts::parse_v0_message_accounts;
65use crate::parse_instruction::parse;
66
67#[macro_use]
68extern crate serde_derive;
69
70pub mod extract_memos;
71pub mod parse_accounts;
72pub mod parse_address_lookup_table;
73pub mod parse_associated_token;
74pub mod parse_bpf_loader;
75pub mod parse_instruction;
76pub mod parse_stake;
77pub mod parse_system;
78pub mod parse_token;
79pub mod parse_vote;
80pub mod token_balances;
81
82pub struct BlockEncodingOptions {
83 pub transaction_details: TransactionDetails,
84 pub show_rewards: bool,
85 pub max_supported_transaction_version: Option<u8>,
86}
87
88pub trait Encodable {
90 type Encoded;
91 fn encode(&self, encoding: UiTransactionEncoding) -> Self::Encoded;
92}
93
94pub trait EncodableWithMeta {
96 type Encoded;
97 fn encode_with_meta(
98 &self,
99 encoding: UiTransactionEncoding,
100 meta: &TransactionStatusMeta,
101 ) -> Self::Encoded;
102 fn json_encode(&self) -> Self::Encoded;
103}
104
105trait JsonAccounts {
106 type Encoded;
107 fn build_json_accounts(&self) -> Self::Encoded;
108}
109
110fn make_ui_partially_decoded_instruction(
111 instruction: &CompiledInstruction,
112 account_keys: &AccountKeys,
113 stack_height: Option<u32>,
114) -> UiPartiallyDecodedInstruction {
115 UiPartiallyDecodedInstruction {
116 program_id: account_keys[instruction.program_id_index as usize],
117 accounts: instruction
118 .accounts
119 .iter()
120 .map(|&i| account_keys[i as usize])
121 .collect(),
122 data: bs58::encode(instruction.data.clone()).into_string(),
123 stack_height,
124 }
125}
126
127pub fn parse_ui_instruction(
128 instruction: &CompiledInstruction,
129 account_keys: &AccountKeys,
130 stack_height: Option<u32>,
131) -> UiInstruction {
132 let program_id = &account_keys[instruction.program_id_index as usize];
133 if let Ok(parsed_instruction) = parse(program_id, instruction, account_keys, stack_height) {
134 UiInstruction::Parsed(UiParsedInstruction::Parsed(parsed_instruction))
135 } else {
136 UiInstruction::Parsed(UiParsedInstruction::PartiallyDecoded(
137 make_ui_partially_decoded_instruction(instruction, account_keys, stack_height),
138 ))
139 }
140}
141
142pub fn map_inner_instructions(
145 inner_instructions: solana_message::inner_instruction::InnerInstructionsList,
146) -> impl Iterator<Item = InnerInstructions> {
147 inner_instructions
148 .into_iter()
149 .enumerate()
150 .map(|(index, instructions)| {
151 InnerInstructions {
152 index: index as u8,
153 instructions: instructions
154 .into_iter()
155 .map(|info| {
156 InnerInstruction {
157 stack_height: Some(u32::from(info.stack_height)),
158 instruction: info.instruction,
159 }
160 })
161 .collect(),
162 }
163 })
164 .filter(|i| !i.instructions.is_empty())
165}
166
167pub fn parse_ui_inner_instructions(
168 inner_instructions: InnerInstructions,
169 account_keys: &AccountKeys,
170) -> UiInnerInstructions {
171 UiInnerInstructions {
172 index: inner_instructions.index,
173 instructions: inner_instructions
174 .instructions
175 .iter()
176 .map(
177 |InnerInstruction {
178 instruction: ix,
179 stack_height,
180 }| { parse_ui_instruction(ix, account_keys, *stack_height) },
181 )
182 .collect(),
183 }
184}
185
186fn build_simple_ui_transaction_status_meta(
187 meta: TransactionStatusMeta,
188 show_rewards: bool,
189) -> UiTransactionStatusMeta {
190 UiTransactionStatusMeta {
191 err: meta.status.clone().err(),
192 status: meta.status,
193 fee: meta.fee,
194 pre_balances: meta.pre_balances,
195 post_balances: meta.post_balances,
196 inner_instructions: None,
197 log_messages: None,
198 pre_token_balances: meta
199 .pre_token_balances
200 .map(|balance| balance.into_iter().collect()),
201 post_token_balances: meta
202 .post_token_balances
203 .map(|balance| balance.into_iter().collect()),
204 rewards: if show_rewards { meta.rewards } else { None },
205 loaded_addresses: None,
206 return_data: None,
207 compute_units_consumed: None,
208 cost_units: None,
209 }
210}
211
212fn parse_ui_transaction_status_meta(
213 meta: TransactionStatusMeta,
214 static_keys: &[Pubkey],
215 show_rewards: bool,
216) -> UiTransactionStatusMeta {
217 let account_keys = AccountKeys::new(static_keys, Some(&meta.loaded_addresses));
218 UiTransactionStatusMeta {
219 err: meta.status.clone().err(),
220 status: meta.status,
221 fee: meta.fee,
222 pre_balances: meta.pre_balances,
223 post_balances: meta.post_balances,
224 inner_instructions: meta.inner_instructions.map(|ixs| {
225 ixs.into_iter()
226 .map(|ix| parse_ui_inner_instructions(ix, &account_keys))
227 .collect()
228 }),
229 log_messages: meta.log_messages,
230 pre_token_balances: meta
231 .pre_token_balances
232 .map(|balance| balance.into_iter().collect()),
233 post_token_balances: meta
234 .post_token_balances
235 .map(|balance| balance.into_iter().collect()),
236 rewards: if show_rewards { meta.rewards } else { None },
237 loaded_addresses: None,
238 return_data: meta.return_data.map(|return_data| return_data.into()),
239 compute_units_consumed: meta.compute_units_consumed,
240 cost_units: meta.cost_units,
241 }
242}
243
244#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
245pub struct RewardsAndNumPartitions {
246 pub rewards: Rewards,
247 pub num_partitions: Option<u64>,
248}
249
250#[derive(Debug, Error)]
251pub enum ConvertBlockError {
252 #[error("transactions missing after converted, before: {0}, after: {1}")]
253 TransactionsMissing(usize, usize),
254}
255
256#[derive(Clone, Debug, PartialEq)]
257pub struct ConfirmedBlock {
258 pub previous_blockhash: Hash,
259 pub blockhash: Hash,
260 pub parent_slot: Slot,
261 pub transactions: Vec<TransactionWithStatusMeta>,
262 pub rewards: Rewards,
263 pub num_partitions: Option<u64>,
264 pub block_time: Option<UnixTimestamp>,
265 pub block_height: Option<u64>,
266}
267
268#[derive(Clone, Debug, PartialEq)]
271pub struct VersionedConfirmedBlock {
272 pub previous_blockhash: Hash,
273 pub blockhash: Hash,
274 pub parent_slot: Slot,
275 pub transactions: Vec<VersionedTransactionWithStatusMeta>,
276 pub rewards: Rewards,
277 pub num_partitions: Option<u64>,
278 pub block_time: Option<UnixTimestamp>,
279 pub block_height: Option<u64>,
280}
281
282impl From<VersionedConfirmedBlock> for ConfirmedBlock {
283 fn from(block: VersionedConfirmedBlock) -> Self {
284 Self {
285 previous_blockhash: block.previous_blockhash,
286 blockhash: block.blockhash,
287 parent_slot: block.parent_slot,
288 transactions: block
289 .transactions
290 .into_iter()
291 .map(TransactionWithStatusMeta::Complete)
292 .collect(),
293 rewards: block.rewards,
294 num_partitions: block.num_partitions,
295 block_time: block.block_time,
296 block_height: block.block_height,
297 }
298 }
299}
300
301impl TryFrom<ConfirmedBlock> for VersionedConfirmedBlock {
302 type Error = ConvertBlockError;
303
304 fn try_from(block: ConfirmedBlock) -> Result<Self, Self::Error> {
305 let expected_transaction_count = block.transactions.len();
306
307 let txs: Vec<_> = block
308 .transactions
309 .into_iter()
310 .filter_map(|tx| {
311 match tx {
312 TransactionWithStatusMeta::MissingMetadata(_) => None,
313 TransactionWithStatusMeta::Complete(tx) => Some(tx),
314 }
315 })
316 .collect();
317
318 if txs.len() != expected_transaction_count {
319 return Err(ConvertBlockError::TransactionsMissing(
320 expected_transaction_count,
321 txs.len(),
322 ));
323 }
324
325 Ok(Self {
326 previous_blockhash: block.previous_blockhash,
327 blockhash: block.blockhash,
328 parent_slot: block.parent_slot,
329 transactions: txs,
330 rewards: block.rewards,
331 num_partitions: block.num_partitions,
332 block_time: block.block_time,
333 block_height: block.block_height,
334 })
335 }
336}
337
338impl ConfirmedBlock {
339 pub fn encode_with_options(
340 self,
341 encoding: UiTransactionEncoding,
342 options: BlockEncodingOptions,
343 ) -> Result<UiConfirmedBlock, EncodeError> {
344 let (transactions, signatures) = match options.transaction_details {
345 TransactionDetails::Full => {
346 (
347 Some(
348 self.transactions
349 .into_iter()
350 .map(|tx_with_meta| {
351 tx_with_meta.encode(
352 encoding,
353 options.max_supported_transaction_version,
354 options.show_rewards,
355 )
356 })
357 .collect::<Result<Vec<_>, _>>()?,
358 ),
359 None,
360 )
361 }
362 TransactionDetails::Signatures => {
363 (
364 None,
365 Some(
366 self.transactions
367 .into_iter()
368 .map(|tx_with_meta| *tx_with_meta.transaction_signature())
369 .collect(),
370 ),
371 )
372 }
373 TransactionDetails::None => (None, None),
374 TransactionDetails::Accounts => {
375 (
376 Some(
377 self.transactions
378 .into_iter()
379 .map(|tx_with_meta| {
380 tx_with_meta.build_json_accounts(
381 options.max_supported_transaction_version,
382 options.show_rewards,
383 )
384 })
385 .collect::<Result<Vec<_>, _>>()?,
386 ),
387 None,
388 )
389 }
390 };
391 Ok(UiConfirmedBlock {
392 previous_blockhash: self.previous_blockhash,
393 blockhash: self.blockhash,
394 parent_slot: self.parent_slot,
395 transactions,
396 signatures,
397 rewards: if options.show_rewards {
398 Some(self.rewards)
399 } else {
400 None
401 },
402 num_reward_partitions: self.num_partitions,
403 block_time: self.block_time,
404 block_height: self.block_height,
405 })
406 }
407}
408
409pub struct VersionedConfirmedBlockWithEntries {
413 pub block: VersionedConfirmedBlock,
414 pub entries: Vec<EntrySummary>,
415}
416
417pub struct EntrySummary {
420 pub num_hashes: u64,
421 pub hash: Hash,
422 pub num_transactions: u64,
423 pub starting_transaction_index: usize,
424}
425
426#[derive(Clone, Debug, PartialEq)]
427#[allow(clippy::large_enum_variant)]
428pub enum TransactionWithStatusMeta {
429 MissingMetadata(Transaction),
431 Complete(VersionedTransactionWithStatusMeta),
433}
434
435#[derive(Clone, Debug, PartialEq)]
436pub struct VersionedTransactionWithStatusMeta {
437 pub transaction: VersionedTransaction,
438 pub meta: TransactionStatusMeta,
439}
440
441impl TransactionWithStatusMeta {
442 pub fn get_status_meta(&self) -> Option<TransactionStatusMeta> {
443 match self {
444 Self::MissingMetadata(_) => None,
445 Self::Complete(tx_with_meta) => Some(tx_with_meta.meta.clone()),
446 }
447 }
448
449 pub fn get_transaction(&self) -> VersionedTransaction {
450 match self {
451 Self::MissingMetadata(transaction) => VersionedTransaction::from(transaction.clone()),
452 Self::Complete(tx_with_meta) => tx_with_meta.transaction.clone(),
453 }
454 }
455
456 pub fn transaction_signature(&self) -> &Signature {
457 match self {
458 Self::MissingMetadata(transaction) => &transaction.signatures[0],
459 Self::Complete(VersionedTransactionWithStatusMeta { transaction, .. }) => {
460 &transaction.signatures[0]
461 }
462 }
463 }
464
465 pub fn encode(
466 self,
467 encoding: UiTransactionEncoding,
468 max_supported_transaction_version: Option<u8>,
469 show_rewards: bool,
470 ) -> Result<EncodedTransactionWithStatusMeta, EncodeError> {
471 match self {
472 Self::MissingMetadata(ref transaction) => {
473 Ok(EncodedTransactionWithStatusMeta {
474 version: None,
475 transaction: transaction.encode(encoding),
476 meta: None,
477 })
478 }
479 Self::Complete(tx_with_meta) => {
480 tx_with_meta.encode(encoding, max_supported_transaction_version, show_rewards)
481 }
482 }
483 }
484
485 pub fn account_keys(&self) -> AccountKeys {
486 match self {
487 Self::MissingMetadata(tx) => AccountKeys::new(&tx.message.account_keys, None),
488 Self::Complete(tx_with_meta) => tx_with_meta.account_keys(),
489 }
490 }
491
492 fn build_json_accounts(
493 self,
494 max_supported_transaction_version: Option<u8>,
495 show_rewards: bool,
496 ) -> Result<EncodedTransactionWithStatusMeta, EncodeError> {
497 match self {
498 Self::MissingMetadata(ref transaction) => {
499 Ok(EncodedTransactionWithStatusMeta {
500 version: None,
501 transaction: transaction.build_json_accounts(),
502 meta: None,
503 })
504 }
505 Self::Complete(tx_with_meta) => {
506 tx_with_meta.build_json_accounts(max_supported_transaction_version, show_rewards)
507 }
508 }
509 }
510}
511
512impl VersionedTransactionWithStatusMeta {
513 fn validate_version(
514 &self,
515 max_supported_transaction_version: Option<u8>,
516 ) -> Result<Option<TransactionVersion>, EncodeError> {
517 match (
518 max_supported_transaction_version,
519 self.transaction.version(),
520 ) {
521 (None, TransactionVersion::LEGACY) => Ok(None),
523 (None, TransactionVersion::Number(version)) => {
524 Err(EncodeError::UnsupportedTransactionVersion(version))
525 }
526 (Some(_), TransactionVersion::LEGACY) => Ok(Some(TransactionVersion::LEGACY)),
527 (Some(max_version), TransactionVersion::Number(version)) => {
528 if version <= max_version {
529 Ok(Some(TransactionVersion::Number(version)))
530 } else {
531 Err(EncodeError::UnsupportedTransactionVersion(version))
532 }
533 }
534 }
535 }
536
537 pub fn encode(
538 self,
539 encoding: UiTransactionEncoding,
540 max_supported_transaction_version: Option<u8>,
541 show_rewards: bool,
542 ) -> Result<EncodedTransactionWithStatusMeta, EncodeError> {
543 let version = self.validate_version(max_supported_transaction_version)?;
544
545 Ok(EncodedTransactionWithStatusMeta {
546 transaction: self.transaction.encode_with_meta(encoding, &self.meta),
547 meta: Some(match encoding {
548 UiTransactionEncoding::JsonParsed => {
549 parse_ui_transaction_status_meta(
550 self.meta,
551 self.transaction.message.static_account_keys(),
552 show_rewards,
553 )
554 }
555 _ => {
556 let mut meta = UiTransactionStatusMeta::from(self.meta);
557 if !show_rewards {
558 meta.rewards = None;
559 }
560 meta
561 }
562 }),
563 version,
564 })
565 }
566
567 pub fn account_keys(&self) -> AccountKeys {
568 AccountKeys::new(
569 self.transaction.message.static_account_keys(),
570 Some(&self.meta.loaded_addresses),
571 )
572 }
573
574 fn build_json_accounts(
575 self,
576 max_supported_transaction_version: Option<u8>,
577 show_rewards: bool,
578 ) -> Result<EncodedTransactionWithStatusMeta, EncodeError> {
579 let version = self.validate_version(max_supported_transaction_version)?;
580 let reserved_account_keys = ReservedAccountKeys::new_all_activated();
581
582 let account_keys = match &self.transaction.message {
583 VersionedMessage::Legacy(message) => parse_legacy_message_accounts(message),
584 VersionedMessage::V0(message) => {
585 let loaded_message = LoadedMessage::new_borrowed(
586 message,
587 &self.meta.loaded_addresses,
588 &reserved_account_keys.active,
589 );
590 parse_v0_message_accounts(&loaded_message)
591 }
592 };
593
594 Ok(EncodedTransactionWithStatusMeta {
595 transaction: EncodedTransaction::Accounts(UiAccountsList {
596 signatures: self.transaction.signatures.clone(),
597 account_keys,
598 }),
599 meta: Some(build_simple_ui_transaction_status_meta(
600 self.meta,
601 show_rewards,
602 )),
603 version,
604 })
605 }
606}
607
608#[derive(Debug, Clone, PartialEq)]
609pub struct ConfirmedTransactionWithStatusMeta {
610 pub slot: Slot,
611 pub tx_with_meta: TransactionWithStatusMeta,
612 pub block_time: Option<UnixTimestamp>,
613}
614
615#[derive(Debug, Clone, PartialEq)]
616pub struct VersionedConfirmedTransactionWithStatusMeta {
617 pub slot: Slot,
618 pub tx_with_meta: VersionedTransactionWithStatusMeta,
619 pub block_time: Option<UnixTimestamp>,
620}
621
622impl ConfirmedTransactionWithStatusMeta {
623 pub fn encode(
624 self,
625 encoding: UiTransactionEncoding,
626 max_supported_transaction_version: Option<u8>,
627 ) -> Result<EncodedConfirmedTransactionWithStatusMeta, EncodeError> {
628 Ok(EncodedConfirmedTransactionWithStatusMeta {
629 slot: self.slot,
630 transaction: self.tx_with_meta.encode(
631 encoding,
632 max_supported_transaction_version,
633 true,
634 )?,
635 block_time: self.block_time,
636 })
637 }
638
639 pub fn get_transaction(&self) -> VersionedTransaction {
640 self.tx_with_meta.get_transaction()
641 }
642}
643
644impl EncodableWithMeta for VersionedTransaction {
645 type Encoded = EncodedTransaction;
646
647 fn encode_with_meta(
648 &self,
649 encoding: UiTransactionEncoding,
650 meta: &TransactionStatusMeta,
651 ) -> Self::Encoded {
652 match encoding {
653 UiTransactionEncoding::Binary => {
654 EncodedTransaction::LegacyBinary(
655 bs58::encode(bincode::serialize(self).unwrap()).into_string(),
656 )
657 }
658 UiTransactionEncoding::Base58 => {
659 EncodedTransaction::Binary(
660 bs58::encode(bincode::serialize(self).unwrap()).into_string(),
661 TransactionBinaryEncoding::Base58,
662 )
663 }
664 UiTransactionEncoding::Base64 => {
665 EncodedTransaction::Binary(
666 BASE64_STANDARD.encode(bincode::serialize(self).unwrap()),
667 TransactionBinaryEncoding::Base64,
668 )
669 }
670 UiTransactionEncoding::Json => self.json_encode(),
671 UiTransactionEncoding::JsonParsed => {
672 EncodedTransaction::Json(UiTransaction {
673 signatures: self.signatures.clone(),
674 message: match &self.message {
675 VersionedMessage::Legacy(message) => {
676 message.encode(UiTransactionEncoding::JsonParsed)
677 }
678 VersionedMessage::V0(message) => {
679 message.encode_with_meta(UiTransactionEncoding::JsonParsed, meta)
680 }
681 },
682 })
683 }
684 }
685 }
686
687 fn json_encode(&self) -> Self::Encoded {
688 EncodedTransaction::Json(UiTransaction {
689 signatures: self.signatures.clone(),
690 message: match &self.message {
691 VersionedMessage::Legacy(message) => message.encode(UiTransactionEncoding::Json),
692 VersionedMessage::V0(message) => message.json_encode(),
693 },
694 })
695 }
696}
697
698impl Encodable for VersionedTransaction {
699 type Encoded = EncodedTransaction;
700
701 fn encode(&self, encoding: UiTransactionEncoding) -> Self::Encoded {
702 match encoding {
703 UiTransactionEncoding::Binary => {
704 EncodedTransaction::LegacyBinary(
705 bs58::encode(bincode::serialize(self).unwrap()).into_string(),
706 )
707 }
708 UiTransactionEncoding::Base58 => {
709 EncodedTransaction::Binary(
710 bs58::encode(bincode::serialize(self).unwrap()).into_string(),
711 TransactionBinaryEncoding::Base58,
712 )
713 }
714 UiTransactionEncoding::Base64 => {
715 EncodedTransaction::Binary(
716 BASE64_STANDARD.encode(bincode::serialize(self).unwrap()),
717 TransactionBinaryEncoding::Base64,
718 )
719 }
720 UiTransactionEncoding::Json | UiTransactionEncoding::JsonParsed => {
721 EncodedTransaction::Json(UiTransaction {
722 signatures: self.signatures.clone(),
723 message: match &self.message {
724 VersionedMessage::Legacy(message) => {
725 message.encode(UiTransactionEncoding::JsonParsed)
726 }
727 VersionedMessage::V0(message) => {
728 message.encode(UiTransactionEncoding::JsonParsed)
729 }
730 },
731 })
732 }
733 }
734 }
735}
736
737impl Encodable for Transaction {
738 type Encoded = EncodedTransaction;
739
740 fn encode(&self, encoding: UiTransactionEncoding) -> Self::Encoded {
741 match encoding {
742 UiTransactionEncoding::Binary => {
743 EncodedTransaction::LegacyBinary(
744 bs58::encode(bincode::serialize(self).unwrap()).into_string(),
745 )
746 }
747 UiTransactionEncoding::Base58 => {
748 EncodedTransaction::Binary(
749 bs58::encode(bincode::serialize(self).unwrap()).into_string(),
750 TransactionBinaryEncoding::Base58,
751 )
752 }
753 UiTransactionEncoding::Base64 => {
754 EncodedTransaction::Binary(
755 BASE64_STANDARD.encode(bincode::serialize(self).unwrap()),
756 TransactionBinaryEncoding::Base64,
757 )
758 }
759 UiTransactionEncoding::Json | UiTransactionEncoding::JsonParsed => {
760 EncodedTransaction::Json(UiTransaction {
761 signatures: self.signatures.clone(),
762 message: self.message.encode(encoding),
763 })
764 }
765 }
766 }
767}
768
769impl JsonAccounts for Transaction {
770 type Encoded = EncodedTransaction;
771
772 fn build_json_accounts(&self) -> Self::Encoded {
773 EncodedTransaction::Accounts(UiAccountsList {
774 signatures: self.signatures.clone(),
775 account_keys: parse_legacy_message_accounts(&self.message),
776 })
777 }
778}
779
780impl Encodable for Message {
781 type Encoded = UiMessage;
782
783 fn encode(&self, encoding: UiTransactionEncoding) -> Self::Encoded {
784 if encoding == UiTransactionEncoding::JsonParsed {
785 let account_keys = AccountKeys::new(&self.account_keys, None);
786 UiMessage::Parsed(UiParsedMessage {
787 account_keys: parse_legacy_message_accounts(self),
788 recent_blockhash: self.recent_blockhash,
789 instructions: self
790 .instructions
791 .iter()
792 .map(|instruction| {
793 parse_ui_instruction(
794 instruction,
795 &account_keys,
796 Some(TRANSACTION_LEVEL_STACK_HEIGHT as u32),
797 )
798 })
799 .collect(),
800 address_table_lookups: None,
801 })
802 } else {
803 UiMessage::Raw(UiRawMessage {
804 header: self.header,
805 account_keys: self.account_keys.iter().map(ToString::to_string).collect(),
806 recent_blockhash: self.recent_blockhash.to_string(),
807 instructions: self
808 .instructions
809 .iter()
810 .map(|ix| {
811 UiCompiledInstruction::from(ix, Some(TRANSACTION_LEVEL_STACK_HEIGHT as u32))
812 })
813 .collect(),
814 address_table_lookups: None,
815 })
816 }
817 }
818}
819
820impl Encodable for v0::Message {
821 type Encoded = UiMessage;
822
823 fn encode(&self, encoding: UiTransactionEncoding) -> Self::Encoded {
824 if encoding == UiTransactionEncoding::JsonParsed {
825 let account_keys = AccountKeys::new(&self.account_keys, None);
826 let loaded_addresses = LoadedAddresses::default();
827 let loaded_message =
828 LoadedMessage::new_borrowed(self, &loaded_addresses, &HashSet::new());
829 UiMessage::Parsed(UiParsedMessage {
830 account_keys: parse_v0_message_accounts(&loaded_message),
831 recent_blockhash: self.recent_blockhash,
832 instructions: self
833 .instructions
834 .iter()
835 .map(|instruction| {
836 parse_ui_instruction(
837 instruction,
838 &account_keys,
839 Some(TRANSACTION_LEVEL_STACK_HEIGHT as u32),
840 )
841 })
842 .collect(),
843 address_table_lookups: None,
844 })
845 } else {
846 UiMessage::Raw(UiRawMessage {
847 header: self.header,
848 account_keys: self.account_keys.iter().map(ToString::to_string).collect(),
849 recent_blockhash: self.recent_blockhash.to_string(),
850 instructions: self
851 .instructions
852 .iter()
853 .map(|ix| {
854 UiCompiledInstruction::from(ix, Some(TRANSACTION_LEVEL_STACK_HEIGHT as u32))
855 })
856 .collect(),
857 address_table_lookups: None,
858 })
859 }
860 }
861}
862
863impl EncodableWithMeta for v0::Message {
864 type Encoded = UiMessage;
865
866 fn encode_with_meta(
867 &self,
868 encoding: UiTransactionEncoding,
869 meta: &TransactionStatusMeta,
870 ) -> Self::Encoded {
871 if encoding == UiTransactionEncoding::JsonParsed {
872 let reserved_account_keys = ReservedAccountKeys::new_all_activated();
873 let account_keys = AccountKeys::new(&self.account_keys, Some(&meta.loaded_addresses));
874 let loaded_message = LoadedMessage::new_borrowed(
875 self,
876 &meta.loaded_addresses,
877 &reserved_account_keys.active,
878 );
879 UiMessage::Parsed(UiParsedMessage {
880 account_keys: parse_v0_message_accounts(&loaded_message),
881 recent_blockhash: self.recent_blockhash,
882 instructions: self
883 .instructions
884 .iter()
885 .map(|instruction| {
886 parse_ui_instruction(
887 instruction,
888 &account_keys,
889 Some(TRANSACTION_LEVEL_STACK_HEIGHT as u32),
890 )
891 })
892 .collect(),
893 address_table_lookups: Some(
894 self.address_table_lookups.iter().map(Into::into).collect(),
895 ),
896 })
897 } else {
898 self.json_encode()
899 }
900 }
901
902 fn json_encode(&self) -> Self::Encoded {
903 UiMessage::Raw(UiRawMessage {
904 header: self.header,
905 account_keys: self.account_keys.iter().map(ToString::to_string).collect(),
906 recent_blockhash: self.recent_blockhash.to_string(),
907 instructions: self
908 .instructions
909 .iter()
910 .map(|ix| {
911 UiCompiledInstruction::from(ix, Some(TRANSACTION_LEVEL_STACK_HEIGHT as u32))
912 })
913 .collect(),
914 address_table_lookups: Some(
915 self.address_table_lookups.iter().map(Into::into).collect(),
916 ),
917 })
918 }
919}
920
921#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
925pub struct TransactionByAddrInfo {
926 pub signature: Signature, pub err: Option<TransactionError>, pub index: u32, pub memo: Option<String>, pub block_time: Option<UnixTimestamp>,
931}
932
933#[cfg(test)]
934mod test {
935 use super::*;
936
937 #[test]
938 fn test_ui_transaction_status_meta_ctors_serialization() {
939 let meta = TransactionStatusMeta {
940 status: Ok(()),
941 fee: 1234,
942 pre_balances: vec![1, 2, 3],
943 post_balances: vec![4, 5, 6],
944 inner_instructions: None,
945 log_messages: None,
946 pre_token_balances: None,
947 post_token_balances: None,
948 rewards: None,
949 loaded_addresses: LoadedAddresses {
950 writable: vec![],
951 readonly: vec![],
952 },
953 return_data: None,
954 compute_units_consumed: None,
955 cost_units: None,
956 };
957 let expected_json_output_value: serde_json::Value = serde_json::from_str(
958 "{\"err\":null,\"status\":{\"Ok\":null},\"fee\":1234,\"preBalances\":[1,2,3],\"\
959 postBalances\":[4,5,6],\"innerInstructions\":null,\"logMessages\":null,\"\
960 preTokenBalances\":null,\"postTokenBalances\":null,\"rewards\":null,\"\
961 loadedAddresses\":{\"readonly\": [],\"writable\": []}}",
962 )
963 .unwrap();
964 let ui_meta_from: UiTransactionStatusMeta = meta.clone().into();
965 assert_eq!(
966 serde_json::to_value(ui_meta_from).unwrap(),
967 expected_json_output_value
968 );
969
970 let expected_json_output_value: serde_json::Value = serde_json::from_str(
971 "{\"err\":null,\"status\":{\"Ok\":null},\"fee\":1234,\"preBalances\":[1,2,3],\"\
972 postBalances\":[4,5,6],\"innerInstructions\":null,\"logMessages\":null,\"\
973 preTokenBalances\":null,\"postTokenBalances\":null,\"rewards\":null}",
974 )
975 .unwrap();
976 let ui_meta_parse_with_rewards = parse_ui_transaction_status_meta(meta.clone(), &[], true);
977 assert_eq!(
978 serde_json::to_value(ui_meta_parse_with_rewards).unwrap(),
979 expected_json_output_value
980 );
981
982 let ui_meta_parse_no_rewards = parse_ui_transaction_status_meta(meta, &[], false);
983 assert_eq!(
984 serde_json::to_value(ui_meta_parse_no_rewards).unwrap(),
985 expected_json_output_value
986 );
987 }
988}