1#![cfg(feature = "agave-unstable-api")]
2#![allow(clippy::arithmetic_side_effects)]
3
4pub use {
5 crate::extract_memos::extract_and_fmt_memos,
6 solana_reward_info::RewardType,
7 solana_transaction_status_client_types::{
8 ConfirmedTransactionStatusWithSignature, EncodeError, EncodedConfirmedBlock,
9 EncodedConfirmedTransactionWithStatusMeta, EncodedTransaction,
10 EncodedTransactionWithStatusMeta, InnerInstruction, InnerInstructions, Reward, Rewards,
11 TransactionBinaryEncoding, TransactionConfirmationStatus, TransactionDetails,
12 TransactionStatus, TransactionStatusMeta, TransactionTokenBalance, UiAccountsList,
13 UiAddressTableLookup, UiCompiledInstruction, UiConfirmedBlock, UiInnerInstructions,
14 UiInstruction, UiLoadedAddresses, UiMessage, UiParsedInstruction, UiParsedMessage,
15 UiPartiallyDecodedInstruction, UiRawMessage, UiReturnDataEncoding, UiTransaction,
16 UiTransactionEncoding, UiTransactionReturnData, UiTransactionStatusMeta,
17 UiTransactionTokenBalance, option_serializer,
18 },
19};
20use {
21 crate::{
22 option_serializer::OptionSerializer,
23 parse_accounts::{
24 parse_legacy_message_accounts, parse_v0_message_accounts, parse_v1_message_accounts,
25 },
26 parse_instruction::parse,
27 },
28 agave_reserved_account_keys::ReservedAccountKeys,
29 base64::{Engine, prelude::BASE64_STANDARD},
30 serde::{Deserialize, Serialize},
31 solana_clock::{Slot, UnixTimestamp},
32 solana_hash::Hash,
33 solana_instruction::TRANSACTION_LEVEL_STACK_HEIGHT,
34 solana_message::{
35 AccountKeys, Message, VersionedMessage,
36 compiled_instruction::CompiledInstruction,
37 v0::{self, LoadedAddresses, LoadedMessage},
38 },
39 solana_pubkey::Pubkey,
40 solana_signature::Signature,
41 solana_transaction::{
42 Transaction,
43 versioned::{TransactionVersion, VersionedTransaction},
44 },
45 solana_transaction_error::TransactionError,
46 solana_transaction_status_client_types::UiTransactionConfig,
47 std::collections::HashSet,
48 thiserror::Error,
49};
50
51pub mod extract_memos;
52pub mod parse_accounts;
53pub mod parse_address_lookup_table;
54pub mod parse_associated_token;
55pub mod parse_bpf_loader;
56pub mod parse_instruction;
57pub mod parse_stake;
58pub mod parse_system;
59pub mod parse_token;
60pub mod parse_vote;
61pub mod token_balances;
62
63fn serialize_versioned_transaction(transaction: &VersionedTransaction) -> Vec<u8> {
64 wincode::serialize(transaction).expect("serialize versioned transaction")
65}
66
67pub struct BlockEncodingOptions {
68 pub transaction_details: TransactionDetails,
69 pub show_rewards: bool,
70 pub max_supported_transaction_version: Option<u8>,
71}
72
73pub trait Encodable {
75 type Encoded;
76 fn encode(&self, encoding: UiTransactionEncoding) -> Self::Encoded;
77}
78
79pub trait EncodableWithMeta {
81 type Encoded;
82 fn encode_with_meta(
83 &self,
84 encoding: UiTransactionEncoding,
85 meta: &TransactionStatusMeta,
86 ) -> Self::Encoded;
87 fn json_encode(&self) -> Self::Encoded;
88}
89
90trait JsonAccounts {
91 type Encoded;
92 fn build_json_accounts(&self) -> Self::Encoded;
93}
94
95fn make_ui_partially_decoded_instruction(
96 instruction: &CompiledInstruction,
97 account_keys: &AccountKeys,
98 stack_height: Option<u32>,
99) -> UiPartiallyDecodedInstruction {
100 UiPartiallyDecodedInstruction {
101 program_id: account_keys[instruction.program_id_index as usize].to_string(),
102 accounts: instruction
103 .accounts
104 .iter()
105 .map(|&i| account_keys[i as usize].to_string())
106 .collect(),
107 data: bs58::encode(instruction.data.clone()).into_string(),
108 stack_height,
109 }
110}
111
112pub fn parse_ui_instruction(
113 instruction: &CompiledInstruction,
114 account_keys: &AccountKeys,
115 stack_height: Option<u32>,
116) -> UiInstruction {
117 let program_id = &account_keys[instruction.program_id_index as usize];
118 if let Ok(parsed_instruction) = parse(program_id, instruction, account_keys, stack_height) {
119 UiInstruction::Parsed(UiParsedInstruction::Parsed(parsed_instruction))
120 } else {
121 UiInstruction::Parsed(UiParsedInstruction::PartiallyDecoded(
122 make_ui_partially_decoded_instruction(instruction, account_keys, stack_height),
123 ))
124 }
125}
126
127pub fn map_inner_instructions(
130 inner_instructions: solana_message::inner_instruction::InnerInstructionsList,
131) -> impl Iterator<Item = InnerInstructions> {
132 inner_instructions
133 .into_iter()
134 .enumerate()
135 .map(|(index, instructions)| InnerInstructions {
136 index: index as u8,
137 instructions: instructions
138 .into_iter()
139 .map(|info| InnerInstruction {
140 stack_height: Some(u32::from(info.stack_height)),
141 instruction: info.instruction,
142 })
143 .collect(),
144 })
145 .filter(|i| !i.instructions.is_empty())
146}
147
148pub fn parse_ui_inner_instructions(
149 inner_instructions: InnerInstructions,
150 account_keys: &AccountKeys,
151) -> UiInnerInstructions {
152 UiInnerInstructions {
153 index: inner_instructions.index,
154 instructions: inner_instructions
155 .instructions
156 .iter()
157 .map(
158 |InnerInstruction {
159 instruction: ix,
160 stack_height,
161 }| { parse_ui_instruction(ix, account_keys, *stack_height) },
162 )
163 .collect(),
164 }
165}
166
167fn build_simple_ui_transaction_status_meta(
168 meta: TransactionStatusMeta,
169 show_rewards: bool,
170) -> UiTransactionStatusMeta {
171 UiTransactionStatusMeta {
172 err: meta.status.clone().map_err(Into::into).err(),
173 status: meta.status.map_err(Into::into),
174 fee: meta.fee,
175 pre_balances: meta.pre_balances,
176 post_balances: meta.post_balances,
177 inner_instructions: OptionSerializer::Skip,
178 log_messages: OptionSerializer::Skip,
179 pre_token_balances: meta
180 .pre_token_balances
181 .map(|balance| balance.into_iter().map(Into::into).collect())
182 .into(),
183 post_token_balances: meta
184 .post_token_balances
185 .map(|balance| balance.into_iter().map(Into::into).collect())
186 .into(),
187 rewards: if show_rewards {
188 meta.rewards.into()
189 } else {
190 OptionSerializer::Skip
191 },
192 loaded_addresses: OptionSerializer::Skip,
193 return_data: OptionSerializer::Skip,
194 compute_units_consumed: OptionSerializer::Skip,
195 cost_units: OptionSerializer::Skip,
196 }
197}
198
199fn parse_ui_transaction_status_meta(
200 meta: TransactionStatusMeta,
201 static_keys: &[Pubkey],
202 show_rewards: bool,
203) -> UiTransactionStatusMeta {
204 let account_keys = AccountKeys::new(static_keys, Some(&meta.loaded_addresses));
205 UiTransactionStatusMeta {
206 err: meta.status.clone().map_err(Into::into).err(),
207 status: meta.status.map_err(Into::into),
208 fee: meta.fee,
209 pre_balances: meta.pre_balances,
210 post_balances: meta.post_balances,
211 inner_instructions: meta
212 .inner_instructions
213 .map(|ixs| {
214 ixs.into_iter()
215 .map(|ix| parse_ui_inner_instructions(ix, &account_keys))
216 .collect()
217 })
218 .into(),
219 log_messages: meta.log_messages.into(),
220 pre_token_balances: meta
221 .pre_token_balances
222 .map(|balance| balance.into_iter().map(Into::into).collect())
223 .into(),
224 post_token_balances: meta
225 .post_token_balances
226 .map(|balance| balance.into_iter().map(Into::into).collect())
227 .into(),
228 rewards: if show_rewards { meta.rewards } else { None }.into(),
229 loaded_addresses: OptionSerializer::Skip,
230 return_data: OptionSerializer::or_skip(
231 meta.return_data.map(|return_data| return_data.into()),
232 ),
233 compute_units_consumed: OptionSerializer::or_skip(meta.compute_units_consumed),
234 cost_units: OptionSerializer::or_skip(meta.cost_units),
235 }
236}
237
238#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
239pub struct RewardsAndNumPartitions {
240 pub rewards: Rewards,
241 pub num_partitions: Option<u64>,
242}
243
244#[derive(Debug, Error)]
245pub enum ConvertBlockError {
246 #[error("transactions missing after converted, before: {0}, after: {1}")]
247 TransactionsMissing(usize, usize),
248}
249
250#[derive(Clone, Debug, PartialEq)]
251pub struct ConfirmedBlock {
252 pub previous_blockhash: String,
253 pub blockhash: String,
254 pub parent_slot: Slot,
255 pub transactions: Vec<TransactionWithStatusMeta>,
256 pub rewards: Rewards,
257 pub num_partitions: Option<u64>,
258 pub block_time: Option<UnixTimestamp>,
259 pub block_height: Option<u64>,
260}
261
262#[derive(Clone, Debug, PartialEq)]
265pub struct VersionedConfirmedBlock {
266 pub previous_blockhash: String,
267 pub blockhash: String,
268 pub parent_slot: Slot,
269 pub transactions: Vec<VersionedTransactionWithStatusMeta>,
270 pub rewards: Rewards,
271 pub num_partitions: Option<u64>,
272 pub block_time: Option<UnixTimestamp>,
273 pub block_height: Option<u64>,
274}
275
276impl From<VersionedConfirmedBlock> for ConfirmedBlock {
277 fn from(block: VersionedConfirmedBlock) -> Self {
278 Self {
279 previous_blockhash: block.previous_blockhash,
280 blockhash: block.blockhash,
281 parent_slot: block.parent_slot,
282 transactions: block
283 .transactions
284 .into_iter()
285 .map(TransactionWithStatusMeta::Complete)
286 .collect(),
287 rewards: block.rewards,
288 num_partitions: block.num_partitions,
289 block_time: block.block_time,
290 block_height: block.block_height,
291 }
292 }
293}
294
295impl TryFrom<ConfirmedBlock> for VersionedConfirmedBlock {
296 type Error = ConvertBlockError;
297
298 fn try_from(block: ConfirmedBlock) -> Result<Self, Self::Error> {
299 let expected_transaction_count = block.transactions.len();
300
301 let txs: Vec<_> = block
302 .transactions
303 .into_iter()
304 .filter_map(|tx| match tx {
305 TransactionWithStatusMeta::MissingMetadata(_) => None,
306 TransactionWithStatusMeta::Complete(tx) => Some(tx),
307 })
308 .collect();
309
310 if txs.len() != expected_transaction_count {
311 return Err(ConvertBlockError::TransactionsMissing(
312 expected_transaction_count,
313 txs.len(),
314 ));
315 }
316
317 Ok(Self {
318 previous_blockhash: block.previous_blockhash,
319 blockhash: block.blockhash,
320 parent_slot: block.parent_slot,
321 transactions: txs,
322 rewards: block.rewards,
323 num_partitions: block.num_partitions,
324 block_time: block.block_time,
325 block_height: block.block_height,
326 })
327 }
328}
329
330impl ConfirmedBlock {
331 pub fn encode_with_options(
332 self,
333 encoding: UiTransactionEncoding,
334 options: BlockEncodingOptions,
335 ) -> Result<UiConfirmedBlock, EncodeError> {
336 let (transactions, signatures) = match options.transaction_details {
337 TransactionDetails::Full => (
338 Some(
339 self.transactions
340 .into_iter()
341 .map(|tx_with_meta| {
342 tx_with_meta.encode(
343 encoding,
344 options.max_supported_transaction_version,
345 options.show_rewards,
346 )
347 })
348 .collect::<Result<Vec<_>, _>>()?,
349 ),
350 None,
351 ),
352 TransactionDetails::Signatures => (
353 None,
354 Some(
355 self.transactions
356 .into_iter()
357 .map(|tx_with_meta| tx_with_meta.transaction_signature().to_string())
358 .collect(),
359 ),
360 ),
361 TransactionDetails::None => (None, None),
362 TransactionDetails::Accounts => (
363 Some(
364 self.transactions
365 .into_iter()
366 .map(|tx_with_meta| {
367 tx_with_meta.build_json_accounts(
368 options.max_supported_transaction_version,
369 options.show_rewards,
370 )
371 })
372 .collect::<Result<Vec<_>, _>>()?,
373 ),
374 None,
375 ),
376 };
377 Ok(UiConfirmedBlock {
378 previous_blockhash: self.previous_blockhash,
379 blockhash: self.blockhash,
380 parent_slot: self.parent_slot,
381 transactions,
382 signatures,
383 rewards: if options.show_rewards {
384 Some(self.rewards)
385 } else {
386 None
387 },
388 num_reward_partitions: self.num_partitions,
389 block_time: self.block_time,
390 block_height: self.block_height,
391 })
392 }
393}
394
395pub struct VersionedConfirmedBlockWithEntries {
399 pub block: VersionedConfirmedBlock,
400 pub entries: Vec<EntrySummary>,
401}
402
403#[derive(Clone, Copy)]
406pub 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 VersionedMessage::V1(message) => parse_v1_message_accounts(message),
574 };
575
576 Ok(EncodedTransactionWithStatusMeta {
577 transaction: EncodedTransaction::Accounts(UiAccountsList {
578 signatures: self
579 .transaction
580 .signatures
581 .iter()
582 .map(ToString::to_string)
583 .collect(),
584 account_keys,
585 }),
586 meta: Some(build_simple_ui_transaction_status_meta(
587 self.meta,
588 show_rewards,
589 )),
590 version,
591 })
592 }
593}
594
595#[derive(Debug, Clone, PartialEq)]
596pub struct ConfirmedTransactionWithStatusMeta {
597 pub slot: Slot,
598 pub tx_with_meta: TransactionWithStatusMeta,
599 pub block_time: Option<UnixTimestamp>,
600 pub index: u32,
601}
602
603#[derive(Debug, Clone, PartialEq)]
604pub struct VersionedConfirmedTransactionWithStatusMeta {
605 pub slot: Slot,
606 pub tx_with_meta: VersionedTransactionWithStatusMeta,
607 pub block_time: Option<UnixTimestamp>,
608}
609
610impl ConfirmedTransactionWithStatusMeta {
611 pub fn encode(
612 self,
613 encoding: UiTransactionEncoding,
614 max_supported_transaction_version: Option<u8>,
615 ) -> Result<EncodedConfirmedTransactionWithStatusMeta, EncodeError> {
616 Ok(EncodedConfirmedTransactionWithStatusMeta {
617 slot: self.slot,
618 transaction: self.tx_with_meta.encode(
619 encoding,
620 max_supported_transaction_version,
621 true,
622 )?,
623 block_time: self.block_time,
624 transaction_index: Some(self.index),
625 })
626 }
627
628 pub fn get_transaction(&self) -> VersionedTransaction {
629 self.tx_with_meta.get_transaction()
630 }
631}
632
633impl EncodableWithMeta for VersionedTransaction {
634 type Encoded = EncodedTransaction;
635 fn encode_with_meta(
636 &self,
637 encoding: UiTransactionEncoding,
638 meta: &TransactionStatusMeta,
639 ) -> Self::Encoded {
640 match encoding {
641 UiTransactionEncoding::Binary => EncodedTransaction::LegacyBinary(
642 bs58::encode(serialize_versioned_transaction(self)).into_string(),
643 ),
644 UiTransactionEncoding::Base58 => EncodedTransaction::Binary(
645 bs58::encode(serialize_versioned_transaction(self)).into_string(),
646 TransactionBinaryEncoding::Base58,
647 ),
648 UiTransactionEncoding::Base64 => EncodedTransaction::Binary(
649 BASE64_STANDARD.encode(serialize_versioned_transaction(self)),
650 TransactionBinaryEncoding::Base64,
651 ),
652 UiTransactionEncoding::Json => self.json_encode(),
653 UiTransactionEncoding::JsonParsed => EncodedTransaction::Json(UiTransaction {
654 signatures: self.signatures.iter().map(ToString::to_string).collect(),
655 message: match &self.message {
656 VersionedMessage::Legacy(message) => {
657 message.encode(UiTransactionEncoding::JsonParsed)
658 }
659 VersionedMessage::V0(message) => {
660 message.encode_with_meta(UiTransactionEncoding::JsonParsed, meta)
661 }
662 VersionedMessage::V1(message) => {
663 message.encode(UiTransactionEncoding::JsonParsed)
664 }
665 },
666 }),
667 }
668 }
669 fn json_encode(&self) -> Self::Encoded {
670 EncodedTransaction::Json(UiTransaction {
671 signatures: self.signatures.iter().map(ToString::to_string).collect(),
672 message: match &self.message {
673 VersionedMessage::Legacy(message) => message.encode(UiTransactionEncoding::Json),
674 VersionedMessage::V0(message) => message.json_encode(),
675 VersionedMessage::V1(message) => message.encode(UiTransactionEncoding::Json),
676 },
677 })
678 }
679}
680
681impl Encodable for VersionedTransaction {
682 type Encoded = EncodedTransaction;
683 fn encode(&self, encoding: UiTransactionEncoding) -> Self::Encoded {
684 match encoding {
685 UiTransactionEncoding::Binary => EncodedTransaction::LegacyBinary(
686 bs58::encode(serialize_versioned_transaction(self)).into_string(),
687 ),
688 UiTransactionEncoding::Base58 => EncodedTransaction::Binary(
689 bs58::encode(serialize_versioned_transaction(self)).into_string(),
690 TransactionBinaryEncoding::Base58,
691 ),
692 UiTransactionEncoding::Base64 => EncodedTransaction::Binary(
693 BASE64_STANDARD.encode(serialize_versioned_transaction(self)),
694 TransactionBinaryEncoding::Base64,
695 ),
696 UiTransactionEncoding::Json | UiTransactionEncoding::JsonParsed => {
697 EncodedTransaction::Json(UiTransaction {
698 signatures: self.signatures.iter().map(ToString::to_string).collect(),
699 message: match &self.message {
700 VersionedMessage::Legacy(message) => {
701 message.encode(UiTransactionEncoding::JsonParsed)
702 }
703 VersionedMessage::V0(message) => {
704 message.encode(UiTransactionEncoding::JsonParsed)
705 }
706 VersionedMessage::V1(message) => {
707 message.encode(UiTransactionEncoding::JsonParsed)
708 }
709 },
710 })
711 }
712 }
713 }
714}
715
716impl Encodable for Transaction {
717 type Encoded = EncodedTransaction;
718 fn encode(&self, encoding: UiTransactionEncoding) -> Self::Encoded {
719 match encoding {
720 UiTransactionEncoding::Binary => EncodedTransaction::LegacyBinary(
721 bs58::encode(bincode::serialize(self).unwrap()).into_string(),
722 ),
723 UiTransactionEncoding::Base58 => EncodedTransaction::Binary(
724 bs58::encode(bincode::serialize(self).unwrap()).into_string(),
725 TransactionBinaryEncoding::Base58,
726 ),
727 UiTransactionEncoding::Base64 => EncodedTransaction::Binary(
728 BASE64_STANDARD.encode(bincode::serialize(self).unwrap()),
729 TransactionBinaryEncoding::Base64,
730 ),
731 UiTransactionEncoding::Json | UiTransactionEncoding::JsonParsed => {
732 EncodedTransaction::Json(UiTransaction {
733 signatures: self.signatures.iter().map(ToString::to_string).collect(),
734 message: self.message.encode(encoding),
735 })
736 }
737 }
738 }
739}
740
741impl JsonAccounts for Transaction {
742 type Encoded = EncodedTransaction;
743 fn build_json_accounts(&self) -> Self::Encoded {
744 EncodedTransaction::Accounts(UiAccountsList {
745 signatures: self.signatures.iter().map(ToString::to_string).collect(),
746 account_keys: parse_legacy_message_accounts(&self.message),
747 })
748 }
749}
750
751impl Encodable for Message {
752 type Encoded = UiMessage;
753 fn encode(&self, encoding: UiTransactionEncoding) -> Self::Encoded {
754 if encoding == UiTransactionEncoding::JsonParsed {
755 let account_keys = AccountKeys::new(&self.account_keys, None);
756 UiMessage::Parsed(UiParsedMessage {
757 account_keys: parse_legacy_message_accounts(self),
758 recent_blockhash: self.recent_blockhash.to_string(),
759 instructions: self
760 .instructions
761 .iter()
762 .map(|instruction| {
763 parse_ui_instruction(
764 instruction,
765 &account_keys,
766 Some(TRANSACTION_LEVEL_STACK_HEIGHT as u32),
767 )
768 })
769 .collect(),
770 address_table_lookups: None,
771 transaction_config: None,
772 })
773 } else {
774 UiMessage::Raw(UiRawMessage {
775 header: self.header,
776 account_keys: self.account_keys.iter().map(ToString::to_string).collect(),
777 recent_blockhash: self.recent_blockhash.to_string(),
778 instructions: self
779 .instructions
780 .iter()
781 .map(|ix| {
782 UiCompiledInstruction::from(ix, Some(TRANSACTION_LEVEL_STACK_HEIGHT as u32))
783 })
784 .collect(),
785 address_table_lookups: None,
786 transaction_config: None,
787 })
788 }
789 }
790}
791
792impl Encodable for v0::Message {
793 type Encoded = UiMessage;
794 fn encode(&self, encoding: UiTransactionEncoding) -> Self::Encoded {
795 if encoding == UiTransactionEncoding::JsonParsed {
796 let account_keys = AccountKeys::new(&self.account_keys, None);
797 let loaded_addresses = LoadedAddresses::default();
798 let loaded_message =
799 LoadedMessage::new_borrowed(self, &loaded_addresses, &HashSet::new());
800 UiMessage::Parsed(UiParsedMessage {
801 account_keys: parse_v0_message_accounts(&loaded_message),
802 recent_blockhash: self.recent_blockhash.to_string(),
803 instructions: self
804 .instructions
805 .iter()
806 .map(|instruction| {
807 parse_ui_instruction(
808 instruction,
809 &account_keys,
810 Some(TRANSACTION_LEVEL_STACK_HEIGHT as u32),
811 )
812 })
813 .collect(),
814 address_table_lookups: None,
815 transaction_config: None,
816 })
817 } else {
818 UiMessage::Raw(UiRawMessage {
819 header: self.header,
820 account_keys: self.account_keys.iter().map(ToString::to_string).collect(),
821 recent_blockhash: self.recent_blockhash.to_string(),
822 instructions: self
823 .instructions
824 .iter()
825 .map(|ix| {
826 UiCompiledInstruction::from(ix, Some(TRANSACTION_LEVEL_STACK_HEIGHT as u32))
827 })
828 .collect(),
829 address_table_lookups: None,
830 transaction_config: None,
831 })
832 }
833 }
834}
835
836impl EncodableWithMeta for v0::Message {
837 type Encoded = UiMessage;
838 fn encode_with_meta(
839 &self,
840 encoding: UiTransactionEncoding,
841 meta: &TransactionStatusMeta,
842 ) -> Self::Encoded {
843 if encoding == UiTransactionEncoding::JsonParsed {
844 let reserved_account_keys = ReservedAccountKeys::new_all_activated();
845 let account_keys = AccountKeys::new(&self.account_keys, Some(&meta.loaded_addresses));
846 let loaded_message = LoadedMessage::new_borrowed(
847 self,
848 &meta.loaded_addresses,
849 &reserved_account_keys.active,
850 );
851 UiMessage::Parsed(UiParsedMessage {
852 account_keys: parse_v0_message_accounts(&loaded_message),
853 recent_blockhash: self.recent_blockhash.to_string(),
854 instructions: self
855 .instructions
856 .iter()
857 .map(|instruction| {
858 parse_ui_instruction(
859 instruction,
860 &account_keys,
861 Some(TRANSACTION_LEVEL_STACK_HEIGHT as u32),
862 )
863 })
864 .collect(),
865 address_table_lookups: Some(
866 self.address_table_lookups.iter().map(Into::into).collect(),
867 ),
868 transaction_config: None,
869 })
870 } else {
871 self.json_encode()
872 }
873 }
874 fn json_encode(&self) -> Self::Encoded {
875 UiMessage::Raw(UiRawMessage {
876 header: self.header,
877 account_keys: self.account_keys.iter().map(ToString::to_string).collect(),
878 recent_blockhash: self.recent_blockhash.to_string(),
879 instructions: self
880 .instructions
881 .iter()
882 .map(|ix| {
883 UiCompiledInstruction::from(ix, Some(TRANSACTION_LEVEL_STACK_HEIGHT as u32))
884 })
885 .collect(),
886 address_table_lookups: Some(
887 self.address_table_lookups.iter().map(Into::into).collect(),
888 ),
889 transaction_config: None,
890 })
891 }
892}
893
894impl Encodable for solana_message::v1::Message {
895 type Encoded = UiMessage;
896 fn encode(&self, encoding: UiTransactionEncoding) -> Self::Encoded {
897 if encoding == UiTransactionEncoding::JsonParsed {
898 let account_keys = AccountKeys::new(&self.account_keys, None);
899 UiMessage::Parsed(UiParsedMessage {
900 account_keys: parse_v1_message_accounts(self),
901 recent_blockhash: self.lifetime_specifier.to_string(),
902 instructions: self
903 .instructions
904 .iter()
905 .map(|instruction| {
906 parse_ui_instruction(
907 instruction,
908 &account_keys,
909 Some(TRANSACTION_LEVEL_STACK_HEIGHT as u32),
910 )
911 })
912 .collect(),
913 address_table_lookups: None,
914 transaction_config: Some(UiTransactionConfig::from(&self.config)),
915 })
916 } else {
917 UiMessage::Raw(UiRawMessage {
918 header: self.header,
919 account_keys: self.account_keys.iter().map(ToString::to_string).collect(),
920 recent_blockhash: self.lifetime_specifier.to_string(),
921 instructions: self
922 .instructions
923 .iter()
924 .map(|ix| {
925 UiCompiledInstruction::from(ix, Some(TRANSACTION_LEVEL_STACK_HEIGHT as u32))
926 })
927 .collect(),
928 address_table_lookups: None,
929 transaction_config: Some(UiTransactionConfig::from(&self.config)),
930 })
931 }
932 }
933}
934
935#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
938pub struct TransactionByAddrInfo {
939 pub signature: Signature, pub err: Option<TransactionError>, pub index: u32, pub memo: Option<String>, pub block_time: Option<UnixTimestamp>,
944}
945
946#[cfg(test)]
947mod test {
948 use super::*;
949
950 #[test]
951 fn test_ui_transaction_status_meta_ctors_serialization() {
952 let meta = TransactionStatusMeta {
953 status: Ok(()),
954 fee: 1234,
955 pre_balances: vec![1, 2, 3],
956 post_balances: vec![4, 5, 6],
957 inner_instructions: None,
958 log_messages: None,
959 pre_token_balances: None,
960 post_token_balances: None,
961 rewards: None,
962 loaded_addresses: LoadedAddresses {
963 writable: vec![],
964 readonly: vec![],
965 },
966 return_data: None,
967 compute_units_consumed: None,
968 cost_units: None,
969 };
970 #[rustfmt::skip]
971 let expected_json_output_value: serde_json::Value = serde_json::from_str(
972 "{\
973 \"err\":null,\
974 \"status\":{\"Ok\":null},\
975 \"fee\":1234,\
976 \"preBalances\":[1,2,3],\
977 \"postBalances\":[4,5,6],\
978 \"innerInstructions\":null,\
979 \"logMessages\":null,\
980 \"preTokenBalances\":null,\
981 \"postTokenBalances\":null,\
982 \"rewards\":null,\
983 \"loadedAddresses\":{\
984 \"readonly\": [],\
985 \"writable\": []\
986 }\
987 }",
988 )
989 .unwrap();
990 let ui_meta_from: UiTransactionStatusMeta = meta.clone().into();
991 assert_eq!(
992 serde_json::to_value(ui_meta_from).unwrap(),
993 expected_json_output_value
994 );
995
996 #[rustfmt::skip]
997 let expected_json_output_value: serde_json::Value = serde_json::from_str(
998 "{\
999 \"err\":null,\
1000 \"status\":{\"Ok\":null},\
1001 \"fee\":1234,\
1002 \"preBalances\":[1,2,3],\
1003 \"postBalances\":[4,5,6],\
1004 \"innerInstructions\":null,\
1005 \"logMessages\":null,\
1006 \"preTokenBalances\":null,\
1007 \"postTokenBalances\":null,\
1008 \"rewards\":null\
1009 }",
1010 )
1011 .unwrap();
1012 let ui_meta_parse_with_rewards = parse_ui_transaction_status_meta(meta.clone(), &[], true);
1013 assert_eq!(
1014 serde_json::to_value(ui_meta_parse_with_rewards).unwrap(),
1015 expected_json_output_value
1016 );
1017
1018 let ui_meta_parse_no_rewards = parse_ui_transaction_status_meta(meta, &[], false);
1019 assert_eq!(
1020 serde_json::to_value(ui_meta_parse_no_rewards).unwrap(),
1021 expected_json_output_value
1022 );
1023 }
1024
1025 #[test]
1026 fn test_confirmed_transaction_with_status_meta_encode() {
1027 use solana_transaction::versioned::VersionedTransaction;
1028
1029 let pubkey1 = solana_pubkey::new_rand();
1030 let pubkey2 = solana_pubkey::new_rand();
1031
1032 let message = solana_message::v0::Message {
1033 header: solana_message::MessageHeader {
1034 num_required_signatures: 1,
1035 num_readonly_signed_accounts: 0,
1036 num_readonly_unsigned_accounts: 1,
1037 },
1038 account_keys: vec![pubkey1, pubkey2],
1039 recent_blockhash: solana_hash::Hash::default(),
1040 instructions: vec![],
1041 address_table_lookups: vec![],
1042 };
1043
1044 let tx = VersionedTransaction {
1045 signatures: vec![solana_signature::Signature::from([1u8; 64])],
1046 message: solana_message::VersionedMessage::V0(message),
1047 };
1048
1049 let meta = TransactionStatusMeta {
1050 status: Ok(()),
1051 fee: 5000,
1052 pre_balances: vec![1_000_000, 0],
1053 post_balances: vec![994_900, 100],
1054 inner_instructions: None,
1055 log_messages: None,
1056 pre_token_balances: None,
1057 post_token_balances: None,
1058 rewards: None,
1059 loaded_addresses: LoadedAddresses::default(),
1060 return_data: None,
1061 compute_units_consumed: None,
1062 cost_units: None,
1063 };
1064
1065 let confirmed_tx = ConfirmedTransactionWithStatusMeta {
1066 slot: 42,
1067 tx_with_meta: TransactionWithStatusMeta::Complete(VersionedTransactionWithStatusMeta {
1068 transaction: tx,
1069 meta,
1070 }),
1071 block_time: Some(1234567890),
1072 index: 7,
1073 };
1074
1075 let encoded = confirmed_tx
1076 .encode(UiTransactionEncoding::Base64, Some(0))
1077 .unwrap();
1078
1079 assert_eq!(encoded.slot, 42);
1080 assert_eq!(encoded.block_time, Some(1234567890));
1081 assert_eq!(encoded.transaction_index, Some(7));
1082 }
1083
1084 #[test]
1085 fn test_v1_binary_encoding_uses_wire_format() {
1086 let message = solana_message::v1::Message::new(
1087 solana_message::MessageHeader {
1088 num_required_signatures: 1,
1089 num_readonly_signed_accounts: 0,
1090 num_readonly_unsigned_accounts: 1,
1091 },
1092 solana_message::v1::TransactionConfig::empty(),
1093 solana_hash::Hash::new_from_array([7; solana_hash::HASH_BYTES]),
1094 vec![
1095 solana_pubkey::Pubkey::new_unique(),
1096 solana_pubkey::Pubkey::new_unique(),
1097 ],
1098 vec![],
1099 );
1100 let transaction = VersionedTransaction {
1101 signatures: vec![solana_signature::Signature::default()],
1102 message: solana_message::VersionedMessage::V1(message),
1103 };
1104
1105 let EncodedTransaction::Binary(encoded, TransactionBinaryEncoding::Base64) =
1106 transaction.encode(UiTransactionEncoding::Base64)
1107 else {
1108 panic!("expected binary base64 output");
1109 };
1110 let bytes = BASE64_STANDARD.decode(&encoded).unwrap();
1111
1112 assert_eq!(bytes[0], solana_message::v1::V1_PREFIX);
1113 assert_eq!(bytes, wincode::serialize(&transaction).unwrap());
1114 assert_eq!(
1115 EncodedTransaction::Binary(encoded, TransactionBinaryEncoding::Base64).decode(),
1116 Some(transaction)
1117 );
1118 }
1119}