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