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