diff --git a/Cargo.lock b/Cargo.lock index 2b5c70dc67403d..f1a8bb9fb0a7ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7440,6 +7440,7 @@ dependencies = [ "solana-transaction", "solana-transaction-error", "solana-transaction-status", + "solana-transaction-status-client-types", "solana-udp-client", "solana-version", "solana-vote-program", @@ -7505,6 +7506,7 @@ dependencies = [ "solana-transaction-context", "solana-transaction-error", "solana-transaction-status", + "solana-transaction-status-client-types", "solana-vote-program", "spl-memo", ] @@ -7550,6 +7552,7 @@ dependencies = [ "solana-tpu-client", "solana-transaction", "solana-transaction-error", + "solana-transaction-status-client-types", "solana-udp-client", "thiserror 2.0.12", "tokio", @@ -11386,12 +11389,15 @@ dependencies = [ "serde_json", "solana-account-decoder-client-types", "solana-commitment-config", + "solana-instruction", "solana-message", + "solana-pubkey", "solana-reward-info", "solana-signature", "solana-transaction", "solana-transaction-context", "solana-transaction-error", + "test-case", "thiserror 2.0.12", ] diff --git a/cli-output/Cargo.toml b/cli-output/Cargo.toml index 8af951f23c92b9..f35df3c5e3cdb8 100644 --- a/cli-output/Cargo.toml +++ b/cli-output/Cargo.toml @@ -46,6 +46,7 @@ solana-sysvar = { workspace = true } solana-transaction = { workspace = true, features = ["verify"] } solana-transaction-error = { workspace = true } solana-transaction-status = { workspace = true } +solana-transaction-status-client-types = { workspace = true } solana-vote-program = { workspace = true } spl-memo = { workspace = true, features = ["no-entrypoint"] } diff --git a/cli-output/src/cli_output.rs b/cli-output/src/cli_output.rs index 4aad5021051af7..a34af79a43d211 100644 --- a/cli-output/src/cli_output.rs +++ b/cli-output/src/cli_output.rs @@ -35,11 +35,11 @@ use { solana_stake_interface::state::{Authorized, Lockup}, solana_sysvar::stake_history::StakeHistoryEntry, solana_transaction::{versioned::VersionedTransaction, Transaction}, - solana_transaction_error::TransactionError, solana_transaction_status::{ EncodedConfirmedBlock, EncodedTransaction, TransactionConfirmationStatus, UiTransactionStatusMeta, }, + solana_transaction_status_client_types::UiTransactionError, solana_vote_program::{ authorized_voters::AuthorizedVoters, vote_state::{BlockTimestamp, LandedVote, MAX_EPOCH_CREDITS_HISTORY, MAX_LOCKOUT_HISTORY}, @@ -876,7 +876,7 @@ impl fmt::Display for CliHistorySignature { pub struct CliHistoryVerbose { pub slot: Slot, pub block_time: Option, - pub err: Option, + pub err: Option, pub confirmation_status: Option, pub memo: Option, } @@ -2925,7 +2925,7 @@ pub struct CliTransactionConfirmation { #[serde(skip_serializing)] pub get_transaction_error: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub err: Option, + pub err: Option, } impl QuietDisplay for CliTransactionConfirmation {} diff --git a/cli-output/src/display.rs b/cli-output/src/display.rs index 5613746c0fc1b4..4245d9cfecf613 100644 --- a/cli-output/src/display.rs +++ b/cli-output/src/display.rs @@ -15,10 +15,10 @@ use { solana_signature::Signature, solana_stake_interface as stake, solana_transaction::versioned::{TransactionVersion, VersionedTransaction}, - solana_transaction_error::TransactionError, solana_transaction_status::{ Rewards, UiReturnDataEncoding, UiTransactionReturnData, UiTransactionStatusMeta, }, + solana_transaction_status_client_types::UiTransactionError, spl_memo::{id as spl_memo_id, v1::id as spl_memo_v1_id}, std::{collections::HashMap, fmt, io, time::Duration}, }; @@ -541,7 +541,7 @@ fn write_rewards( fn write_status( w: &mut W, - transaction_status: &Result<(), TransactionError>, + transaction_status: &Result<(), UiTransactionError>, prefix: &str, ) -> io::Result<()> { writeln!( diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 65acf02be571e7..b1811dae7fd456 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -92,6 +92,7 @@ solana-tpu-client = { workspace = true, features = ["default"] } solana-transaction = "=2.2.3" solana-transaction-error = "=2.2.1" solana-transaction-status = { workspace = true } +solana-transaction-status-client-types = { workspace = true } solana-udp-client = { workspace = true } solana-version = { workspace = true } solana-vote-program = { workspace = true } diff --git a/cli/src/wallet.rs b/cli/src/wallet.rs index dd7cd097cbe890..32e3658fb71972 100644 --- a/cli/src/wallet.rs +++ b/cli/src/wallet.rs @@ -793,7 +793,7 @@ pub fn process_confirm( confirmation_status: Some(transaction_status.confirmation_status()), transaction, get_transaction_error, - err: transaction_status.err.clone(), + err: transaction_status.err.clone().map(Into::into), } } else { CliTransactionConfirmation { diff --git a/client/Cargo.toml b/client/Cargo.toml index b9bf4f9604e658..32d683114863b0 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -48,6 +48,7 @@ solana-time-utils = { workspace = true } solana-tpu-client = { workspace = true, features = ["default"] } solana-transaction = { workspace = true } solana-transaction-error = { workspace = true } +solana-transaction-status-client-types = { workspace = true } solana-udp-client = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true, features = ["full"] } diff --git a/client/src/send_and_confirm_transactions_in_parallel.rs b/client/src/send_and_confirm_transactions_in_parallel.rs index 8e754b5d01b627..f6ea54655961ed 100644 --- a/client/src/send_and_confirm_transactions_in_parallel.rs +++ b/client/src/send_and_confirm_transactions_in_parallel.rs @@ -284,32 +284,32 @@ async fn send_transaction_with_rpc_fallback( ErrorKind::Io(_) | ErrorKind::Reqwest(_) => { // fall through on io error, we will retry the transaction } - ErrorKind::TransactionError(TransactionError::BlockhashNotFound) - | ErrorKind::RpcError(RpcError::RpcResponseError { - data: - RpcResponseErrorData::SendTransactionPreflightFailure( - RpcSimulateTransactionResult { - err: Some(TransactionError::BlockhashNotFound), - .. - }, - ), - .. - }) => { + ErrorKind::TransactionError(TransactionError::BlockhashNotFound) => { // fall through so that we will resend with another blockhash } - ErrorKind::TransactionError(transaction_error) - | ErrorKind::RpcError(RpcError::RpcResponseError { + ErrorKind::TransactionError(transaction_error) => { + // if we get other than blockhash not found error the transaction is invalid + context.error_map.insert(index, transaction_error.clone()); + } + ErrorKind::RpcError(RpcError::RpcResponseError { data: RpcResponseErrorData::SendTransactionPreflightFailure( RpcSimulateTransactionResult { - err: Some(transaction_error), + err: Some(ui_transaction_error), .. }, ), .. }) => { - // if we get other than blockhash not found error the transaction is invalid - context.error_map.insert(index, transaction_error.clone()); + match TransactionError::from(ui_transaction_error.clone()) { + TransactionError::BlockhashNotFound => { + // fall through so that we will resend with another blockhash + } + err => { + // if we get other than blockhash not found error the transaction is invalid + context.error_map.insert(index, err); + } + } } _ => { return Err(TpuSenderError::from(e)); diff --git a/ledger-tool/src/bigtable.rs b/ledger-tool/src/bigtable.rs index 4721cba29d9e89..3c872f5596b10c 100644 --- a/ledger-tool/src/bigtable.rs +++ b/ledger-tool/src/bigtable.rs @@ -519,7 +519,7 @@ async fn confirm( confirmation_status: Some(transaction_status.confirmation_status()), transaction, get_transaction_error, - err: transaction_status.err.clone(), + err: transaction_status.err.clone().map(Into::into), }; println!("{}", output_format.formatted_string(&cli_transaction)); Ok(()) diff --git a/programs/sbf/Cargo.lock b/programs/sbf/Cargo.lock index 97e0286611a8d8..a58e7a22f8825d 100644 --- a/programs/sbf/Cargo.lock +++ b/programs/sbf/Cargo.lock @@ -5829,6 +5829,7 @@ dependencies = [ "solana-transaction", "solana-transaction-error", "solana-transaction-status", + "solana-transaction-status-client-types", "solana-vote-program", "spl-memo", ] @@ -5872,6 +5873,7 @@ dependencies = [ "solana-tpu-client", "solana-transaction", "solana-transaction-error", + "solana-transaction-status-client-types", "solana-udp-client", "thiserror 2.0.12", "tokio", @@ -9541,7 +9543,9 @@ dependencies = [ "serde_json", "solana-account-decoder-client-types", "solana-commitment-config", + "solana-instruction", "solana-message", + "solana-pubkey", "solana-reward-info", "solana-signature", "solana-transaction", diff --git a/rpc-client-api/src/client_error.rs b/rpc-client-api/src/client_error.rs index fbe2fcb46b93c0..00c228471ea184 100644 --- a/rpc-client-api/src/client_error.rs +++ b/rpc-client-api/src/client_error.rs @@ -39,7 +39,7 @@ impl ErrorKind { }, ), .. - }) => Some(tx_err.clone()), + }) => Some(tx_err.clone().into()), Self::TransactionError(tx_err) => Some(tx_err.clone()), _ => None, } diff --git a/rpc-client-types/src/response.rs b/rpc-client-types/src/response.rs index 117c5be75976bd..ff96616e127b58 100644 --- a/rpc-client-types/src/response.rs +++ b/rpc-client-types/src/response.rs @@ -4,10 +4,10 @@ use { solana_clock::{Epoch, Slot, UnixTimestamp}, solana_fee_calculator::{FeeCalculator, FeeRateGovernor}, solana_inflation::Inflation, - solana_transaction_error::{TransactionError, TransactionResult as Result}, + solana_transaction_error::TransactionResult as Result, solana_transaction_status_client_types::{ ConfirmedTransactionStatusWithSignature, TransactionConfirmationStatus, UiConfirmedBlock, - UiInnerInstructions, UiTransactionReturnData, + UiInnerInstructions, UiTransactionError, UiTransactionReturnData, }, std::{collections::HashMap, fmt, net::SocketAddr, str::FromStr}, thiserror::Error, @@ -240,14 +240,14 @@ pub enum RpcSignatureResult { #[serde(rename_all = "camelCase")] pub struct RpcLogsResponse { pub signature: String, // Signature as base58 string - pub err: Option, + pub err: Option, pub logs: Vec, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct ProcessedSignatureResult { - pub err: Option, + pub err: Option, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] @@ -397,7 +397,7 @@ pub struct RpcSignatureConfirmation { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct RpcSimulateTransactionResult { - pub err: Option, + pub err: Option, pub logs: Option>, pub accounts: Option>>, pub units_consumed: Option, @@ -452,7 +452,7 @@ pub struct RpcTokenAccountBalance { pub struct RpcConfirmedTransactionStatusWithSignature { pub signature: String, pub slot: Slot, - pub err: Option, + pub err: Option, pub memo: Option, pub block_time: Option, pub confirmation_status: Option, @@ -507,7 +507,7 @@ impl From for RpcConfirmedTransactionSt Self { signature: signature.to_string(), slot, - err, + err: err.map(Into::into), memo, block_time, confirmation_status: None, diff --git a/rpc/src/rpc.rs b/rpc/src/rpc.rs index b1193802da6b60..1df8307ebd9bc3 100644 --- a/rpc/src/rpc.rs +++ b/rpc/src/rpc.rs @@ -3924,7 +3924,7 @@ pub mod rpc_full { return Err(RpcCustomError::SendTransactionPreflightFailure { message: format!("Transaction simulation failed: {err}"), result: RpcSimulateTransactionResult { - err: Some(err), + err: Some(err.into()), logs: Some(logs), accounts: None, units_consumed: Some(units_consumed), @@ -4073,7 +4073,7 @@ pub mod rpc_full { Ok(new_response( bank, RpcSimulateTransactionResult { - err: result.err(), + err: result.err().map(Into::into), logs: Some(logs), accounts, units_consumed: Some(units_consumed), @@ -7276,17 +7276,17 @@ pub mod tests { let meta = meta.unwrap(); assert_eq!( meta.err, - Some(TransactionError::InstructionError( - 0, - InstructionError::Custom(1) - )) + Some( + TransactionError::InstructionError(0, InstructionError::Custom(1)) + .into() + ) ); assert_eq!( meta.status, - Err(TransactionError::InstructionError( - 0, - InstructionError::Custom(1) - )) + Err( + TransactionError::InstructionError(0, InstructionError::Custom(1)) + .into() + ), ); } else { assert_eq!(meta, None); @@ -7322,17 +7322,17 @@ pub mod tests { let meta = meta.unwrap(); assert_eq!( meta.err, - Some(TransactionError::InstructionError( - 0, - InstructionError::Custom(1) - )) + Some( + TransactionError::InstructionError(0, InstructionError::Custom(1)) + .into() + ) ); assert_eq!( meta.status, - Err(TransactionError::InstructionError( - 0, - InstructionError::Custom(1) - )) + Err( + TransactionError::InstructionError(0, InstructionError::Custom(1)) + .into() + ), ); } else { assert_eq!(meta, None); @@ -8533,7 +8533,7 @@ pub mod tests { }, ]); } - assert_eq!(result["result"]["value"]["data"], expected_value); + assert_eq!(result["result"]["value"]["data"], expected_value,); // Test Mint let req = format!( diff --git a/rpc/src/rpc_subscriptions.rs b/rpc/src/rpc_subscriptions.rs index 76a44d9e94d28d..6bc6aa51d2f3be 100644 --- a/rpc/src/rpc_subscriptions.rs +++ b/rpc/src/rpc_subscriptions.rs @@ -402,7 +402,9 @@ fn filter_signature_result( ) -> (Option, Slot) { ( result.map(|result| { - RpcSignatureResult::ProcessedSignature(ProcessedSignatureResult { err: result.err() }) + RpcSignatureResult::ProcessedSignature(ProcessedSignatureResult { + err: result.err().map(Into::into), + }) }), last_notified_slot, ) @@ -446,7 +448,7 @@ fn filter_logs_results( ) -> (impl Iterator, Slot) { let responses = logs.into_iter().flatten().map(|log| RpcLogsResponse { signature: log.signature.to_string(), - err: log.result.err(), + err: log.result.err().map(Into::into), logs: log.log_messages, }); (responses, last_notified_slot) diff --git a/svm/examples/Cargo.lock b/svm/examples/Cargo.lock index ea5cde2fab4b75..fb695481185dc8 100644 --- a/svm/examples/Cargo.lock +++ b/svm/examples/Cargo.lock @@ -5676,6 +5676,7 @@ dependencies = [ "solana-transaction", "solana-transaction-error", "solana-transaction-status", + "solana-transaction-status-client-types", "solana-vote-program", "spl-memo", ] @@ -5719,6 +5720,7 @@ dependencies = [ "solana-tpu-client", "solana-transaction", "solana-transaction-error", + "solana-transaction-status-client-types", "solana-udp-client", "thiserror 2.0.12", "tokio", @@ -8641,7 +8643,9 @@ dependencies = [ "serde_json", "solana-account-decoder-client-types", "solana-commitment-config", + "solana-instruction", "solana-message", + "solana-pubkey", "solana-reward-info", "solana-signature", "solana-transaction", diff --git a/svm/examples/json-rpc/server/src/rpc_process.rs b/svm/examples/json-rpc/server/src/rpc_process.rs index 00b266891f31e7..9b9c19905197a8 100644 --- a/svm/examples/json-rpc/server/src/rpc_process.rs +++ b/svm/examples/json-rpc/server/src/rpc_process.rs @@ -757,7 +757,7 @@ pub mod rpc { Ok(new_response( 0, RpcSimulateTransactionResult { - err: result.err(), + err: result.err().map(Into::into), logs: Some(logs), accounts, units_consumed: Some(units_consumed), diff --git a/transaction-status-client-types/Cargo.toml b/transaction-status-client-types/Cargo.toml index d67ad700d692c4..09b85aba647754 100644 --- a/transaction-status-client-types/Cargo.toml +++ b/transaction-status-client-types/Cargo.toml @@ -21,10 +21,15 @@ serde_derive = { workspace = true } serde_json = { workspace = true } solana-account-decoder-client-types = { workspace = true } solana-commitment-config = { workspace = true } +solana-instruction = { workspace = true } solana-message = { workspace = true } +solana-pubkey = { workspace = true } solana-reward-info = { workspace = true, features = ["serde"] } solana-signature = { workspace = true, default-features = false } solana-transaction = { workspace = true, features = ["serde"] } solana-transaction-context = { workspace = true } solana-transaction-error = { workspace = true, features = ["serde"] } thiserror = { workspace = true } + +[dev-dependencies] +test-case = { workspace = true } diff --git a/transaction-status-client-types/src/lib.rs b/transaction-status-client-types/src/lib.rs index 6036f9c8fc37df..2daca8be5289df 100644 --- a/transaction-status-client-types/src/lib.rs +++ b/transaction-status-client-types/src/lib.rs @@ -3,10 +3,16 @@ use { crate::option_serializer::OptionSerializer, base64::{prelude::BASE64_STANDARD, Engine}, core::fmt, + serde::{ + de::{self, Deserialize as DeserializeTrait, Error as DeserializeError}, + ser::{Serialize as SerializeTrait, SerializeTupleVariant}, + Deserializer, + }, serde_derive::{Deserialize, Serialize}, - serde_json::Value, + serde_json::{from_value, Value}, solana_account_decoder_client_types::token::UiTokenAmount, solana_commitment_config::CommitmentConfig, + solana_instruction::error::InstructionError, solana_message::{ compiled_instruction::CompiledInstruction, v0::{LoadedAddresses, MessageAddressTableLookup}, @@ -226,12 +232,94 @@ impl From<&MessageAddressTableLookup> for UiAddressTableLookup { } } +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct UiTransactionError(TransactionError); + +impl fmt::Display for UiTransactionError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl std::error::Error for UiTransactionError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + Some(&self.0) + } +} + +impl From for UiTransactionError { + fn from(value: TransactionError) -> Self { + UiTransactionError(value) + } +} + +impl From for TransactionError { + fn from(value: UiTransactionError) -> Self { + value.0 + } +} + +impl SerializeTrait for UiTransactionError { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match &self.0 { + TransactionError::InstructionError(outer_instruction_index, err) => { + let mut state = serializer.serialize_tuple_variant( + "TransactionError", + 8, + "InstructionError", + 2, + )?; + state.serialize_field(outer_instruction_index)?; + state.serialize_field(err)?; + state.end() + } + err => TransactionError::serialize(err, serializer), + } + } +} + +impl<'de> DeserializeTrait<'de> for UiTransactionError { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let value = serde_json::Value::deserialize(deserializer)?; + if let Some(obj) = value.as_object() { + if let Some(arr) = obj.get("InstructionError").and_then(|v| v.as_array()) { + let outer_instruction_index: u8 = arr + .first() + .ok_or_else(|| { + DeserializeError::invalid_length(0, &"Expected the first element to exist") + })? + .as_u64() + .ok_or_else(|| { + DeserializeError::custom("Expected the first element to be a u64") + })? as u8; + let instruction_error = arr.get(1).ok_or_else(|| { + DeserializeError::invalid_length(1, &"Expected there to be at least 2 elements") + })?; + let err: InstructionError = from_value(instruction_error.clone()) + .map_err(|e| DeserializeError::custom(e.to_string()))?; + return Ok(UiTransactionError(TransactionError::InstructionError( + outer_instruction_index, + err, + ))); + } + } + let err = TransactionError::deserialize(value).map_err(de::Error::custom)?; + Ok(UiTransactionError(err)) + } +} + /// A duplicate representation of TransactionStatusMeta with `err` field #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct UiTransactionStatusMeta { - pub err: Option, - pub status: TransactionResult<()>, // This field is deprecated. See https://github.com/solana-labs/solana/issues/9302 + pub err: Option, + pub status: Result<(), UiTransactionError>, // This field is deprecated. See https://github.com/solana-labs/solana/issues/9302 pub fee: u64, pub pre_balances: Vec, pub post_balances: Vec, @@ -285,8 +373,8 @@ pub struct UiTransactionStatusMeta { impl From for UiTransactionStatusMeta { fn from(meta: TransactionStatusMeta) -> Self { Self { - err: meta.status.clone().err(), - status: meta.status, + err: meta.status.clone().map_err(Into::into).err(), + status: meta.status.map_err(Into::into), fee: meta.fee, pre_balances: meta.pre_balances, post_balances: meta.post_balances, @@ -660,7 +748,11 @@ impl TransactionStatus { #[cfg(test)] mod test { - use {super::*, serde_json::json}; + use { + super::*, + serde_json::{from_value, json, to_value}, + test_case::test_case, + }; #[test] fn test_decode_invalid_transaction() { @@ -812,4 +904,53 @@ mod test { }"; test_serde::(json_input, expected_json_output); } + + #[test_case( + TransactionError::InstructionError (42, InstructionError::Custom(0xdeadbeef)), + json!({"InstructionError": [ + 42, + { "Custom": 0xdeadbeef_u32 }, + ]}); + "`InstructionError`" + )] + #[test_case(TransactionError::InsufficientFundsForRent { + account_index: 42, + }, json!({"InsufficientFundsForRent": { + "account_index": 42, + }}); "Struct variant error")] + #[test_case(TransactionError::DuplicateInstruction(42), json!({ "DuplicateInstruction": 42 }); "Single-value tuple variant error")] + #[test_case(TransactionError::InsufficientFundsForFee, json!("InsufficientFundsForFee"); "Named variant error")] + fn test_serialize_ui_transaction_error( + transaction_error: TransactionError, + expected_serialization: Value, + ) { + let actual_serialization = to_value(UiTransactionError(transaction_error)) + .expect("Failed to serialize `UiTransactionError"); + assert_eq!(actual_serialization, expected_serialization); + } + + #[test_case( + TransactionError::InstructionError (42, InstructionError::Custom(0xdeadbeef)), + json!({"InstructionError": [ + 42, + { "Custom": 0xdeadbeef_u32 }, + ]}); + "`InstructionError`" + )] + #[test_case(TransactionError::InsufficientFundsForRent { + account_index: 42, + }, json!({"InsufficientFundsForRent": { + "account_index": 42, + }}); "Struct variant error")] + #[test_case(TransactionError::DuplicateInstruction(42), json!({ "DuplicateInstruction": 42 }); "Single-value tuple variant error")] + #[test_case(TransactionError::InsufficientFundsForFee, json!("InsufficientFundsForFee"); "Named variant error")] + fn test_deserialize_ui_transaction_error( + expected_transaction_error: TransactionError, + serialized_value: Value, + ) { + let UiTransactionError(actual_transaction_error) = + from_value::(serialized_value) + .expect("Failed to deserialize `UiTransactionError"); + assert_eq!(actual_transaction_error, expected_transaction_error); + } } diff --git a/transaction-status/src/lib.rs b/transaction-status/src/lib.rs index 449fe355f6f6d4..9776a8a9a915a3 100644 --- a/transaction-status/src/lib.rs +++ b/transaction-status/src/lib.rs @@ -163,8 +163,8 @@ fn build_simple_ui_transaction_status_meta( show_rewards: bool, ) -> UiTransactionStatusMeta { UiTransactionStatusMeta { - err: meta.status.clone().err(), - status: meta.status, + err: meta.status.clone().map_err(Into::into).err(), + status: meta.status.map_err(Into::into), fee: meta.fee, pre_balances: meta.pre_balances, post_balances: meta.post_balances, @@ -197,8 +197,8 @@ fn parse_ui_transaction_status_meta( ) -> UiTransactionStatusMeta { let account_keys = AccountKeys::new(static_keys, Some(&meta.loaded_addresses)); UiTransactionStatusMeta { - err: meta.status.clone().err(), - status: meta.status, + err: meta.status.clone().map_err(Into::into).err(), + status: meta.status.map_err(Into::into), fee: meta.fee, pre_balances: meta.pre_balances, post_balances: meta.post_balances,