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