1
2use {
3 crate::{
4 option_serializer::OptionSerializer,
18 parse_accounts::{ParsedAccount, parse_v0_message_accounts, parse_legacy_message_accounts},
20 parse_instruction::{parse, ParsedInstruction},
24 },
29 solana_account_decoder::parse_token::UiTokenAmount,
30 solana_sdk::{
31 clock::{Slot, UnixTimestamp},
32 instruction::CompiledInstruction,
33 message::{
34 v0::{
35 self,
36 LoadedAddresses,
37 LoadedMessage,
38 MessageAddressTableLookup
39 },
40 AccountKeys,
41 Message,
42 MessageHeader,
43 VersionedMessage,
44 },
45 pubkey::{Pubkey, ParsePubkeyError},
46 signature::Signature,
47 transaction::{
48 Result as TransactionResult,
49 Transaction,
50 TransactionError,
51 TransactionVersion,
52 VersionedTransaction,
53 },
54 transaction_context::TransactionReturnData,
55 reward_type::RewardType,
56 signature::ParseSignatureError,
57 hash::{Hash, ParseHashError},
58 },
59 serde_derive::{Serialize,Deserialize},
60 thiserror::Error,
61};
62use std::{error::Error, fmt};
63use std::str::FromStr;
64#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
68pub struct TransactionByAddrInfo {
69 pub signature: Signature, pub err: Option<TransactionError>, pub index: u32, pub memo: Option<String>, pub block_time: Option<UnixTimestamp>,
74}
75
76#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
77#[serde(rename_all = "camelCase")]
78pub enum TransactionConfirmationStatus {
79 Processed,
80 Confirmed,
81 Finalized,
82}
83
84#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
85#[serde(rename_all = "camelCase")]
86pub struct TransactionStatus {
87 pub slot: Slot,
88 pub confirmations: Option<usize>, pub status: TransactionResult<()>, pub err: Option<TransactionError>,
91 pub confirmation_status: Option<TransactionConfirmationStatus>,
92}
93
94pub struct BlockEncodingOptions {
95 pub transaction_details: TransactionDetails,
96 pub show_rewards: bool,
97 pub max_supported_transaction_version: Option<u8>,
98}
99
100#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
101#[allow(clippy::large_enum_variant)]
102pub enum TransactionWithStatusMeta {
103 MissingMetadata(Transaction),
105 Complete(VersionedTransactionWithStatusMeta),
107}
108
109
110
111#[derive(Error, Debug, PartialEq, Eq, Clone)]
112pub enum EncodeError {
113 #[error("Encoding does not support transaction version {0}")]
114 UnsupportedTransactionVersion(u8),
115}
116
117#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
118pub struct VersionedTransactionWithStatusMeta {
119 pub transaction: VersionedTransaction,
120 pub meta: TransactionStatusMeta,
121}
122
123
124
125pub trait Encodable {
127 type Encoded;
128 fn encode(&self, encoding: UiTransactionEncoding) -> Self::Encoded;
129}
130
131pub trait EncodableWithMeta {
133 type Encoded;
134 fn encode_with_meta(
135 &self,
136 encoding: UiTransactionEncoding,
137 meta: &TransactionStatusMeta,
138 ) -> Self::Encoded;
139 fn json_encode(&self) -> Self::Encoded;
140}
141
142impl EncodableWithMeta for VersionedTransaction {
143 type Encoded = EncodedTransaction;
144 fn encode_with_meta(
145 &self,
146 encoding: UiTransactionEncoding,
147 meta: &TransactionStatusMeta,
148 ) -> Self::Encoded {
149 match encoding {
150 UiTransactionEncoding::Binary => EncodedTransaction::LegacyBinary(
151 bs58::encode(bincode::serialize(self).unwrap()).into_string(),
152 ),
153 UiTransactionEncoding::Base58 => EncodedTransaction::Binary(
154 bs58::encode(bincode::serialize(self).unwrap()).into_string(),
155 TransactionBinaryEncoding::Base58,
156 ),
157 UiTransactionEncoding::Base64 => EncodedTransaction::Binary(
158 base64::encode(bincode::serialize(self).unwrap()),
159 TransactionBinaryEncoding::Base64,
160 ),
161 UiTransactionEncoding::Json => self.json_encode(),
162 UiTransactionEncoding::JsonParsed => EncodedTransaction::Json(UiTransaction {
163 signatures: self.signatures.iter().map(ToString::to_string).collect(),
164 message: match &self.message {
165 VersionedMessage::Legacy(message) => {
166 message.encode(UiTransactionEncoding::JsonParsed)
167 }
168 VersionedMessage::V0(message) => {
169 message.encode_with_meta(UiTransactionEncoding::JsonParsed, meta)
170 }
171 },
172 }),
173 }
174 }
175 fn json_encode(&self) -> Self::Encoded {
176 EncodedTransaction::Json(UiTransaction {
177 signatures: self.signatures.iter().map(ToString::to_string).collect(),
178 message: match &self.message {
179 VersionedMessage::Legacy(message) => message.encode(UiTransactionEncoding::Json),
180 VersionedMessage::V0(message) => message.json_encode(),
181 },
182 })
183 }
184}
185
186
187
188impl Encodable for Transaction {
189 type Encoded = EncodedTransaction;
190 fn encode(&self, encoding: UiTransactionEncoding) -> Self::Encoded {
191 match encoding {
192 UiTransactionEncoding::Binary => EncodedTransaction::LegacyBinary(
193 bs58::encode(bincode::serialize(self).unwrap()).into_string(),
194 ),
195 UiTransactionEncoding::Base58 => EncodedTransaction::Binary(
196 bs58::encode(bincode::serialize(self).unwrap()).into_string(),
197 TransactionBinaryEncoding::Base58,
198 ),
199 UiTransactionEncoding::Base64 => EncodedTransaction::Binary(
200 base64::encode(bincode::serialize(self).unwrap()),
201 TransactionBinaryEncoding::Base64,
202 ),
203 UiTransactionEncoding::Json | UiTransactionEncoding::JsonParsed => {
204 EncodedTransaction::Json(UiTransaction {
205 signatures: self.signatures.iter().map(ToString::to_string).collect(),
206 message: self.message.encode(encoding),
207 })
208 }
209 }
210 }
211}
212
213impl Encodable for Message {
214 type Encoded = UiMessage;
215 fn encode(&self, encoding: UiTransactionEncoding) -> Self::Encoded {
216 if encoding == UiTransactionEncoding::JsonParsed {
217 let account_keys = AccountKeys::new(&self.account_keys, None);
218 UiMessage::Parsed(UiParsedMessage {
219 account_keys: parse_legacy_message_accounts(self),
220 recent_blockhash: self.recent_blockhash.to_string(),
221 instructions: self
222 .instructions
223 .iter()
224 .map(|instruction| UiInstruction::parse(instruction, &account_keys, None))
225 .collect(),
226 address_table_lookups: None,
227 })
228 } else {
229 UiMessage::Raw(UiRawMessage {
230 header: self.header,
231 account_keys: self.account_keys.iter().map(ToString::to_string).collect(),
232 recent_blockhash: self.recent_blockhash.to_string(),
233 instructions: self
234 .instructions
235 .iter()
236 .map(|ix| UiCompiledInstruction::from(ix, None))
237 .collect(),
238 address_table_lookups: None,
239 })
240 }
241 }
242}
243
244impl EncodableWithMeta for v0::Message {
245 type Encoded = UiMessage;
246 fn encode_with_meta(
247 &self,
248 encoding: UiTransactionEncoding,
249 meta: &TransactionStatusMeta,
250 ) -> Self::Encoded {
251 if encoding == UiTransactionEncoding::JsonParsed {
252 let account_keys = AccountKeys::new(&self.account_keys, Some(&meta.loaded_addresses));
253 let loaded_message = LoadedMessage::new_borrowed(self, &meta.loaded_addresses);
254 UiMessage::Parsed(UiParsedMessage {
255 account_keys: parse_v0_message_accounts(&loaded_message),
256 recent_blockhash: self.recent_blockhash.to_string(),
257 instructions: self
258 .instructions
259 .iter()
260 .map(|instruction| UiInstruction::parse(instruction, &account_keys, None))
261 .collect(),
262 address_table_lookups: Some(
263 self.address_table_lookups.iter().map(Into::into).collect(),
264 ),
265 })
266 } else {
267 self.json_encode()
268 }
269 }
270 fn json_encode(&self) -> Self::Encoded {
271 UiMessage::Raw(UiRawMessage {
272 header: self.header,
273 account_keys: self.account_keys.iter().map(ToString::to_string).collect(),
274 recent_blockhash: self.recent_blockhash.to_string(),
275 instructions: self
276 .instructions
277 .iter()
278 .map(|ix| UiCompiledInstruction::from(ix, None))
279 .collect(),
280 address_table_lookups: Some(
281 self.address_table_lookups.iter().map(Into::into).collect(),
282 ),
283 })
284 }
285}
286
287impl DecodableWithMeta for v0::Message {
288 type Encoded = UiMessage;
289 type Decoded = v0::Message;
290
291 fn decode_with_meta(
292 encoded: Self::Encoded,
293 decoding: UiTransactionEncoding,
294 version: Option<TransactionVersion>
295 ) -> Result<Self::Decoded, DecodeError> {
297 match decoding {
298 UiTransactionEncoding::Json => match encoded {
299 UiMessage::Raw(_) => Self::json_decode(encoded, version),
300 UiMessage::Parsed(_) => Err(DecodeError::UnsupportedEncoding),
301 },
302 _ => Err(DecodeError::UnsupportedEncoding),
303 }
304 }
305
306 fn json_decode(encoded: Self::Encoded, version: Option<TransactionVersion>) -> Result<Self::Decoded, DecodeError> {
307 if let UiMessage::Raw(raw_msg) = encoded {
309 let header = raw_msg.header;
310 let account_keys = raw_msg.account_keys
311 .iter()
312 .map(|s| s.parse::<Pubkey>())
313 .collect::<Result<Vec<_>, _>>()
314 .map_err(|_| DecodeError::InvalidAccountKey)?;
315 let recent_blockhash = raw_msg.recent_blockhash.parse::<Hash>()
316 .map_err(|_| DecodeError::InvalidBlockhash)?;
317 let instructions = raw_msg.instructions
329 .iter()
330 .map(|i| CompiledInstruction::from(i.clone()))
331 .collect::<Vec<_>>();
332 let address_table_lookups = match raw_msg.address_table_lookups {
333 Some(lookups) => lookups
334 .iter()
335 .map(|lookup| MessageAddressTableLookup::try_from(lookup))
336 .collect::<Result<Vec<_>, _>>()?,
337 None => vec![],
338 };
339
340 Ok(Self {
341 header,
342 account_keys,
343 recent_blockhash,
344 instructions,
345 address_table_lookups,
346 })
347 } else {
348 Err(DecodeError::UnsupportedEncoding)
349 }
350 }
351}
352
353
354trait JsonAccounts {
355 type Encoded;
356 fn build_json_accounts(&self) -> Self::Encoded;
357}
358
359impl JsonAccounts for Transaction {
360 type Encoded = EncodedTransaction;
361 fn build_json_accounts(&self) -> Self::Encoded {
362 EncodedTransaction::Accounts(UiAccountsList {
363 signatures: self.signatures.iter().map(ToString::to_string).collect(),
364 account_keys: parse_legacy_message_accounts(&self.message),
365 })
366 }
367}
368
369#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
370pub struct TransactionStatusMeta {
371 pub status: TransactionResult<()>,
372 pub fee: u64,
373 pub pre_balances: Vec<u64>,
374 pub post_balances: Vec<u64>,
375 pub inner_instructions: Option<Vec<InnerInstructions>>,
376 pub log_messages: Option<Vec<String>>,
377 pub pre_token_balances: Option<Vec<TransactionTokenBalance>>,
378 pub post_token_balances: Option<Vec<TransactionTokenBalance>>,
379 pub rewards: Option<Rewards>,
380 pub loaded_addresses: LoadedAddresses,
381 pub return_data: Option<TransactionReturnData>,
382 pub compute_units_consumed: Option<u64>,
383}
384
385#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
386pub struct InnerInstructions {
387 pub index: u8,
389 pub instructions: Vec<InnerInstruction>,
391 }
393
394#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
395pub struct InnerInstruction {
396 pub instruction: CompiledInstruction,
398 pub stack_height: Option<u32>,
400}
401
402#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
403#[serde(rename_all = "camelCase")]
404pub struct UiInnerInstructions {
405 pub index: u8,
407 pub instructions: Vec<UiInstruction>,
409}
410
411impl UiInnerInstructions {
412 fn parse(inner_instructions: InnerInstructions, account_keys: &AccountKeys) -> Self {
413 Self {
414 index: inner_instructions.index,
415 instructions: inner_instructions
416 .instructions
417 .iter()
418 .map(
419 |InnerInstruction {
420 instruction: ix,
421 stack_height,
422 }| {
423 UiInstruction::parse(ix, account_keys, *stack_height)
424 },
425 )
426 .collect(),
427 }
428 }
429}
430
431impl From<InnerInstructions> for UiInnerInstructions {
432 fn from(inner_instructions: InnerInstructions) -> Self {
433 Self {
434 index: inner_instructions.index,
435 instructions: inner_instructions
436 .instructions
437 .iter()
438 .map(
439 |InnerInstruction {
440 instruction: ix,
441 stack_height,
442 }| {
443 UiInstruction::Compiled(UiCompiledInstruction::from(ix, *stack_height))
444 },
445 )
446 .collect(),
447 }
448 }
449}
450
451#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
453#[serde(rename_all = "camelCase", untagged)]
454pub enum UiInstruction {
455 Compiled(UiCompiledInstruction),
456 Parsed(UiParsedInstruction),
457}
458
459impl UiInstruction {
460 fn parse(
461 instruction: &CompiledInstruction,
462 account_keys: &AccountKeys,
463 stack_height: Option<u32>,
464 ) -> Self {
465 let program_id = &account_keys[instruction.program_id_index as usize];
466 if let Ok(parsed_instruction) = parse(program_id, instruction, account_keys, stack_height) {
467 UiInstruction::Parsed(UiParsedInstruction::Parsed(parsed_instruction))
468 } else {
469 UiInstruction::Parsed(UiParsedInstruction::PartiallyDecoded(
470 UiPartiallyDecodedInstruction::from(instruction, account_keys, stack_height),
471 ))
472 }
473 }
474}
475
476#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
477#[serde(rename_all = "camelCase", untagged)]
478pub enum UiParsedInstruction {
479 Parsed(ParsedInstruction),
480 PartiallyDecoded(UiPartiallyDecodedInstruction),
481}
482
483#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
493#[serde(rename_all = "camelCase")]
494pub struct UiPartiallyDecodedInstruction {
495 pub program_id: String,
496 pub accounts: Vec<String>,
497 pub data: String,
498 pub stack_height: Option<u32>,
499}
500
501impl UiPartiallyDecodedInstruction {
502 fn from(
503 instruction: &CompiledInstruction,
504 account_keys: &AccountKeys,
505 stack_height: Option<u32>,
506 ) -> Self {
507 Self {
508 program_id: account_keys[instruction.program_id_index as usize].to_string(),
509 accounts: instruction
510 .accounts
511 .iter()
512 .map(|&i| account_keys[i as usize].to_string())
513 .collect(),
514 data: bs58::encode(instruction.data.clone()).into_string(),
515 stack_height,
516 }
517 }
518}
519
520#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
522#[serde(rename_all = "camelCase")]
523pub struct UiCompiledInstruction {
524 pub program_id_index: u8,
525 pub accounts: Vec<u8>,
526 pub data: String,
527 pub stack_height: Option<u32>,
528}
529
530impl UiCompiledInstruction {
531 fn from(instruction: &CompiledInstruction, stack_height: Option<u32>) -> Self {
532 Self {
533 program_id_index: instruction.program_id_index,
534 accounts: instruction.accounts.clone(),
535 data: bs58::encode(&instruction.data).into_string(),
536 stack_height,
537 }
538 }
539}
540
541#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
552pub struct TransactionTokenBalance {
553 pub account_index: u8,
554 pub mint: String,
555 pub ui_token_amount: UiTokenAmount,
556 pub owner: String,
557 pub program_id: String,
558}
559
560#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
561pub struct ConfirmedTransactionWithStatusMeta {
562 pub slot: Slot,
563 pub tx_with_meta: TransactionWithStatusMeta,
564 pub block_time: Option<UnixTimestamp>,
565}
566
567#[allow(dead_code)]
569impl ConfirmedTransactionWithStatusMeta {
570 pub fn encode(
571 self,
572 encoding: UiTransactionEncoding,
573 max_supported_transaction_version: Option<u8>,
574 ) -> Result<EncodedConfirmedTransactionWithStatusMeta, EncodeError> {
575 Ok(EncodedConfirmedTransactionWithStatusMeta {
576 slot: self.slot,
577 transaction: self.tx_with_meta.encode(
578 encoding,
579 max_supported_transaction_version,
580 true,
581 )?,
582 block_time: self.block_time,
583 })
584 }
585}
586
587#[derive(Debug, PartialEq, Serialize, Deserialize)]
588#[serde(rename_all = "camelCase")]
589pub struct EncodedConfirmedTransactionWithStatusMeta {
590 pub slot: Slot,
591 #[serde(flatten)]
592 pub transaction: EncodedTransactionWithStatusMeta,
593 pub block_time: Option<UnixTimestamp>,
594}
595
596#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
597#[serde(rename_all = "camelCase")]
598pub struct EncodedTransactionWithStatusMeta {
599 pub transaction: EncodedTransaction,
600 pub meta: Option<UiTransactionStatusMeta>,
601 #[serde(default, skip_serializing_if = "Option::is_none")]
602 pub version: Option<TransactionVersion>,
603}
604
605#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
606#[serde(rename_all = "camelCase", untagged)]
607pub enum EncodedTransaction {
608 LegacyBinary(String), Binary(String, TransactionBinaryEncoding),
610 Json(UiTransaction),
611 Accounts(UiAccountsList),
612}
613
614impl EncodedTransaction {
615 pub fn decode(&self) -> Option<VersionedTransaction> {
616 let (blob, encoding) = match self {
617 Self::Json(_) | Self::Accounts(_) => return None,
618 Self::LegacyBinary(blob) => (blob, TransactionBinaryEncoding::Base58),
619 Self::Binary(blob, encoding) => (blob, *encoding),
620 };
621
622 let transaction: Option<VersionedTransaction> = match encoding {
623 TransactionBinaryEncoding::Base58 => bs58::decode(blob)
624 .into_vec()
625 .ok()
626 .and_then(|bytes| bincode::deserialize(&bytes).ok()),
627 TransactionBinaryEncoding::Base64 => base64::decode(blob)
628 .ok()
629 .and_then(|bytes| bincode::deserialize(&bytes).ok()),
630 };
631
632 transaction.filter(|transaction| {
633 transaction
634 .sanitize(
635 true, )
637 .is_ok()
638 })
639 }
640}
641
642#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
644#[serde(rename_all = "camelCase")]
645pub struct UiTransactionStatusMeta {
646 pub err: Option<TransactionError>,
647 pub status: TransactionResult<()>, pub fee: u64,
649 pub pre_balances: Vec<u64>,
650 pub post_balances: Vec<u64>,
651 #[serde(
652 default = "OptionSerializer::none",
653 skip_serializing_if = "OptionSerializer::should_skip"
654 )]
655 pub inner_instructions: OptionSerializer<Vec<UiInnerInstructions>>,
656 #[serde(
657 default = "OptionSerializer::none",
658 skip_serializing_if = "OptionSerializer::should_skip"
659 )]
660 pub log_messages: OptionSerializer<Vec<String>>,
661 #[serde(
662 default = "OptionSerializer::none",
663 skip_serializing_if = "OptionSerializer::should_skip"
664 )]
665 pub pre_token_balances: OptionSerializer<Vec<UiTransactionTokenBalance>>,
666 #[serde(
667 default = "OptionSerializer::none",
668 skip_serializing_if = "OptionSerializer::should_skip"
669 )]
670 pub post_token_balances: OptionSerializer<Vec<UiTransactionTokenBalance>>,
671 #[serde(
672 default = "OptionSerializer::none",
673 skip_serializing_if = "OptionSerializer::should_skip"
674 )]
675 pub rewards: OptionSerializer<Rewards>,
676 #[serde(
677 default = "OptionSerializer::skip",
678 skip_serializing_if = "OptionSerializer::should_skip"
679 )]
680 pub loaded_addresses: OptionSerializer<UiLoadedAddresses>,
681 #[serde(
682 default = "OptionSerializer::skip",
683 skip_serializing_if = "OptionSerializer::should_skip"
684 )]
685 pub return_data: OptionSerializer<UiTransactionReturnData>,
686 #[serde(
687 default = "OptionSerializer::skip",
688 skip_serializing_if = "OptionSerializer::should_skip"
689 )]
690 pub compute_units_consumed: OptionSerializer<u64>,
691}
692
693impl UiTransactionStatusMeta {
694 fn parse(meta: TransactionStatusMeta, static_keys: &[Pubkey], show_rewards: bool) -> Self {
695 let account_keys = AccountKeys::new(static_keys, Some(&meta.loaded_addresses));
696 Self {
697 err: meta.status.clone().err(),
698 status: meta.status,
699 fee: meta.fee,
700 pre_balances: meta.pre_balances,
701 post_balances: meta.post_balances,
702 inner_instructions: meta
703 .inner_instructions
704 .map(|ixs| {
705 ixs.into_iter()
706 .map(|ix| UiInnerInstructions::parse(ix, &account_keys))
707 .collect()
708 })
709 .into(),
710 log_messages: meta.log_messages.into(),
711 pre_token_balances: meta
712 .pre_token_balances
713 .map(|balance| balance.into_iter().map(Into::into).collect())
714 .into(),
715 post_token_balances: meta
716 .post_token_balances
717 .map(|balance| balance.into_iter().map(Into::into).collect())
718 .into(),
719 rewards: if show_rewards { meta.rewards } else { None }.into(),
720 loaded_addresses: OptionSerializer::Skip,
721 return_data: OptionSerializer::or_skip(
722 meta.return_data.map(|return_data| return_data.into()),
723 ),
724 compute_units_consumed: OptionSerializer::or_skip(meta.compute_units_consumed),
725 }
726 }
727
728 fn build_simple(meta: TransactionStatusMeta, show_rewards: bool) -> Self {
729 Self {
730 err: meta.status.clone().err(),
731 status: meta.status,
732 fee: meta.fee,
733 pre_balances: meta.pre_balances,
734 post_balances: meta.post_balances,
735 inner_instructions: OptionSerializer::Skip,
736 log_messages: OptionSerializer::Skip,
737 pre_token_balances: meta
738 .pre_token_balances
739 .map(|balance| balance.into_iter().map(Into::into).collect())
740 .into(),
741 post_token_balances: meta
742 .post_token_balances
743 .map(|balance| balance.into_iter().map(Into::into).collect())
744 .into(),
745 rewards: if show_rewards {
746 meta.rewards.into()
747 } else {
748 OptionSerializer::Skip
749 },
750 loaded_addresses: OptionSerializer::Skip,
751 return_data: OptionSerializer::Skip,
752 compute_units_consumed: OptionSerializer::Skip,
753 }
754 }
755}
756
757impl From<TransactionStatusMeta> for UiTransactionStatusMeta {
758 fn from(meta: TransactionStatusMeta) -> Self {
759 Self {
760 err: meta.status.clone().err(),
761 status: meta.status,
762 fee: meta.fee,
763 pre_balances: meta.pre_balances,
764 post_balances: meta.post_balances,
765 inner_instructions: meta
766 .inner_instructions
767 .map(|ixs| ixs.into_iter().map(Into::into).collect())
768 .into(),
769 log_messages: meta.log_messages.into(),
770 pre_token_balances: meta
771 .pre_token_balances
772 .map(|balance| balance.into_iter().map(Into::into).collect())
773 .into(),
774 post_token_balances: meta
775 .post_token_balances
776 .map(|balance| balance.into_iter().map(Into::into).collect())
777 .into(),
778 rewards: meta.rewards.into(),
779 loaded_addresses: Some(UiLoadedAddresses::from(&meta.loaded_addresses)).into(),
780 return_data: OptionSerializer::or_skip(
781 meta.return_data.map(|return_data| return_data.into()),
782 ),
783 compute_units_consumed: OptionSerializer::or_skip(meta.compute_units_consumed),
784 }
785 }
786}
787
788#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
789#[serde(rename_all = "camelCase")]
790pub struct Reward {
791 pub pubkey: String,
792 pub lamports: i64,
793 pub post_balance: u64, pub reward_type: Option<RewardType>,
795 pub commission: Option<u8>, }
797
798pub type Rewards = Vec<Reward>;
799
800#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
802#[serde(rename_all = "camelCase")]
803pub struct UiLoadedAddresses {
804 pub writable: Vec<String>,
805 pub readonly: Vec<String>,
806}
807
808impl From<&LoadedAddresses> for UiLoadedAddresses {
809 fn from(loaded_addresses: &LoadedAddresses) -> Self {
810 Self {
811 writable: loaded_addresses
812 .writable
813 .iter()
814 .map(ToString::to_string)
815 .collect(),
816 readonly: loaded_addresses
817 .readonly
818 .iter()
819 .map(ToString::to_string)
820 .collect(),
821 }
822 }
823}
824
825#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
826#[serde(rename_all = "camelCase")]
827pub struct UiTransactionReturnData {
828 pub program_id: String,
829 pub data: (String, UiReturnDataEncoding),
830}
831
832impl Default for UiTransactionReturnData {
833 fn default() -> Self {
834 Self {
835 program_id: String::default(),
836 data: (String::default(), UiReturnDataEncoding::Base64),
837 }
838 }
839}
840
841impl From<TransactionReturnData> for UiTransactionReturnData {
842 fn from(return_data: TransactionReturnData) -> Self {
843 Self {
844 program_id: return_data.program_id.to_string(),
845 data: (
846 base64::encode(return_data.data),
847 UiReturnDataEncoding::Base64,
848 ),
849 }
850 }
851}
852
853#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, Hash, PartialEq)]
854#[serde(rename_all = "camelCase")]
855pub enum UiReturnDataEncoding {
856 Base64,
857}
858
859#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, Hash, PartialEq)]
860#[serde(rename_all = "camelCase")]
861pub enum TransactionBinaryEncoding {
862 Base58,
863 Base64,
864}
865
866#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
867#[serde(rename_all = "camelCase")]
868pub struct UiTransactionTokenBalance {
869 pub account_index: u8,
870 pub mint: String,
871 pub ui_token_amount: UiTokenAmount,
872 #[serde(
873 default = "OptionSerializer::skip",
874 skip_serializing_if = "OptionSerializer::should_skip"
875 )]
876 pub owner: OptionSerializer<String>,
877 #[serde(
878 default = "OptionSerializer::skip",
879 skip_serializing_if = "OptionSerializer::should_skip"
880 )]
881 pub program_id: OptionSerializer<String>,
882}
883
884impl From<TransactionTokenBalance> for UiTransactionTokenBalance {
885 fn from(token_balance: TransactionTokenBalance) -> Self {
886 Self {
887 account_index: token_balance.account_index,
888 mint: token_balance.mint,
889 ui_token_amount: token_balance.ui_token_amount,
890 owner: if !token_balance.owner.is_empty() {
891 OptionSerializer::Some(token_balance.owner)
892 } else {
893 OptionSerializer::Skip
894 },
895 program_id: if !token_balance.program_id.is_empty() {
896 OptionSerializer::Some(token_balance.program_id)
897 } else {
898 OptionSerializer::Skip
899 },
900 }
901 }
902}
903
904#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
906#[serde(rename_all = "camelCase")]
907pub struct UiTransaction {
908 pub signatures: Vec<String>,
909 pub message: UiMessage,
910}
911
912#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
913#[serde(rename_all = "camelCase", untagged)]
914pub enum UiMessage {
915 Parsed(UiParsedMessage),
916 Raw(UiRawMessage),
917}
918
919#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
921#[serde(rename_all = "camelCase")]
922pub struct UiRawMessage {
923 pub header: MessageHeader,
924 pub account_keys: Vec<String>,
925 pub recent_blockhash: String,
926 pub instructions: Vec<UiCompiledInstruction>,
927 #[serde(default, skip_serializing_if = "Option::is_none")]
928 pub address_table_lookups: Option<Vec<UiAddressTableLookup>>,
929}
930
931#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
932#[serde(rename_all = "camelCase")]
933pub struct UiAccountsList {
934 pub signatures: Vec<String>,
935 pub account_keys: Vec<ParsedAccount>,
936}
937
938#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
940#[serde(rename_all = "camelCase")]
941pub struct UiAddressTableLookup {
942 pub account_key: String,
943 pub writable_indexes: Vec<u8>,
944 pub readonly_indexes: Vec<u8>,
945}
946
947impl From<&MessageAddressTableLookup> for UiAddressTableLookup {
948 fn from(lookup: &MessageAddressTableLookup) -> Self {
949 Self {
950 account_key: lookup.account_key.to_string(),
951 writable_indexes: lookup.writable_indexes.clone(),
952 readonly_indexes: lookup.readonly_indexes.clone(),
953 }
954 }
955}
956
957impl TryFrom<&UiAddressTableLookup> for MessageAddressTableLookup {
958 type Error = DecodeError;
959
960 fn try_from(lookup: &UiAddressTableLookup) -> Result<Self, Self::Error> {
961 let account_key = Pubkey::from_str(&lookup.account_key)
962 .map_err(|_| DecodeError::ParsePubkeyFailed(ParsePubkeyError::Invalid))?;
963 Ok(Self {
964 account_key,
965 writable_indexes: lookup.writable_indexes.clone(),
966 readonly_indexes: lookup.readonly_indexes.clone(),
967 })
968 }
969}
970
971#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
973#[serde(rename_all = "camelCase")]
974pub struct UiParsedMessage {
975 pub account_keys: Vec<ParsedAccount>,
976 pub recent_blockhash: String,
977 pub instructions: Vec<UiInstruction>,
978 pub address_table_lookups: Option<Vec<UiAddressTableLookup>>,
979}
980
981#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, Hash, PartialEq)]
982#[serde(rename_all = "camelCase")]
983pub enum UiTransactionEncoding {
984 Binary, Base64,
986 Base58,
987 Json,
988 JsonParsed,
989}
990
991#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, Serialize, Deserialize)]
992#[serde(rename_all = "camelCase")]
993pub enum TransactionDetails {
994 Full,
995 Signatures,
996 None,
997 Accounts,
998}
999
1000impl Default for TransactionDetails {
1001 fn default() -> Self {
1002 Self::Full
1003 }
1004}
1005
1006#[derive(Debug, Error)]
1007pub enum ConvertBlockError {
1008 #[error("transactions missing after converted, before: {0}, after: {1}")]
1009 TransactionsMissing(usize, usize),
1010}
1011
1012#[derive(Clone, Debug, PartialEq)]
1013pub struct ConfirmedBlock {
1014 pub previous_blockhash: String,
1015 pub blockhash: String,
1016 pub parent_slot: Slot,
1017 pub transactions: Vec<TransactionWithStatusMeta>,
1018 pub rewards: Rewards,
1019 pub block_time: Option<UnixTimestamp>,
1020 pub block_height: Option<u64>,
1021}
1022
1023#[derive(Clone, Debug, PartialEq)]
1026pub struct VersionedConfirmedBlock {
1027 pub previous_blockhash: String,
1028 pub blockhash: String,
1029 pub parent_slot: Slot,
1030 pub transactions: Vec<VersionedTransactionWithStatusMeta>,
1031 pub rewards: Rewards,
1032 pub block_time: Option<UnixTimestamp>,
1033 pub block_height: Option<u64>,
1034}
1035
1036impl From<VersionedConfirmedBlock> for ConfirmedBlock {
1037 fn from(block: VersionedConfirmedBlock) -> Self {
1038 Self {
1039 previous_blockhash: block.previous_blockhash,
1040 blockhash: block.blockhash,
1041 parent_slot: block.parent_slot,
1042 transactions: block
1043 .transactions
1044 .into_iter()
1045 .map(TransactionWithStatusMeta::Complete)
1046 .collect(),
1047 rewards: block.rewards,
1048 block_time: block.block_time,
1049 block_height: block.block_height,
1050 }
1051 }
1052}
1053
1054
1055
1056#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
1057#[serde(rename_all = "camelCase")]
1058pub struct UiConfirmedBlock {
1059 pub previous_blockhash: String,
1060 pub blockhash: String,
1061 pub parent_slot: Slot,
1062 #[serde(default, skip_serializing_if = "Option::is_none")]
1063 pub transactions: Option<Vec<EncodedTransactionWithStatusMeta>>,
1064 #[serde(default, skip_serializing_if = "Option::is_none")]
1065 pub signatures: Option<Vec<String>>,
1066 #[serde(default, skip_serializing_if = "Option::is_none")]
1067 pub rewards: Option<Rewards>,
1068 pub block_time: Option<UnixTimestamp>,
1069 pub block_height: Option<u64>,
1070}
1071
1072#[derive(Debug, PartialEq, Serialize, Deserialize)]
1073#[serde(rename_all = "camelCase")]
1074pub struct EncodedConfirmedBlock {
1075 pub previous_blockhash: String,
1076 pub blockhash: String,
1077 pub parent_slot: Slot,
1078 pub transactions: Vec<EncodedTransactionWithStatusMeta>,
1079 pub rewards: Rewards,
1080 pub block_time: Option<UnixTimestamp>,
1081 pub block_height: Option<u64>,
1082}
1083
1084impl From<UiConfirmedBlock> for EncodedConfirmedBlock {
1085 fn from(block: UiConfirmedBlock) -> Self {
1086 Self {
1087 previous_blockhash: block.previous_blockhash,
1088 blockhash: block.blockhash,
1089 parent_slot: block.parent_slot,
1090 transactions: block.transactions.unwrap_or_default(),
1091 rewards: block.rewards.unwrap_or_default(),
1092 block_time: block.block_time,
1093 block_height: block.block_height,
1094 }
1095 }
1096}
1097
1098
1099
1100impl VersionedTransactionWithStatusMeta {
1101 fn validate_version(
1102 &self,
1103 max_supported_transaction_version: Option<u8>,
1104 ) -> Result<Option<TransactionVersion>, EncodeError> {
1105 match (
1106 max_supported_transaction_version,
1107 self.transaction.version(),
1108 ) {
1109 (None, TransactionVersion::LEGACY) => Ok(None),
1111 (None, TransactionVersion::Number(version)) => {
1112 Err(EncodeError::UnsupportedTransactionVersion(version))
1113 }
1114 (Some(_), TransactionVersion::LEGACY) => Ok(Some(TransactionVersion::LEGACY)),
1115 (Some(max_version), TransactionVersion::Number(version)) => {
1116 if version <= max_version {
1117 Ok(Some(TransactionVersion::Number(version)))
1118 } else {
1119 Err(EncodeError::UnsupportedTransactionVersion(version))
1120 }
1121 }
1122 }
1123 }
1124
1125 pub fn account_keys(&self) -> AccountKeys {
1126 AccountKeys::new(
1127 self.transaction.message.static_account_keys(),
1128 Some(&self.meta.loaded_addresses),
1129 )
1130 }
1131
1132 pub fn encode(
1133 self,
1134 encoding: UiTransactionEncoding,
1135 max_supported_transaction_version: Option<u8>,
1136 show_rewards: bool,
1137 ) -> Result<EncodedTransactionWithStatusMeta, EncodeError> {
1138 let version = self.validate_version(max_supported_transaction_version)?;
1139
1140 Ok(EncodedTransactionWithStatusMeta {
1141 transaction: self.transaction.encode_with_meta(encoding, &self.meta),
1142 meta: Some(match encoding {
1143 UiTransactionEncoding::JsonParsed => UiTransactionStatusMeta::parse(
1144 self.meta,
1145 self.transaction.message.static_account_keys(),
1146 show_rewards,
1147 ),
1148 _ => {
1149 let mut meta = UiTransactionStatusMeta::from(self.meta);
1150 if !show_rewards {
1151 meta.rewards = OptionSerializer::None;
1152 }
1153 meta
1154 }
1155 }),
1156 version,
1157 })
1158 }
1159
1160 pub fn decode(
1161 encoded: EncodedTransactionWithStatusMeta,
1162 encoding: UiTransactionEncoding,
1163 ) -> Result<Self, DecodeError> {
1165 let transaction = match VersionedTransaction::decode_with_meta(encoded.transaction, encoding, encoded.version ) {
1167 Ok(decoded) => decoded,
1168 Err(e) => return Err(e),
1169 };
1170
1171 let meta = match encoded.meta {
1173 Some(ui_meta) => match TransactionStatusMeta::try_from(ui_meta) {
1174 Ok(meta) => meta,
1175 Err(_) => return Err(DecodeError::InvalidData),
1176 },
1177 None => return Err(DecodeError::InvalidData),
1178 };
1179
1180 Ok(Self {
1181 transaction,
1182 meta,
1183 })
1184 }
1185
1186 fn build_json_accounts(
1187 self,
1188 max_supported_transaction_version: Option<u8>,
1189 show_rewards: bool,
1190 ) -> Result<EncodedTransactionWithStatusMeta, EncodeError> {
1191 let version = self.validate_version(max_supported_transaction_version)?;
1192
1193 let account_keys = match &self.transaction.message {
1194 VersionedMessage::Legacy(message) => parse_legacy_message_accounts(message),
1195 VersionedMessage::V0(message) => {
1196 let loaded_message =
1197 LoadedMessage::new_borrowed(message, &self.meta.loaded_addresses);
1198 parse_v0_message_accounts(&loaded_message)
1199 }
1200 };
1201
1202 Ok(EncodedTransactionWithStatusMeta {
1203 transaction: EncodedTransaction::Accounts(UiAccountsList {
1204 signatures: self
1205 .transaction
1206 .signatures
1207 .iter()
1208 .map(ToString::to_string)
1209 .collect(),
1210 account_keys,
1211 }),
1212 meta: Some(UiTransactionStatusMeta::build_simple(
1213 self.meta,
1214 show_rewards,
1215 )),
1216 version,
1217 })
1218 }
1219}
1220
1221
1222#[derive(Debug, PartialEq)]
1223pub enum DecodeError {
1224 InvalidEncoding,
1225 InvalidAccountKey,
1226 InvalidBlockhash,
1227 DecodeFailed,
1228 DeserializeFailed,
1229 ParseSignatureFailed(ParseSignatureError),
1230 ParseHashFailed(ParseHashError),
1231 ParsePubkeyFailed(ParsePubkeyError),
1232 NotImplemented,
1233 InvalidData,
1234 UnsupportedEncoding,
1235 UnsupportedVersion,
1236}
1237
1238impl fmt::Display for DecodeError {
1239 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1240 match self {
1241 DecodeError::InvalidEncoding => write!(f, "Invalid encoding"),
1242 DecodeError::DecodeFailed => write!(f, "Decoding failed"),
1243 DecodeError::DeserializeFailed => write!(f, "Deserialization failed"),
1244 DecodeError::InvalidAccountKey => write!(f, "Invalid account key"),
1245 DecodeError::InvalidBlockhash => write!(f, "Invalid blockhash"),
1246 DecodeError::ParseSignatureFailed(err) => write!(f, "Failed to parse signature: {}", err),
1247 DecodeError::ParseHashFailed(err) => write!(f, "Failed to parse hash: {}", err),
1248 DecodeError::ParsePubkeyFailed(err) => write!(f, "Failed to parse pubkey: {}", err),
1249 DecodeError::NotImplemented => write!(f, "Not implemented"),
1250 DecodeError::InvalidData => write!(f, "Invalid data"),
1251 DecodeError::UnsupportedEncoding => write!(f, "Encoding is not supported"),
1252 DecodeError::UnsupportedVersion => write!(f, "Transaction version is not supported"),
1253 }
1254 }
1255}
1256
1257impl Error for DecodeError {}
1258
1259impl From<ParsePubkeyError> for DecodeError {
1260 fn from(err: ParsePubkeyError) -> Self {
1261 DecodeError::ParsePubkeyFailed(err)
1262 }
1263}
1264
1265
1266
1267
1268
1269pub trait Decodable {
1270 type Encoded;
1271 type Decoded;
1272 fn decode(encoded: &Self::Encoded) -> Result<Self::Decoded, DecodeError>;
1273}
1274
1275impl Decodable for Message {
1276 type Encoded = UiMessage;
1277 type Decoded = Message;
1278
1279 fn decode(encoded: &Self::Encoded) -> Result<Self::Decoded, DecodeError> {
1280 match encoded {
1281 UiMessage::Raw(raw_message) => {
1282 let header = raw_message.header;
1283 let account_keys: Result<Vec<Pubkey>, _> = raw_message
1284 .account_keys
1285 .iter()
1286 .map(|key_str| key_str.parse())
1287 .collect();
1288 let account_keys = account_keys?;
1289 let recent_blockhash = Hash::from_str(&raw_message.recent_blockhash)
1290 .map_err(|err| DecodeError::ParseHashFailed(err))?;
1291 let instructions: Vec<CompiledInstruction> = raw_message
1292 .instructions
1293 .iter()
1294 .map(|ui_instruction| ui_instruction.clone().into() )
1296 .collect();
1297
1298 Ok(Message {
1299 header,
1300 account_keys,
1301 recent_blockhash,
1302 instructions,
1303 })
1304 }
1305 UiMessage::Parsed(_) => {
1306 Err(DecodeError::UnsupportedEncoding)
1307 }
1308 }
1309 }
1310}
1311
1312impl Decodable for Transaction {
1313 type Encoded = EncodedTransaction;
1314 type Decoded = Transaction;
1315
1316 fn decode(encoded: &Self::Encoded) -> Result<Self::Decoded, DecodeError> {
1317 match encoded {
1318 EncodedTransaction::LegacyBinary(s) | EncodedTransaction::Binary(s, TransactionBinaryEncoding::Base58) => {
1319 let data = bs58::decode(s)
1321 .into_vec()
1322 .map_err(|_| DecodeError::DeserializeFailed)?;
1323 let transaction: Transaction = bincode::deserialize(&data)
1325 .map_err(|_| DecodeError::DeserializeFailed)?;
1326 Ok(transaction)
1327 }
1328 EncodedTransaction::Binary(s, TransactionBinaryEncoding::Base64) => {
1329 let data = base64::decode(s)
1331 .map_err(|_| DecodeError::DeserializeFailed)?;
1332 let transaction: Transaction = bincode::deserialize(&data)
1334 .map_err(|_| DecodeError::DeserializeFailed)?;
1335 Ok(transaction)
1336 }
1337 EncodedTransaction::Json(ui_transaction) => {
1338 let message = Message::decode(&ui_transaction.message)?;
1339 let signatures: Result<Vec<Signature>, ParseSignatureError> = ui_transaction.signatures.iter()
1340 .map(|s| Signature::from_str(s))
1341 .collect();
1342 let signatures = match signatures {
1343 Ok(signatures) => signatures,
1344 Err(error) => return Err(DecodeError::ParseSignatureFailed(error)),
1345 };
1346 Ok(Transaction {
1347 signatures,
1348 message,
1349 })
1350 }
1351 EncodedTransaction::Accounts(_) => {
1352 Err(DecodeError::UnsupportedEncoding)
1353 }
1354 }
1355 }
1356}
1357
1358
1359
1360pub trait DecodableWithMeta {
1361 type Encoded;
1362 type Decoded;
1363 fn decode_with_meta(
1364 encoded: Self::Encoded,
1365 encoding: UiTransactionEncoding,
1366 version: Option<TransactionVersion>
1367 ) -> Result<Self::Decoded, DecodeError>;
1369 fn json_decode(encoded: Self::Encoded, version: Option<TransactionVersion>) -> Result<Self::Decoded, DecodeError>;
1371}
1372
1373impl DecodableWithMeta for VersionedTransaction {
1374 type Encoded = EncodedTransaction;
1375 type Decoded = VersionedTransaction;
1376
1377 fn decode_with_meta(
1378 encoded: Self::Encoded,
1379 decoding: UiTransactionEncoding,
1380 version: Option<TransactionVersion>
1381 ) -> Result<Self::Decoded, DecodeError> {
1382 match decoding {
1383 UiTransactionEncoding::Binary | UiTransactionEncoding::Base58 => {
1384 if let EncodedTransaction::LegacyBinary(encoded_string) = encoded {
1385 let decoded_bytes = bs58::decode(encoded_string).into_vec().unwrap();
1386 let decoded: Self::Decoded =
1387 bincode::deserialize(&decoded_bytes).map_err(|_| DecodeError::DeserializeFailed)?;
1388 Ok(decoded)
1389 } else {
1390 Err(DecodeError::UnsupportedEncoding)
1391 }
1392 }
1393 UiTransactionEncoding::Base64 => {
1394 if let EncodedTransaction::Binary(encoded_string, _) = encoded {
1395 let decoded_bytes = base64::decode(encoded_string).unwrap();
1396 let decoded: Self::Decoded =
1397 bincode::deserialize(&decoded_bytes).map_err(|_| DecodeError::DeserializeFailed)?;
1398 Ok(decoded)
1399 } else {
1400 Err(DecodeError::UnsupportedEncoding)
1401 }
1402 }
1403 UiTransactionEncoding::Json => Self::json_decode(encoded, version),
1404 UiTransactionEncoding::JsonParsed => Err(DecodeError::UnsupportedEncoding),
1405 }
1406 }
1407
1408 fn json_decode(encoded: Self::Encoded, version: Option<TransactionVersion>) -> Result<Self::Decoded, DecodeError> {
1464 if let EncodedTransaction::Json(ui_transaction) = encoded {
1465 let signatures = ui_transaction
1466 .signatures
1467 .iter()
1468 .map(|s| s.parse::<Signature>())
1469 .collect::<Result<Vec<_>, _>>()
1470 .map_err(|err| DecodeError::ParseSignatureFailed(err))?;
1471
1472 let message = match ui_transaction.message {
1473 UiMessage::Raw(_) => {
1474 match version {
1475 Some(TransactionVersion::Number(0)) => {
1476 let v0_message = v0::Message::json_decode(ui_transaction.message, version)?;
1478 VersionedMessage::V0(v0_message)
1479 }
1480 Some(TransactionVersion::Legacy(_)) | None => {
1481 let legacy_message = Message::decode(&ui_transaction.message)?;
1483 VersionedMessage::Legacy(legacy_message)
1484 }
1485 _ => {
1487 return Err(DecodeError::UnsupportedVersion);
1489 }
1490 }
1491 }
1492 UiMessage::Parsed(_) => {
1493 return Err(DecodeError::UnsupportedEncoding);
1494 }
1495 };
1496
1497 Ok(Self {
1498 signatures,
1499 message,
1500 })
1501 } else {
1502 Err(DecodeError::UnsupportedEncoding)
1503 }
1504 }
1505
1506 }
1527
1528
1529impl TransactionWithStatusMeta {
1530 pub fn encode(
1531 self,
1532 encoding: UiTransactionEncoding,
1533 max_supported_transaction_version: Option<u8>,
1534 show_rewards: bool,
1535 ) -> Result<EncodedTransactionWithStatusMeta, EncodeError> {
1536 match self {
1537 Self::MissingMetadata(ref transaction) => Ok(EncodedTransactionWithStatusMeta {
1538 version: None,
1539 transaction: transaction.encode(encoding),
1540 meta: None,
1541 }),
1542 Self::Complete(tx_with_meta) => {
1543 tx_with_meta.encode(encoding, max_supported_transaction_version, show_rewards)
1544 }
1545 }
1546 }
1547
1548 pub fn decode(
1549 encoded: EncodedTransactionWithStatusMeta,
1550 encoding: UiTransactionEncoding,
1551 ) -> Result<Self, DecodeError> {
1552 match encoded.meta {
1553 Some(_) => {
1554 let complete = VersionedTransactionWithStatusMeta::decode(encoded, encoding )?;
1555 Ok(Self::Complete(complete))
1556 },
1557 None => {
1558 let transaction = Transaction::decode(&encoded.transaction)?;
1559 Ok(Self::MissingMetadata(transaction))
1560 }
1561 }
1562 }
1563
1564 pub fn transaction_signature(&self) -> &Signature {
1590 match self {
1591 Self::MissingMetadata(transaction) => &transaction.signatures[0],
1592 Self::Complete(VersionedTransactionWithStatusMeta { transaction, .. }) => {
1593 &transaction.signatures[0]
1594 }
1595 }
1596 }
1597
1598 fn build_json_accounts(
1599 self,
1600 max_supported_transaction_version: Option<u8>,
1601 show_rewards: bool,
1602 ) -> Result<EncodedTransactionWithStatusMeta, EncodeError> {
1603 match self {
1604 Self::MissingMetadata(ref transaction) => Ok(EncodedTransactionWithStatusMeta {
1605 version: None,
1606 transaction: transaction.build_json_accounts(),
1607 meta: None,
1608 }),
1609 Self::Complete(tx_with_meta) => {
1610 tx_with_meta.build_json_accounts(max_supported_transaction_version, show_rewards)
1611 }
1612 }
1613 }
1614}
1615
1616
1617impl TryFrom<UiTransactionStatusMeta> for TransactionStatusMeta {
1643 type Error = ConversionError;
1644
1645 fn try_from(meta: UiTransactionStatusMeta) -> Result<Self, Self::Error> {
1646 let inner_instructions: Option<Vec<InnerInstructions>> = match meta.inner_instructions {
1647 OptionSerializer::Some(ui_inner_instructions) => {
1648 let inner_instructions_result: Result<Vec<_>, _> = ui_inner_instructions
1649 .into_iter()
1650 .map(|ui_inner_instruction| InnerInstructions::try_from(ui_inner_instruction))
1651 .collect();
1652
1653 match inner_instructions_result {
1654 Ok(inner_instructions) => Some(inner_instructions),
1655 Err(e) => return Err(e),
1656 }
1657 }
1658 _ => None,
1659 };
1660
1661 let pre_token_balances: Option<Vec<TransactionTokenBalance>> = match meta.pre_token_balances {
1662 OptionSerializer::Some(ui_pre_token_balances) => {
1663 let pre_token_balances: Vec<_> = ui_pre_token_balances
1664 .into_iter()
1665 .map(TransactionTokenBalance::from)
1666 .collect();
1667
1668 Some(pre_token_balances)
1669 }
1670 _ => None,
1671 };
1672
1673 let post_token_balances: Option<Vec<TransactionTokenBalance>> = match meta.post_token_balances {
1674 OptionSerializer::Some(ui_post_token_balances) => {
1675 let post_token_balances: Vec<_> = ui_post_token_balances
1676 .into_iter()
1677 .map(TransactionTokenBalance::from)
1678 .collect();
1679
1680 Some(post_token_balances)
1681 }
1682 _ => None,
1683 };
1684
1685 let return_data: Option<TransactionReturnData> = match meta.return_data {
1686 OptionSerializer::Some(ui_return_data) => {
1687 let return_data = TransactionReturnData::try_from(ui_return_data)?;
1688 Some(return_data)
1689 }
1690 _ => None,
1691 };
1692
1693 let loaded_addresses: LoadedAddresses = match &meta.loaded_addresses {
1699 OptionSerializer::Some(ui_loaded_addresses) => {
1700 match LoadedAddresses::try_from(ui_loaded_addresses) {
1701 Ok(loaded_addresses) => loaded_addresses,
1702 Err(_) => return Err(ConversionError::InvalidProgramId),
1703 }
1704 }
1709 _ => return Err(ConversionError::InvalidProgramId),
1710 };
1711
1712 let compute_units_consumed: Option<u64> = match meta.compute_units_consumed {
1713 OptionSerializer::Some(cuc) => Some(cuc),
1714 _ => None,
1715 };
1716
1717 Ok(Self {
1718 status: meta.status,
1719 fee: meta.fee,
1720 pre_balances: meta.pre_balances,
1721 post_balances: meta.post_balances,
1722 inner_instructions,
1723 log_messages: match meta.log_messages {
1724 OptionSerializer::Some(logs) => Some(logs),
1725 _ => None,
1726 },
1727 pre_token_balances,
1728 post_token_balances,
1729 rewards: match meta.rewards {
1730 OptionSerializer::Some(rewards) => Some(rewards),
1731 _ => None,
1732 },
1733 loaded_addresses,
1734 return_data,
1735 compute_units_consumed,
1736 })
1737 }
1738}
1739
1740
1741
1742impl TryFrom<UiInnerInstructions> for InnerInstructions {
1759 type Error = ConversionError;
1760
1761 fn try_from(ui_inner_instructions: UiInnerInstructions) -> Result<Self, Self::Error> {
1762 let instructions_result: Result<Vec<_>, _> = ui_inner_instructions
1763 .instructions
1764 .into_iter()
1765 .map(|ix| match ix {
1766 UiInstruction::Compiled(ui_compiled) => Ok(InnerInstruction::from(ui_compiled)),
1768 _ => Err(ConversionError::UnsupportedInstructionFormat),
1769 })
1770 .collect();
1771
1772 match instructions_result {
1773 Ok(instructions) => Ok(Self {
1774 index: ui_inner_instructions.index,
1775 instructions,
1776 }),
1777 Err(e) => Err(e),
1778 }
1779 }
1780}
1781
1782impl From<UiCompiledInstruction> for InnerInstruction {
1783 fn from(ui_compiled_instruction: UiCompiledInstruction) -> Self {
1784 Self {
1785 instruction: CompiledInstruction::from(ui_compiled_instruction.clone()), stack_height: ui_compiled_instruction.stack_height,
1787 }
1788 }
1789}
1790
1791impl From<UiCompiledInstruction> for CompiledInstruction {
1792 fn from(ui_compiled_instruction: UiCompiledInstruction) -> Self {
1793 Self {
1794 program_id_index: ui_compiled_instruction.program_id_index,
1795 accounts: ui_compiled_instruction.accounts,
1796 data: bs58::decode(ui_compiled_instruction.data).into_vec().unwrap(),
1797 }
1798 }
1799}
1800
1801impl From<UiTransactionTokenBalance> for TransactionTokenBalance {
1802 fn from(token_balance: UiTransactionTokenBalance) -> Self {
1803 Self {
1804 account_index: token_balance.account_index,
1805 mint: token_balance.mint,
1806 ui_token_amount: token_balance.ui_token_amount,
1807 owner: match token_balance.owner {
1808 OptionSerializer::Some(owner) => owner,
1809 _ => String::new(),
1810 },
1811 program_id: match token_balance.program_id {
1812 OptionSerializer::Some(program_id) => program_id,
1813 _ => String::new(),
1814 },
1815 }
1816 }
1817}
1818
1819#[derive(Debug)]
1820pub enum ConversionError {
1821 InvalidProgramId,
1822 InvalidData,
1823 UnsupportedInstructionFormat,
1824}
1825
1826impl fmt::Display for ConversionError {
1827 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1828 match self {
1829 Self::InvalidProgramId => write!(f, "Invalid program id"),
1830 Self::InvalidData => write!(f, "Invalid data"),
1831 Self::UnsupportedInstructionFormat => write!(f, "Cannot convert from UiInstruction::Parsed to CompiledInstruction"),
1832 }
1833 }
1834}
1835
1836impl Error for ConversionError {} impl TryFrom<UiTransactionReturnData> for TransactionReturnData {
1839 type Error = ConversionError;
1840
1841 fn try_from(ui_return_data: UiTransactionReturnData) -> Result<Self, Self::Error> {
1842 let program_id = Pubkey::from_str(&ui_return_data.program_id)
1843 .map_err(|_| ConversionError::InvalidProgramId)?;
1844
1845 let data = base64::decode(&ui_return_data.data.0)
1846 .map_err(|_| ConversionError::InvalidData)?;
1847
1848 Ok(Self { program_id, data })
1849 }
1850}
1851
1852impl TryFrom<&UiLoadedAddresses> for LoadedAddresses {
1891 type Error = ParsePubkeyError;
1892
1893 fn try_from(ui_loaded_addresses: &UiLoadedAddresses) -> Result<Self, Self::Error> {
1894 let writable: Result<Vec<Pubkey>, _> = ui_loaded_addresses
1895 .writable
1896 .iter()
1897 .map(|s| Pubkey::from_str(s))
1898 .collect();
1899
1900 let readonly: Result<Vec<Pubkey>, _> = ui_loaded_addresses
1901 .readonly
1902 .iter()
1903 .map(|s| Pubkey::from_str(s))
1904 .collect();
1905
1906 Ok(Self {
1907 writable: writable?,
1908 readonly: readonly?,
1909 })
1910 }
1911}
1912
1913
1914
1915
1916impl TryFrom<ConfirmedBlock> for VersionedConfirmedBlock {
1917 type Error = ConvertBlockError;
1918
1919 fn try_from(block: ConfirmedBlock) -> Result<Self, Self::Error> {
1920 let expected_transaction_count = block.transactions.len();
1921
1922 let txs: Vec<_> = block
1923 .transactions
1924 .into_iter()
1925 .filter_map(|tx| match tx {
1926 TransactionWithStatusMeta::MissingMetadata(_) => None,
1927 TransactionWithStatusMeta::Complete(tx) => Some(tx),
1928 })
1929 .collect();
1930
1931 if txs.len() != expected_transaction_count {
1932 return Err(ConvertBlockError::TransactionsMissing(
1933 expected_transaction_count,
1934 txs.len(),
1935 ));
1936 }
1937
1938 Ok(Self {
1939 previous_blockhash: block.previous_blockhash,
1940 blockhash: block.blockhash,
1941 parent_slot: block.parent_slot,
1942 transactions: txs,
1943 rewards: block.rewards,
1944 block_time: block.block_time,
1945 block_height: block.block_height,
1946 })
1947 }
1948}
1949
1950impl ConfirmedBlock {
1951 pub fn encode_with_options(
1952 self,
1953 encoding: UiTransactionEncoding,
1954 options: BlockEncodingOptions,
1955 ) -> Result<UiConfirmedBlock, EncodeError> {
1956 let (transactions, signatures) = match options.transaction_details {
1957 TransactionDetails::Full => (
1958 Some(
1959 self.transactions
1960 .into_iter()
1961 .map(|tx_with_meta| {
1962 tx_with_meta.encode(
1963 encoding,
1964 options.max_supported_transaction_version,
1965 options.show_rewards,
1966 )
1967 })
1968 .collect::<Result<Vec<_>, _>>()?,
1969 ),
1970 None,
1971 ),
1972 TransactionDetails::Signatures => (
1973 None,
1974 Some(
1975 self.transactions
1976 .into_iter()
1977 .map(|tx_with_meta| tx_with_meta.transaction_signature().to_string())
1978 .collect(),
1979 ),
1980 ),
1981 TransactionDetails::None => (None, None),
1982 TransactionDetails::Accounts => (
1983 Some(
1984 self.transactions
1985 .into_iter()
1986 .map(|tx_with_meta| {
1987 tx_with_meta.build_json_accounts(
1988 options.max_supported_transaction_version,
1989 options.show_rewards,
1990 )
1991 })
1992 .collect::<Result<Vec<_>, _>>()?,
1993 ),
1994 None,
1995 ),
1996 };
1997 Ok(UiConfirmedBlock {
1998 previous_blockhash: self.previous_blockhash,
1999 blockhash: self.blockhash,
2000 parent_slot: self.parent_slot,
2001 transactions,
2002 signatures,
2003 rewards: if options.show_rewards {
2004 Some(self.rewards)
2005 } else {
2006 None
2007 },
2008 block_time: self.block_time,
2009 block_height: self.block_height,
2010 })
2011 }
2012
2013 pub fn decode_with_options(
2014 ui_confirmed_block: UiConfirmedBlock,
2015 encoding: UiTransactionEncoding,
2016 options: BlockEncodingOptions,
2017 ) -> Result<Self, DecodeError> {
2018 let transactions = match options.transaction_details {
2019 TransactionDetails::Full => {
2020 let transactions = ui_confirmed_block
2021 .transactions
2022 .ok_or(DecodeError::InvalidEncoding)?
2023 .into_iter()
2024 .map(|encoded_tx_with_meta| {
2025 TransactionWithStatusMeta::decode(encoded_tx_with_meta, encoding)
2026 })
2027 .collect::<Result<Vec<_>, _>>()?;
2028 transactions
2029 }
2030 TransactionDetails::Signatures => {
2031 let signatures = ui_confirmed_block
2032 .signatures
2033 .ok_or(DecodeError::InvalidEncoding)?;
2034 return Err(DecodeError::NotImplemented);
2036 }
2037 TransactionDetails::None => Vec::new(),
2038 TransactionDetails::Accounts => {
2039 let transactions = ui_confirmed_block
2040 .transactions
2041 .ok_or(DecodeError::InvalidEncoding)?
2042 .into_iter()
2043 .map(|encoded_tx_with_meta| {
2044 TransactionWithStatusMeta::decode(encoded_tx_with_meta, encoding)
2045 })
2046 .collect::<Result<Vec<_>, _>>()?;
2047 transactions
2048 }
2049 };
2050
2051 Ok(ConfirmedBlock {
2052 previous_blockhash: ui_confirmed_block.previous_blockhash,
2053 blockhash: ui_confirmed_block.blockhash,
2054 parent_slot: ui_confirmed_block.parent_slot,
2055 transactions,
2056 rewards: ui_confirmed_block
2057 .rewards
2058 .unwrap_or_default(),
2059 block_time: ui_confirmed_block.block_time,
2060 block_height: ui_confirmed_block.block_height,
2061 })
2062 }
2063}
2064
2065impl From<EncodedConfirmedBlock> for UiConfirmedBlock {
2066 fn from(block: EncodedConfirmedBlock) -> Self {
2067 Self {
2068 previous_blockhash: block.previous_blockhash,
2069 blockhash: block.blockhash,
2070 parent_slot: block.parent_slot,
2071 transactions: Some(block.transactions),
2072 signatures: None, rewards: Some(block.rewards),
2074 block_time: block.block_time,
2075 block_height: block.block_height,
2076 }
2077 }
2078}
2079
2080impl From<VersionedTransactionWithStatusMeta> for TransactionWithStatusMeta {
2081 fn from(item: VersionedTransactionWithStatusMeta) -> Self {
2082 TransactionWithStatusMeta::Complete(item)
2083 }
2084}