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