//! Helper trait and functions to format Ethereum types.

use std::num::NonZeroU64;

use alloy_consensus::{
    BlockHeader, Eip658Value, Signed, Transaction as TxTrait, TxEip1559, TxEip2930,
    TxEip4844Variant, TxEip7702, TxEnvelope, TxLegacy, TxReceipt, Typed2718,
    transaction::TxHashRef,
};
use alloy_network::{
    AnyRpcBlock, AnyRpcHeader, AnyRpcTransaction, AnyTransactionReceipt, AnyTxEnvelope,
    BlockResponse, Network, ReceiptResponse, primitives::HeaderResponse,
};
use alloy_primitives::{
    Address, Bloom, Bytes, FixedBytes, I256, Signature, U8, U64, U256, Uint, hex,
};
use alloy_rpc_types::{
    AccessListItem, Block, BlockTransactions, Header, Log, Transaction, TransactionReceipt,
};
use alloy_serde::{OtherFields, WithOtherFields};
#[cfg(feature = "optimism")]
use op_alloy_consensus::{OpTxEnvelope, TxDeposit, TxPostExec};
use revm::context_interface::transaction::SignedAuthorization;
use serde::Deserialize;
use tempo_alloy::{
    primitives::{
        AASigned, TempoSignature, TempoTransaction, TempoTxEnvelope,
        transaction::{Call, PrimitiveSignature},
    },
    rpc::{TempoHeaderResponse, TempoTransactionReceipt},
};

/// length of the name column for pretty formatting `{:>20}{value}`
const NAME_COLUMN_LEN: usize = 20usize;

/// Helper trait to format Ethereum types.
///
/// # Examples
///
/// ```
/// use foundry_common_fmt::UIfmt;
///
/// let boolean: bool = true;
/// let string = boolean.pretty();
/// ```
pub trait UIfmt {
    /// Return a prettified string version of the value
    fn pretty(&self) -> String;
}

impl<T: UIfmt> UIfmt for &T {
    fn pretty(&self) -> String {
        (*self).pretty()
    }
}

impl<T: UIfmt> UIfmt for Option<T> {
    fn pretty(&self) -> String {
        if let Some(inner) = self { inner.pretty() } else { String::new() }
    }
}

impl<T: UIfmt> UIfmt for [T] {
    fn pretty(&self) -> String {
        if self.is_empty() {
            "[]".to_string()
        } else {
            let mut s = String::with_capacity(self.len() * 64);
            s.push_str("[\n");
            for item in self {
                for line in item.pretty().lines() {
                    s.push('\t');
                    s.push_str(line);
                    s.push('\n');
                }
            }
            s.push(']');
            s
        }
    }
}

impl UIfmt for String {
    fn pretty(&self) -> String {
        self.clone()
    }
}

impl UIfmt for u64 {
    fn pretty(&self) -> String {
        self.to_string()
    }
}

impl UIfmt for NonZeroU64 {
    fn pretty(&self) -> String {
        self.get().pretty()
    }
}

impl UIfmt for u128 {
    fn pretty(&self) -> String {
        self.to_string()
    }
}

impl UIfmt for bool {
    fn pretty(&self) -> String {
        self.to_string()
    }
}

impl<const BITS: usize, const LIMBS: usize> UIfmt for Uint<BITS, LIMBS> {
    fn pretty(&self) -> String {
        self.to_string()
    }
}

impl UIfmt for I256 {
    fn pretty(&self) -> String {
        self.to_string()
    }
}

impl UIfmt for Address {
    fn pretty(&self) -> String {
        self.to_string()
    }
}

impl UIfmt for Bloom {
    fn pretty(&self) -> String {
        self.to_string()
    }
}

impl UIfmt for Vec<u8> {
    fn pretty(&self) -> String {
        self[..].pretty()
    }
}

impl UIfmt for Bytes {
    fn pretty(&self) -> String {
        self[..].pretty()
    }
}

impl<const N: usize> UIfmt for [u8; N] {
    fn pretty(&self) -> String {
        self[..].pretty()
    }
}

impl<const N: usize> UIfmt for FixedBytes<N> {
    fn pretty(&self) -> String {
        self[..].pretty()
    }
}

impl UIfmt for [u8] {
    fn pretty(&self) -> String {
        hex::encode_prefixed(self)
    }
}

impl UIfmt for Eip658Value {
    fn pretty(&self) -> String {
        match self {
            Self::Eip658(status) => if *status { "1 (success)" } else { "0 (failed)" }.to_string(),
            Self::PostState(state) => state.pretty(),
        }
    }
}

impl UIfmt for Signature {
    fn pretty(&self) -> String {
        format!("[r: {}, s: {}, y_parity: {}]", self.r(), self.s(), self.v())
    }
}

/// Pretty-prints the common fields of any `TransactionReceipt<T>`.
fn pretty_receipt<T: TxReceipt<Log = Log>>(receipt: &TransactionReceipt<T>, tx_type: u8) -> String {
    let mut pretty = format!(
        "
blockHash            {}
blockNumber          {}
contractAddress      {}
cumulativeGasUsed    {}
effectiveGasPrice    {}
from                 {}
gasUsed              {}
logs                 {}
logsBloom            {}
root                 {}
status               {}
transactionHash      {}
transactionIndex     {}
type                 {}
blobGasPrice         {}
blobGasUsed          {}",
        receipt.block_hash.pretty(),
        receipt.block_number.pretty(),
        receipt.contract_address.pretty(),
        receipt.inner.cumulative_gas_used().pretty(),
        receipt.effective_gas_price.pretty(),
        receipt.from.pretty(),
        receipt.gas_used.pretty(),
        serde_json::to_string(receipt.inner.logs()).unwrap(),
        receipt.inner.bloom().pretty(),
        receipt.state_root().pretty(),
        receipt.inner.status_or_post_state().pretty(),
        receipt.transaction_hash.pretty(),
        receipt.transaction_index.pretty(),
        tx_type,
        receipt.blob_gas_price.pretty(),
        receipt.blob_gas_used.pretty()
    );

    if let Some(to) = receipt.to {
        pretty.push_str(&format!("\nto                   {}", to.pretty()));
    }

    pretty
}

impl UIfmt for TransactionReceipt {
    fn pretty(&self) -> String {
        pretty_receipt(self, self.transaction_type() as u8)
    }
}

impl UIfmt for AnyTransactionReceipt {
    fn pretty(&self) -> String {
        let mut pretty = pretty_receipt(&self.inner, self.inner.inner.r#type);
        pretty.push_str(&self.other.pretty());
        pretty
    }
}

impl UIfmt for Log {
    fn pretty(&self) -> String {
        format!(
            "
address: {}
blockHash: {}
blockNumber: {}
data: {}
logIndex: {}
removed: {}
topics: {}
transactionHash: {}
transactionIndex: {}",
            self.address().pretty(),
            self.block_hash.pretty(),
            self.block_number.pretty(),
            self.data().data.pretty(),
            self.log_index.pretty(),
            self.removed.pretty(),
            self.topics().pretty(),
            self.transaction_hash.pretty(),
            self.transaction_index.pretty(),
        )
    }
}

impl<T: UIfmt, H: HeaderResponse + UIfmtHeaderExt> UIfmt for Block<T, H> {
    fn pretty(&self) -> String {
        format!(
            "
{}
transactions:        {}",
            pretty_generic_header_response(&self.header),
            self.transactions.pretty()
        )
    }
}

impl<T: UIfmt> UIfmt for BlockTransactions<T> {
    fn pretty(&self) -> String {
        match self {
            Self::Hashes(hashes) => hashes.pretty(),
            Self::Full(transactions) => transactions.pretty(),
            Self::Uncle => String::new(),
        }
    }
}

impl UIfmt for OtherFields {
    fn pretty(&self) -> String {
        let mut s = String::with_capacity(self.len() * 30);
        if !self.is_empty() {
            s.push('\n');
        }
        for (key, value) in self {
            let val = EthValue::from(value.clone()).pretty();
            let offset = NAME_COLUMN_LEN.saturating_sub(key.len());
            s.push_str(key);
            s.extend(std::iter::repeat_n(' ', offset + 1));
            s.push_str(&val);
            s.push('\n');
        }
        s
    }
}

impl UIfmt for AccessListItem {
    fn pretty(&self) -> String {
        let mut s = String::with_capacity(42 + self.storage_keys.len() * 66);
        s.push_str(self.address.pretty().as_str());
        s.push_str(" => ");
        s.push_str(self.storage_keys.pretty().as_str());
        s
    }
}

impl UIfmt for TxLegacy {
    fn pretty(&self) -> String {
        format!(
            "
chainId              {}
nonce                {}
gasPrice             {}
gasLimit             {}
to                   {}
value                {}
input                {}",
            self.chain_id.pretty(),
            self.nonce.pretty(),
            self.gas_price.pretty(),
            self.gas_limit.pretty(),
            self.to().pretty(),
            self.value.pretty(),
            self.input.pretty(),
        )
    }
}

impl UIfmt for TxEip2930 {
    fn pretty(&self) -> String {
        format!(
            "
chainId              {}
nonce                {}
gasPrice             {}
gasLimit             {}
to                   {}
value                {}
accessList           {}
input                {}",
            self.chain_id.pretty(),
            self.nonce.pretty(),
            self.gas_price.pretty(),
            self.gas_limit.pretty(),
            self.to().pretty(),
            self.value.pretty(),
            self.access_list.pretty(),
            self.input.pretty(),
        )
    }
}

impl UIfmt for TxEip1559 {
    fn pretty(&self) -> String {
        format!(
            "
chainId              {}
nonce                {}
gasLimit             {}
maxFeePerGas         {}
maxPriorityFeePerGas {}
to                   {}
value                {}
accessList           {}
input                {}",
            self.chain_id.pretty(),
            self.nonce.pretty(),
            self.gas_limit.pretty(),
            self.max_fee_per_gas.pretty(),
            self.max_priority_fee_per_gas.pretty(),
            self.to().pretty(),
            self.value.pretty(),
            self.access_list.pretty(),
            self.input.pretty(),
        )
    }
}

impl UIfmt for TxEip4844Variant {
    fn pretty(&self) -> String {
        use alloy_consensus::TxEip4844;
        let tx: &TxEip4844 = match self {
            Self::TxEip4844(tx) => tx,
            Self::TxEip4844WithSidecar(tx) => tx.tx(),
        };
        format!(
            "
chainId              {}
nonce                {}
gasLimit             {}
maxFeePerGas         {}
maxPriorityFeePerGas {}
to                   {}
value                {}
accessList           {}
blobVersionedHashes  {}
maxFeePerBlobGas     {}
input                {}",
            tx.chain_id.pretty(),
            tx.nonce.pretty(),
            tx.gas_limit.pretty(),
            tx.max_fee_per_gas.pretty(),
            tx.max_priority_fee_per_gas.pretty(),
            tx.to.pretty(),
            tx.value.pretty(),
            tx.access_list.pretty(),
            tx.blob_versioned_hashes.pretty(),
            tx.max_fee_per_blob_gas.pretty(),
            tx.input.pretty(),
        )
    }
}

impl UIfmt for TxEip7702 {
    fn pretty(&self) -> String {
        format!(
            "
chainId              {}
nonce                {}
gasLimit             {}
maxFeePerGas         {}
maxPriorityFeePerGas {}
to                   {}
value                {}
accessList           {}
authorizationList    {}
input                {}",
            self.chain_id.pretty(),
            self.nonce.pretty(),
            self.gas_limit.pretty(),
            self.max_fee_per_gas.pretty(),
            self.max_priority_fee_per_gas.pretty(),
            self.to.pretty(),
            self.value.pretty(),
            self.access_list.pretty(),
            self.authorization_list.pretty(),
            self.input.pretty(),
        )
    }
}

#[cfg(feature = "optimism")]
impl UIfmt for TxDeposit {
    fn pretty(&self) -> String {
        format!(
            "
sourceHash           {}
from                 {}
to                   {}
mint                 {}
value                {}
gasLimit             {}
isSystemTransaction  {}
input                {}",
            self.source_hash.pretty(),
            self.from.pretty(),
            self.to().pretty(),
            self.mint.pretty(),
            self.value.pretty(),
            self.gas_limit.pretty(),
            self.is_system_transaction,
            self.input.pretty(),
        )
    }
}

#[cfg(feature = "optimism")]
impl UIfmt for TxPostExec {
    fn pretty(&self) -> String {
        format!(
            "
blockNumber          {}
gasRefundEntries     {:?}
input                {}",
            self.payload.block_number.pretty(),
            self.payload.gas_refund_entries,
            self.input.pretty(),
        )
    }
}

impl UIfmt for Call {
    fn pretty(&self) -> String {
        format!(
            "to: {}, value: {}, input: {}",
            self.to.into_to().pretty(),
            self.value.pretty(),
            self.input.pretty(),
        )
    }
}

impl UIfmt for TempoTransaction {
    fn pretty(&self) -> String {
        format!(
            "
chainId              {}
feeToken             {}
maxPriorityFeePerGas {}
maxFeePerGas         {}
gasLimit             {}
calls                {}
accessList           {}
nonceKey             {}
nonce                {}
feePayerSignature    {}
validBefore          {}
validAfter           {}",
            self.chain_id.pretty(),
            self.fee_token.pretty(),
            self.max_priority_fee_per_gas.pretty(),
            self.max_fee_per_gas.pretty(),
            self.gas_limit.pretty(),
            self.calls.pretty(),
            self.access_list.pretty(),
            self.nonce_key.pretty(),
            self.nonce.pretty(),
            self.fee_payer_signature.pretty(),
            self.valid_before.pretty(),
            self.valid_after.pretty(),
        )
    }
}

impl UIfmt for TempoSignature {
    fn pretty(&self) -> String {
        serde_json::to_string(self).unwrap_or_default()
    }
}

impl UIfmt for AASigned {
    fn pretty(&self) -> String {
        format!(
            "
hash                 {}
type                 {}
{}
tempoSignature       {}",
            self.hash().pretty(),
            self.tx().ty(),
            self.tx().pretty().trim_start(),
            self.signature().pretty(),
        )
    }
}

impl<T: UIfmt + Typed2718> UIfmt for Signed<T>
where
    Self: TxHashRef,
{
    fn pretty(&self) -> String {
        format!(
            "
hash                 {}
type                 {}
{}
r                    {}
s                    {}
yParity              {}",
            self.tx_hash().pretty(),
            self.ty(),
            self.tx().pretty().trim_start(),
            FixedBytes::from(self.signature().r()).pretty(),
            FixedBytes::from(self.signature().s()).pretty(),
            (if self.signature().v() { 1u64 } else { 0 }).pretty(),
        )
    }
}

impl UIfmt for TxEnvelope {
    fn pretty(&self) -> String {
        match self {
            Self::Legacy(tx) => tx.pretty(),
            Self::Eip2930(tx) => tx.pretty(),
            Self::Eip1559(tx) => tx.pretty(),
            Self::Eip4844(tx) => tx.pretty(),
            Self::Eip7702(tx) => tx.pretty(),
        }
    }
}

impl UIfmt for AnyTxEnvelope {
    fn pretty(&self) -> String {
        match self {
            Self::Ethereum(envelop) => envelop.pretty(),
            Self::Unknown(tx) => {
                format!(
                    "
hash                 {}
type               {:#x}
{}
                    ",
                    tx.hash.pretty(),
                    tx.ty(),
                    tx.inner.fields.pretty().trim_start(),
                )
            }
        }
    }
}

#[cfg(feature = "optimism")]
impl UIfmt for OpTxEnvelope {
    fn pretty(&self) -> String {
        match self {
            Self::Legacy(tx) => tx.pretty(),
            Self::Eip2930(tx) => tx.pretty(),
            Self::Eip1559(tx) => tx.pretty(),
            Self::Eip7702(tx) => tx.pretty(),
            Self::Deposit(tx) => tx.pretty(),
            Self::PostExec(tx) => tx.pretty(),
        }
    }
}

impl UIfmt for TempoTxEnvelope {
    fn pretty(&self) -> String {
        match self {
            Self::Legacy(tx) => tx.pretty(),
            Self::Eip2930(tx) => tx.pretty(),
            Self::Eip1559(tx) => tx.pretty(),
            Self::Eip7702(tx) => tx.pretty(),
            Self::AA(tx) => tx.pretty(),
        }
    }
}

impl<T: UIfmt> UIfmt for Transaction<T> {
    fn pretty(&self) -> String {
        format!(
            "
blockHash            {}
blockNumber          {}
from                 {}
transactionIndex     {}
effectiveGasPrice    {}
{}",
            self.block_hash.pretty(),
            self.block_number.pretty(),
            self.inner.signer().pretty(),
            self.transaction_index.pretty(),
            self.effective_gas_price.pretty(),
            self.inner.inner().pretty().trim_start(),
        )
    }
}

#[cfg(feature = "optimism")]
impl<T: UIfmt> UIfmt for op_alloy_rpc_types::Transaction<T> {
    fn pretty(&self) -> String {
        format!(
            "
depositNonce         {}
depositReceiptVersion {}
{}",
            self.deposit_nonce.pretty(),
            self.deposit_receipt_version.pretty(),
            self.inner.pretty().trim_start(),
        )
    }
}

impl UIfmt for AnyRpcBlock {
    fn pretty(&self) -> String {
        self.0.pretty()
    }
}

impl UIfmt for AnyRpcTransaction {
    fn pretty(&self) -> String {
        self.0.pretty()
    }
}

impl<T: UIfmt> UIfmt for WithOtherFields<T> {
    fn pretty(&self) -> String {
        format!("{}{}", self.inner.pretty(), self.other.pretty())
    }
}

/// Various numerical ethereum types used for pretty printing
#[derive(Clone, Debug, Deserialize)]
#[serde(untagged)]
#[expect(missing_docs)]
pub enum EthValue {
    U64(U64),
    Address(Address),
    U256(U256),
    U64Array(Vec<U64>),
    U256Array(Vec<U256>),
    Other(serde_json::Value),
}

impl From<serde_json::Value> for EthValue {
    fn from(val: serde_json::Value) -> Self {
        serde_json::from_value(val).expect("infallible")
    }
}

impl UIfmt for EthValue {
    fn pretty(&self) -> String {
        match self {
            Self::U64(num) => num.pretty(),
            Self::U256(num) => num.pretty(),
            Self::Address(addr) => addr.pretty(),
            Self::U64Array(arr) => arr.pretty(),
            Self::U256Array(arr) => arr.pretty(),
            Self::Other(val) => val.to_string().trim_matches('"').to_string(),
        }
    }
}

impl UIfmt for SignedAuthorization {
    fn pretty(&self) -> String {
        let signed_authorization = serde_json::to_string(self).unwrap_or("<invalid>".to_string());

        match self.recover_authority() {
            Ok(authority) => format!(
                "{{recoveredAuthority: {authority}, signedAuthority: {signed_authorization}}}",
            ),
            Err(e) => format!(
                "{{recoveredAuthority: <error: {e}>, signedAuthority: {signed_authorization}}}",
            ),
        }
    }
}

pub trait UIfmtHeaderExt {
    fn size_pretty(&self) -> String;
    fn total_difficulty_pretty(&self) -> String;
}

impl UIfmtHeaderExt for Header {
    fn size_pretty(&self) -> String {
        self.size.pretty()
    }

    fn total_difficulty_pretty(&self) -> String {
        self.total_difficulty.unwrap_or_else(|| self.difficulty()).pretty()
    }
}

impl UIfmtHeaderExt for AnyRpcHeader {
    fn size_pretty(&self) -> String {
        self.size.pretty()
    }

    fn total_difficulty_pretty(&self) -> String {
        self.total_difficulty.unwrap_or_else(|| self.difficulty()).pretty()
    }
}

impl UIfmtHeaderExt for TempoHeaderResponse {
    fn size_pretty(&self) -> String {
        self.inner.size.pretty()
    }

    fn total_difficulty_pretty(&self) -> String {
        self.inner.total_difficulty.unwrap_or_else(|| self.inner.difficulty()).pretty()
    }
}

pub trait UIfmtSignatureExt {
    fn signature_pretty(&self) -> Option<(String, String, String)>;
}

impl UIfmtSignatureExt for TxEnvelope {
    fn signature_pretty(&self) -> Option<(String, String, String)> {
        let sig = self.signature();
        Some((
            FixedBytes::from(sig.r()).pretty(),
            FixedBytes::from(sig.s()).pretty(),
            U8::from_le_slice(&sig.as_bytes()[64..]).pretty(),
        ))
    }
}

impl UIfmtSignatureExt for AnyTxEnvelope {
    fn signature_pretty(&self) -> Option<(String, String, String)> {
        self.as_envelope().and_then(|envelope| envelope.signature_pretty())
    }
}

#[cfg(feature = "optimism")]
impl UIfmtSignatureExt for OpTxEnvelope {
    fn signature_pretty(&self) -> Option<(String, String, String)> {
        self.signature().map(|sig| {
            (
                FixedBytes::from(sig.r()).pretty(),
                FixedBytes::from(sig.s()).pretty(),
                U8::from_le_slice(&sig.as_bytes()[64..]).pretty(),
            )
        })
    }
}

impl UIfmtSignatureExt for TempoTxEnvelope {
    fn signature_pretty(&self) -> Option<(String, String, String)> {
        let sig = match self {
            Self::Legacy(tx) => Some(tx.signature()),
            Self::Eip2930(tx) => Some(tx.signature()),
            Self::Eip1559(tx) => Some(tx.signature()),
            Self::Eip7702(tx) => Some(tx.signature()),
            Self::AA(tempo_tx) => {
                if let TempoSignature::Primitive(PrimitiveSignature::Secp256k1(sig)) =
                    tempo_tx.signature()
                {
                    Some(sig)
                } else {
                    None
                }
            }
        }?;
        Some((
            FixedBytes::from(sig.r()).pretty(),
            FixedBytes::from(sig.s()).pretty(),
            U8::from_le_slice(&sig.as_bytes()[64..]).pretty(),
        ))
    }
}

pub trait UIfmtReceiptExt {
    fn logs_pretty(&self) -> String;
    fn logs_bloom_pretty(&self) -> String;
    fn tx_type_pretty(&self) -> String;
}

fn receipt_logs_pretty<T: TxReceipt<Log = Log>>(receipt: &TransactionReceipt<T>) -> String {
    serde_json::to_string(receipt.inner.logs()).unwrap_or_default()
}

fn receipt_logs_bloom_pretty<T: TxReceipt<Log = Log>>(receipt: &TransactionReceipt<T>) -> String {
    receipt.inner.bloom().pretty()
}

impl UIfmtReceiptExt for TransactionReceipt {
    fn logs_pretty(&self) -> String {
        receipt_logs_pretty(self)
    }

    fn logs_bloom_pretty(&self) -> String {
        receipt_logs_bloom_pretty(self)
    }

    fn tx_type_pretty(&self) -> String {
        self.transaction_type().to_string()
    }
}

impl UIfmtReceiptExt for AnyTransactionReceipt {
    fn logs_pretty(&self) -> String {
        receipt_logs_pretty(&self.inner)
    }

    fn logs_bloom_pretty(&self) -> String {
        receipt_logs_bloom_pretty(&self.inner)
    }

    fn tx_type_pretty(&self) -> String {
        self.inner.inner.r#type.to_string()
    }
}

impl UIfmt for TempoTransactionReceipt {
    fn pretty(&self) -> String {
        let receipt = &self.inner;

        let mut pretty = format!(
            "
blockHash            {}
blockNumber          {}
contractAddress      {}
cumulativeGasUsed    {}
effectiveGasPrice    {}
from                 {}
gasUsed              {}
logs                 {}
logsBloom            {}
root                 {}
status               {}
transactionHash      {}
transactionIndex     {}
type                 {}
feePayer             {}
feeToken             {}",
            receipt.block_hash().pretty(),
            receipt.block_number().pretty(),
            receipt.contract_address().pretty(),
            receipt.cumulative_gas_used().pretty(),
            receipt.effective_gas_price().pretty(),
            receipt.from().pretty(),
            receipt.gas_used().pretty(),
            serde_json::to_string(receipt.inner.logs()).unwrap(),
            receipt.inner.logs_bloom.pretty(),
            self.state_root().pretty(),
            receipt.status().pretty(),
            receipt.transaction_hash().pretty(),
            receipt.transaction_index().pretty(),
            receipt.inner.receipt.tx_type as u8,
            self.fee_payer.pretty(),
            self.fee_token.pretty(),
        );

        if let Some(to) = receipt.to() {
            pretty.push_str(&format!("\nto                   {}", to.pretty()));
        }

        pretty
    }
}

impl UIfmtReceiptExt for TempoTransactionReceipt {
    fn logs_pretty(&self) -> String {
        serde_json::to_string(self.inner.inner.logs()).unwrap_or_default()
    }

    fn logs_bloom_pretty(&self) -> String {
        self.inner.inner.logs_bloom.pretty()
    }

    fn tx_type_pretty(&self) -> String {
        (self.inner.inner.receipt.tx_type as u8).to_string()
    }
}

/// Returns the `UiFmt::pretty()` formatted attribute of the transactions
pub fn get_pretty_tx_attr<N>(transaction: &N::TransactionResponse, attr: &str) -> Option<String>
where
    N: Network,
    N::TxEnvelope: UIfmtSignatureExt,
{
    let (r, s, v) = transaction.as_ref().signature_pretty().unwrap_or_default();
    match attr {
        "blockHash" | "block_hash" => {
            Some(alloy_network::TransactionResponse::block_hash(transaction).pretty())
        }
        "blockNumber" | "block_number" => {
            Some(alloy_network::TransactionResponse::block_number(transaction).pretty())
        }
        "from" => Some(alloy_network::TransactionResponse::from(transaction).pretty()),
        "gas" => Some(TxTrait::gas_limit(transaction).pretty()),
        "gasPrice" | "gas_price" => Some(TxTrait::max_fee_per_gas(transaction).pretty()),
        "hash" => Some(alloy_network::TransactionResponse::tx_hash(transaction).pretty()),
        "input" => Some(TxTrait::input(transaction).pretty()),
        "nonce" => Some(TxTrait::nonce(transaction).to_string()),
        "s" => Some(s),
        "r" => Some(r),
        "to" => Some(TxTrait::to(transaction).pretty()),
        "transactionIndex" | "transaction_index" => {
            Some(alloy_network::TransactionResponse::transaction_index(transaction).pretty())
        }
        "v" => Some(v),
        "value" => Some(TxTrait::value(transaction).pretty()),
        _ => None,
    }
}

pub fn get_pretty_block_attr<N>(block: &N::BlockResponse, attr: &str) -> Option<String>
where
    N: Network,
    N::BlockResponse: BlockResponse<Header = N::HeaderResponse>,
    N::HeaderResponse: UIfmtHeaderExt,
{
    match attr {
        "baseFeePerGas" | "base_fee_per_gas" => Some(block.header().base_fee_per_gas().pretty()),
        "difficulty" => Some(block.header().difficulty().pretty()),
        "extraData" | "extra_data" => Some(block.header().extra_data().pretty()),
        "gasLimit" | "gas_limit" => Some(block.header().gas_limit().pretty()),
        "gasUsed" | "gas_used" => Some(block.header().gas_used().pretty()),
        "hash" => Some(block.header().hash().pretty()),
        "logsBloom" | "logs_bloom" => Some(block.header().logs_bloom().pretty()),
        "miner" | "author" => Some(block.header().beneficiary().pretty()),
        "mixHash" | "mix_hash" => Some(block.header().mix_hash().pretty()),
        "nonce" => Some(block.header().nonce().pretty()),
        "number" => Some(block.header().number().pretty()),
        "parentHash" | "parent_hash" => Some(block.header().parent_hash().pretty()),
        "transactionsRoot" | "transactions_root" => {
            Some(block.header().transactions_root().pretty())
        }
        "receiptsRoot" | "receipts_root" => Some(block.header().receipts_root().pretty()),
        "sha3Uncles" | "sha_3_uncles" => Some(block.header().ommers_hash().pretty()),
        "size" => Some(block.header().size_pretty()),
        "stateRoot" | "state_root" => Some(block.header().state_root().pretty()),
        "timestamp" => Some(block.header().timestamp().pretty()),
        "totalDifficulty" | "total_difficulty" => Some(block.header().total_difficulty_pretty()),
        "blobGasUsed" | "blob_gas_used" => Some(block.header().blob_gas_used().pretty()),
        "excessBlobGas" | "excess_blob_gas" => Some(block.header().excess_blob_gas().pretty()),
        "requestsHash" | "requests_hash" => Some(block.header().requests_hash().pretty()),
        other => {
            if let Some(value) = block.other_fields().and_then(|fields| fields.get(other)) {
                let val = EthValue::from(value.clone());
                return Some(val.pretty());
            }
            None
        }
    }
}

pub fn get_pretty_receipt_attr<N>(receipt: &N::ReceiptResponse, attr: &str) -> Option<String>
where
    N: Network,
    N::ReceiptResponse: ReceiptResponse + UIfmtReceiptExt,
{
    match attr {
        "blockHash" | "block_hash" => Some(receipt.block_hash().pretty()),
        "blockNumber" | "block_number" => Some(receipt.block_number().pretty()),
        "contractAddress" | "contract_address" => Some(receipt.contract_address().pretty()),
        "cumulativeGasUsed" | "cumulative_gas_used" => Some(receipt.cumulative_gas_used().pretty()),
        "effectiveGasPrice" | "effective_gas_price" => Some(receipt.effective_gas_price().pretty()),
        "from" => Some(receipt.from().pretty()),
        "gasUsed" | "gas_used" => Some(receipt.gas_used().pretty()),
        "logs" => Some(receipt.logs_pretty()),
        "logsBloom" | "logs_bloom" => Some(receipt.logs_bloom_pretty()),
        "root" | "stateRoot" | "state_root" => Some(receipt.state_root().pretty()),
        "status" | "statusCode" | "status_code" => Some(receipt.status().pretty()),
        "transactionHash" | "transaction_hash" => Some(receipt.transaction_hash().pretty()),
        "transactionIndex" | "transaction_index" => Some(receipt.transaction_index().pretty()),
        "to" => Some(receipt.to().pretty()),
        "type" | "transaction_type" => Some(receipt.tx_type_pretty()),
        "blobGasPrice" | "blob_gas_price" => Some(receipt.blob_gas_price().pretty()),
        "blobGasUsed" | "blob_gas_used" => Some(receipt.blob_gas_used().pretty()),
        _ => None,
    }
}

fn pretty_generic_header_response<H: HeaderResponse + UIfmtHeaderExt>(header: &H) -> String {
    format!(
        "
baseFeePerGas        {}
difficulty           {}
extraData            {}
gasLimit             {}
gasUsed              {}
hash                 {}
logsBloom            {}
miner                {}
mixHash              {}
nonce                {}
number               {}
parentHash           {}
parentBeaconRoot     {}
transactionsRoot     {}
receiptsRoot         {}
sha3Uncles           {}
size                 {}
stateRoot            {}
timestamp            {} ({})
withdrawalsRoot      {}
totalDifficulty      {}
blobGasUsed          {}
excessBlobGas        {}
requestsHash         {}",
        header.base_fee_per_gas().pretty(),
        header.difficulty().pretty(),
        header.extra_data().pretty(),
        header.gas_limit().pretty(),
        header.gas_used().pretty(),
        header.hash().pretty(),
        header.logs_bloom().pretty(),
        header.beneficiary().pretty(),
        header.mix_hash().pretty(),
        header.nonce().pretty(),
        header.number().pretty(),
        header.parent_hash().pretty(),
        header.parent_beacon_block_root().pretty(),
        header.transactions_root().pretty(),
        header.receipts_root().pretty(),
        header.ommers_hash().pretty(),
        header.size_pretty(),
        header.state_root().pretty(),
        header.timestamp().pretty(),
        fmt_timestamp(header.timestamp()),
        header.withdrawals_root().pretty(),
        header.total_difficulty_pretty(),
        header.blob_gas_used().pretty(),
        header.excess_blob_gas().pretty(),
        header.requests_hash().pretty(),
    )
}

/// Formats the timestamp to string
///
/// Assumes timestamp is seconds, but handles millis if it is too large
fn fmt_timestamp(timestamp: u64) -> String {
    // Tue Jan 19 2038 03:14:07 GMT+0000
    if timestamp > 2147483647 {
        // assume this is in millis, incorrectly set to millis by a node
        chrono::DateTime::from_timestamp_millis(timestamp as i64)
            .expect("block timestamp in range")
            .to_rfc3339()
    } else {
        // assume this is still in seconds
        chrono::DateTime::from_timestamp(timestamp as i64, 0)
            .expect("block timestamp in range")
            .to_rfc2822()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use alloy_network::Ethereum;
    use alloy_primitives::B256;
    use alloy_rpc_types::Authorization;
    use similar_asserts::assert_eq;
    use std::str::FromStr;

    #[test]
    fn format_date_time() {
        // Fri Aug 29 2025 08:05:38 GMT+0000
        let timestamp = 1756454738u64;

        let datetime = fmt_timestamp(timestamp);
        assert_eq!(datetime, "Fri, 29 Aug 2025 08:05:38 +0000");
        let datetime = fmt_timestamp(timestamp * 1000);
        assert_eq!(datetime, "2025-08-29T08:05:38+00:00");
    }

    #[test]
    fn can_format_bytes32() {
        let val = hex::decode("7465737400000000000000000000000000000000000000000000000000000000")
            .unwrap();
        let mut b32 = [0u8; 32];
        b32.copy_from_slice(&val);

        assert_eq!(
            b32.pretty(),
            "0x7465737400000000000000000000000000000000000000000000000000000000"
        );
        let b: Bytes = val.into();
        assert_eq!(b.pretty(), b32.pretty());
    }

    #[cfg(feature = "optimism")]
    #[test]
    fn can_pretty_print_optimism_tx() {
        let s = r#"
        {
        "blockHash": "0x02b853cf50bc1c335b70790f93d5a390a35a166bea9c895e685cc866e4961cae",
        "blockNumber": "0x1b4",
        "from": "0x3b179DcfC5fAa677044c27dCe958e4BC0ad696A6",
        "gas": "0x11cbbdc",
        "gasPrice": "0x0",
        "hash": "0x2642e960d3150244e298d52b5b0f024782253e6d0b2c9a01dd4858f7b4665a3f",
        "input": "0xd294f093",
        "nonce": "0xa2",
        "to": "0x4a16A42407AA491564643E1dfc1fd50af29794eF",
        "transactionIndex": "0x0",
        "value": "0x0",
        "v": "0x38",
        "r": "0x6fca94073a0cf3381978662d46cf890602d3e9ccf6a31e4b69e8ecbd995e2bee",
        "s": "0xe804161a2b56a37ca1f6f4c4b8bce926587afa0d9b1acc5165e6556c959d583",
        "depositNonce": "",
        "depositReceiptVersion": "0x1"
    }
        "#;

        let tx: op_alloy_rpc_types::Transaction<OpTxEnvelope> = serde_json::from_str(s).unwrap();
        assert_eq!(
            tx.pretty().trim(),
            r"
depositNonce         
depositReceiptVersion 1
blockHash            0x02b853cf50bc1c335b70790f93d5a390a35a166bea9c895e685cc866e4961cae
blockNumber          436
from                 0x3b179DcfC5fAa677044c27dCe958e4BC0ad696A6
transactionIndex     0
effectiveGasPrice    0
hash                 0x2642e960d3150244e298d52b5b0f024782253e6d0b2c9a01dd4858f7b4665a3f
type                 0
chainId              10
nonce                162
gasPrice             0
gasLimit             18660316
to                   0x4a16A42407AA491564643E1dfc1fd50af29794eF
value                0
input                0xd294f093
r                    0x6fca94073a0cf3381978662d46cf890602d3e9ccf6a31e4b69e8ecbd995e2bee
s                    0x0e804161a2b56a37ca1f6f4c4b8bce926587afa0d9b1acc5165e6556c959d583
yParity              1
"
            .trim()
        );
    }

    #[cfg(feature = "optimism")]
    #[test]
    fn can_pretty_print_optimism_tx_through_any() {
        let s = r#"
        {
        "blockHash": "0x02b853cf50bc1c335b70790f93d5a390a35a166bea9c895e685cc866e4961cae",
        "blockNumber": "0x1b4",
        "from": "0x3b179DcfC5fAa677044c27dCe958e4BC0ad696A6",
        "gas": "0x11cbbdc",
        "gasPrice": "0x0",
        "hash": "0x2642e960d3150244e298d52b5b0f024782253e6d0b2c9a01dd4858f7b4665a3f",
        "input": "0xd294f093",
        "nonce": "0xa2",
        "to": "0x4a16A42407AA491564643E1dfc1fd50af29794eF",
        "transactionIndex": "0x0",
        "value": "0x0",
        "v": "0x38",
        "r": "0x6fca94073a0cf3381978662d46cf890602d3e9ccf6a31e4b69e8ecbd995e2bee",
        "s": "0xe804161a2b56a37ca1f6f4c4b8bce926587afa0d9b1acc5165e6556c959d583",
        "queueOrigin": "sequencer",
        "txType": "",
        "l1TxOrigin": null,
        "l1BlockNumber": "0xc1a65c",
        "l1Timestamp": "0x60d34b60",
        "index": "0x1b3",
        "queueIndex": null,
        "rawTransaction": "0xf86681a28084011cbbdc944a16a42407aa491564643e1dfc1fd50af29794ef8084d294f09338a06fca94073a0cf3381978662d46cf890602d3e9ccf6a31e4b69e8ecbd995e2beea00e804161a2b56a37ca1f6f4c4b8bce926587afa0d9b1acc5165e6556c959d583"
    }
        "#;

        let tx: WithOtherFields<Transaction<AnyTxEnvelope>> = serde_json::from_str(s).unwrap();
        assert_eq!(tx.pretty().trim(),
                   r"
blockHash            0x02b853cf50bc1c335b70790f93d5a390a35a166bea9c895e685cc866e4961cae
blockNumber          436
from                 0x3b179DcfC5fAa677044c27dCe958e4BC0ad696A6
transactionIndex     0
effectiveGasPrice    0
hash                 0x2642e960d3150244e298d52b5b0f024782253e6d0b2c9a01dd4858f7b4665a3f
type                 0
chainId              10
nonce                162
gasPrice             0
gasLimit             18660316
to                   0x4a16A42407AA491564643E1dfc1fd50af29794eF
value                0
input                0xd294f093
r                    0x6fca94073a0cf3381978662d46cf890602d3e9ccf6a31e4b69e8ecbd995e2bee
s                    0x0e804161a2b56a37ca1f6f4c4b8bce926587afa0d9b1acc5165e6556c959d583
yParity              1
index                435
l1BlockNumber        12691036
l1Timestamp          1624460128
l1TxOrigin           null
queueIndex           null
queueOrigin          sequencer
rawTransaction       0xf86681a28084011cbbdc944a16a42407aa491564643e1dfc1fd50af29794ef8084d294f09338a06fca94073a0cf3381978662d46cf890602d3e9ccf6a31e4b69e8ecbd995e2beea00e804161a2b56a37ca1f6f4c4b8bce926587afa0d9b1acc5165e6556c959d583
txType               0
".trim()
        );
    }

    #[test]
    fn can_pretty_print_eip2930() {
        let s = r#"{
        "type": "0x1",
        "blockHash": "0x2b27fe2bbc8ce01ac7ae8bf74f793a197cf7edbe82727588811fa9a2c4776f81",
        "blockNumber": "0x12b1d",
        "from": "0x2b371c0262ceab27face32fbb5270ddc6aa01ba4",
        "gas": "0x6bdf",
        "gasPrice": "0x3b9aca00",
        "hash": "0xbddbb685774d8a3df036ed9fb920b48f876090a57e9e90ee60921e0510ef7090",
        "input": "0x9c0e3f7a0000000000000000000000000000000000000000000000000000000000000078000000000000000000000000000000000000000000000000000000000000002a",
        "nonce": "0x1c",
        "to": "0x8e730df7c70d33118d9e5f79ab81aed0be6f6635",
        "transactionIndex": "0x2",
        "value": "0x0",
        "v": "0x1",
        "r": "0x2a98c51c2782f664d3ce571fef0491b48f5ebbc5845fa513192e6e6b24ecdaa1",
        "s": "0x29b8e0c67aa9c11327e16556c591dc84a7aac2f6fc57c7f93901be8ee867aebc",
        "chainId": "0x66a",
        "accessList": [
            { "address": "0x2b371c0262ceab27face32fbb5270ddc6aa01ba4", "storageKeys": ["0x1122334455667788990011223344556677889900112233445566778899001122", "0x0000000000000000000000000000000000000000000000000000000000000000"] },
            { "address": "0x8e730df7c70d33118d9e5f79ab81aed0be6f6635", "storageKeys": [] }
        ]
      }
        "#;
        let tx: Transaction = serde_json::from_str(s).unwrap();
        assert_eq!(tx.pretty().trim(),
                   r"
blockHash            0x2b27fe2bbc8ce01ac7ae8bf74f793a197cf7edbe82727588811fa9a2c4776f81
blockNumber          76573
from                 0x2b371c0262CEAb27fAcE32FBB5270dDc6Aa01ba4
transactionIndex     2
effectiveGasPrice    1000000000
hash                 0xbddbb685774d8a3df036ed9fb920b48f876090a57e9e90ee60921e0510ef7090
type                 1
chainId              1642
nonce                28
gasPrice             1000000000
gasLimit             27615
to                   0x8E730Df7C70D33118D9e5F79ab81aEd0bE6F6635
value                0
accessList           [
	0x2b371c0262CEAb27fAcE32FBB5270dDc6Aa01ba4 => [
		0x1122334455667788990011223344556677889900112233445566778899001122
		0x0000000000000000000000000000000000000000000000000000000000000000
	]
	0x8E730Df7C70D33118D9e5F79ab81aEd0bE6F6635 => []
]
input                0x9c0e3f7a0000000000000000000000000000000000000000000000000000000000000078000000000000000000000000000000000000000000000000000000000000002a
r                    0x2a98c51c2782f664d3ce571fef0491b48f5ebbc5845fa513192e6e6b24ecdaa1
s                    0x29b8e0c67aa9c11327e16556c591dc84a7aac2f6fc57c7f93901be8ee867aebc
yParity              1
".trim()
        );
    }

    #[test]
    fn can_pretty_print_eip1559() {
        let s = r#"{
        "type": "0x2",
        "blockHash": "0x61abbe5e22738de0462046f5a5d6c4cd6bc1f3a6398e4457d5e293590e721125",
        "blockNumber": "0x7647",
        "from": "0xbaadf00d42264eeb3fafe6799d0b56cf55df0f00",
        "gas": "0x186a0",
        "hash": "0xa7231d4da0576fade5d3b9481f4cd52459ec59b9bbdbf4f60d6cd726b2a3a244",
        "input": "0x48600055323160015500",
        "nonce": "0x12c",
        "to": null,
        "transactionIndex": "0x41",
        "value": "0x0",
        "v": "0x1",
        "yParity": "0x1",
        "r": "0x396864e5f9132327defdb1449504252e1fa6bce73feb8cd6f348a342b198af34",
        "s": "0x44dbba72e6d3304104848277143252ee43627c82f02d1ef8e404e1bf97c70158",
        "gasPrice": "0x4a817c800",
        "maxFeePerGas": "0x4a817c800",
        "maxPriorityFeePerGas": "0x4a817c800",
        "chainId": "0x66a",
        "accessList": [
          {
            "address": "0xc141a9a7463e6c4716d9fc0c056c054f46bb2993",
            "storageKeys": [
              "0x0000000000000000000000000000000000000000000000000000000000000000"
            ]
          }
        ]
      }
"#;
        let tx: Transaction = serde_json::from_str(s).unwrap();
        assert_eq!(
            tx.pretty().trim(),
            r"
blockHash            0x61abbe5e22738de0462046f5a5d6c4cd6bc1f3a6398e4457d5e293590e721125
blockNumber          30279
from                 0xBaaDF00d42264eEb3FAFe6799d0b56cf55DF0F00
transactionIndex     65
effectiveGasPrice    20000000000
hash                 0xa7231d4da0576fade5d3b9481f4cd52459ec59b9bbdbf4f60d6cd726b2a3a244
type                 2
chainId              1642
nonce                300
gasLimit             100000
maxFeePerGas         20000000000
maxPriorityFeePerGas 20000000000
to                   
value                0
accessList           [
	0xC141a9A7463e6C4716d9FC0C056C054F46Bb2993 => [
		0x0000000000000000000000000000000000000000000000000000000000000000
	]
]
input                0x48600055323160015500
r                    0x396864e5f9132327defdb1449504252e1fa6bce73feb8cd6f348a342b198af34
s                    0x44dbba72e6d3304104848277143252ee43627c82f02d1ef8e404e1bf97c70158
yParity              1
"
            .trim()
        );
    }

    #[test]
    fn can_pretty_print_eip4884() {
        let s = r#"{
        "blockHash": "0xfc2715ff196e23ae613ed6f837abd9035329a720a1f4e8dce3b0694c867ba052",
        "blockNumber": "0x2a1cb",
        "from": "0xad01b55d7c3448b8899862eb335fbb17075d8de2",
        "gas": "0x5208",
        "gasPrice": "0x1d1a94a201c",
        "maxFeePerGas": "0x1d1a94a201c",
        "maxPriorityFeePerGas": "0x1d1a94a201c",
        "maxFeePerBlobGas": "0x3e8",
        "hash": "0x5ceec39b631763ae0b45a8fb55c373f38b8fab308336ca1dc90ecd2b3cf06d00",
        "input": "0x",
        "nonce": "0x1b483",
        "to": "0x000000000000000000000000000000000000f1c1",
        "transactionIndex": "0x0",
        "value": "0x0",
        "type": "0x3",
        "accessList": [],
        "chainId": "0x1a1f0ff42",
        "blobVersionedHashes": [
          "0x01a128c46fc61395706686d6284f83c6c86dfc15769b9363171ea9d8566e6e76"
        ],
        "v": "0x0",
        "r": "0x343c6239323a81ef61293cb4a4d37b6df47fbf68114adb5dd41581151a077da1",
        "s": "0x48c21f6872feaf181d37cc4f9bbb356d3f10b352ceb38d1c3b190d749f95a11b",
        "yParity": "0x0"
      }
"#;
        let tx: Transaction = serde_json::from_str(s).unwrap();
        assert_eq!(
            tx.pretty().trim(),
            r"
blockHash            0xfc2715ff196e23ae613ed6f837abd9035329a720a1f4e8dce3b0694c867ba052
blockNumber          172491
from                 0xAD01b55d7c3448B8899862eb335FBb17075d8DE2
transactionIndex     0
effectiveGasPrice    2000000000028
hash                 0x5ceec39b631763ae0b45a8fb55c373f38b8fab308336ca1dc90ecd2b3cf06d00
type                 3
chainId              7011893058
nonce                111747
gasLimit             21000
maxFeePerGas         2000000000028
maxPriorityFeePerGas 2000000000028
to                   0x000000000000000000000000000000000000f1C1
value                0
accessList           []
blobVersionedHashes  [
	0x01a128c46fc61395706686d6284f83c6c86dfc15769b9363171ea9d8566e6e76
]
maxFeePerBlobGas     1000
input                0x
r                    0x343c6239323a81ef61293cb4a4d37b6df47fbf68114adb5dd41581151a077da1
s                    0x48c21f6872feaf181d37cc4f9bbb356d3f10b352ceb38d1c3b190d749f95a11b
yParity              0
"
            .trim()
        );
    }

    #[test]
    fn print_block_w_txs() {
        let block = r#"{"number":"0x3","hash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","parentHash":"0x689c70c080ca22bc0e681694fa803c1aba16a69c8b6368fed5311d279eb9de90","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x7270c1c4440180f2bd5215809ee3d545df042b67329499e1ab97eb759d31610d","stateRoot":"0x29f32984517a7d25607da485b23cefabfd443751422ca7e603395e1de9bc8a4b","receiptsRoot":"0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2","miner":"0x0000000000000000000000000000000000000000","difficulty":"0x0","totalDifficulty":"0x0","extraData":"0x","size":"0x3e8","gasLimit":"0x6691b7","gasUsed":"0x5208","timestamp":"0x5ecedbb9","transactions":[{"hash":"0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067","nonce":"0x2","blockHash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","blockNumber":"0x3","transactionIndex":"0x0","from":"0xfdcedc3bfca10ecb0890337fbdd1977aba84807a","to":"0xdca8ce283150ab773bcbeb8d38289bdb5661de1e","value":"0x0","gas":"0x15f90","gasPrice":"0x4a817c800","input":"0x","v":"0x25","r":"0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88","s":"0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e"}],"uncles":[]}"#;
        let block: Block = serde_json::from_str(block).unwrap();
        let output = "
blockHash            0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972
blockNumber          3
from                 0xFdCeDC3bFca10eCb0890337fbdD1977aba84807a
transactionIndex     0
effectiveGasPrice    20000000000
hash                 0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067
type                 0
chainId              1
nonce                2
gasPrice             20000000000
gasLimit             90000
to                   0xdca8ce283150AB773BCbeB8d38289bdB5661dE1e
value                0
input                0x
r                    0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88
s                    0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e
yParity              0"
            .to_string();
        let txs = match block.transactions() {
            BlockTransactions::Full(txs) => txs,
            _ => panic!("not full transactions"),
        };
        let generated = txs[0].pretty();
        assert_eq!(generated.as_str(), output.as_str());
    }

    #[test]
    fn uifmt_option_u64() {
        assert_eq!(None::<U64>.pretty(), "");
        assert_eq!(U64::from(100).pretty(), "100");
        assert_eq!(Some(U64::from(100)).pretty(), "100");
    }

    #[test]
    fn uifmt_option_h64() {
        assert_eq!(None::<B256>.pretty(), "");
        assert_eq!(
            B256::with_last_byte(100).pretty(),
            "0x0000000000000000000000000000000000000000000000000000000000000064",
        );
        assert_eq!(
            Some(B256::with_last_byte(100)).pretty(),
            "0x0000000000000000000000000000000000000000000000000000000000000064",
        );
    }

    #[test]
    fn uifmt_option_bytes() {
        assert_eq!(None::<Bytes>.pretty(), "");
        assert_eq!(
            Bytes::from_str("0x0000000000000000000000000000000000000000000000000000000000000064")
                .unwrap()
                .pretty(),
            "0x0000000000000000000000000000000000000000000000000000000000000064",
        );
        assert_eq!(
            Some(
                Bytes::from_str(
                    "0x0000000000000000000000000000000000000000000000000000000000000064"
                )
                .unwrap()
            )
            .pretty(),
            "0x0000000000000000000000000000000000000000000000000000000000000064",
        );
    }

    #[test]
    fn test_pretty_tx_attr() {
        let block = r#"{"number":"0x3","hash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","parentHash":"0x689c70c080ca22bc0e681694fa803c1aba16a69c8b6368fed5311d279eb9de90","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x7270c1c4440180f2bd5215809ee3d545df042b67329499e1ab97eb759d31610d","stateRoot":"0x29f32984517a7d25607da485b23cefabfd443751422ca7e603395e1de9bc8a4b","receiptsRoot":"0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2","miner":"0x0000000000000000000000000000000000000000","difficulty":"0x0","totalDifficulty":"0x0","extraData":"0x","size":"0x3e8","gasLimit":"0x6691b7","gasUsed":"0x5208","timestamp":"0x5ecedbb9","transactions":[{"hash":"0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067","nonce":"0x2","blockHash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","blockNumber":"0x3","transactionIndex":"0x0","from":"0xfdcedc3bfca10ecb0890337fbdd1977aba84807a","to":"0xdca8ce283150ab773bcbeb8d38289bdb5661de1e","value":"0x0","gas":"0x15f90","gasPrice":"0x4a817c800","input":"0x","v":"0x25","r":"0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88","s":"0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e"}],"uncles":[]}"#;
        let block: <Ethereum as Network>::BlockResponse = serde_json::from_str(block).unwrap();
        let txs = match block.transactions() {
            BlockTransactions::Full(txes) => txes,
            _ => panic!("not full transactions"),
        };

        assert_eq!(None, get_pretty_tx_attr::<Ethereum>(&txs[0], ""));
        assert_eq!(Some("3".to_string()), get_pretty_tx_attr::<Ethereum>(&txs[0], "blockNumber"));
        assert_eq!(
            Some("0xFdCeDC3bFca10eCb0890337fbdD1977aba84807a".to_string()),
            get_pretty_tx_attr::<Ethereum>(&txs[0], "from")
        );
        assert_eq!(Some("90000".to_string()), get_pretty_tx_attr::<Ethereum>(&txs[0], "gas"));
        assert_eq!(
            Some("20000000000".to_string()),
            get_pretty_tx_attr::<Ethereum>(&txs[0], "gasPrice")
        );
        assert_eq!(
            Some("0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067".to_string()),
            get_pretty_tx_attr::<Ethereum>(&txs[0], "hash")
        );
        assert_eq!(Some("0x".to_string()), get_pretty_tx_attr::<Ethereum>(&txs[0], "input"));
        assert_eq!(Some("2".to_string()), get_pretty_tx_attr::<Ethereum>(&txs[0], "nonce"));
        assert_eq!(
            Some("0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88".to_string()),
            get_pretty_tx_attr::<Ethereum>(&txs[0], "r")
        );
        assert_eq!(
            Some("0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e".to_string()),
            get_pretty_tx_attr::<Ethereum>(&txs[0], "s")
        );
        assert_eq!(
            Some("0xdca8ce283150AB773BCbeB8d38289bdB5661dE1e".into()),
            get_pretty_tx_attr::<Ethereum>(&txs[0], "to")
        );
        assert_eq!(
            Some("0".to_string()),
            get_pretty_tx_attr::<Ethereum>(&txs[0], "transactionIndex")
        );
        assert_eq!(Some("27".to_string()), get_pretty_tx_attr::<Ethereum>(&txs[0], "v"));
        assert_eq!(Some("0".to_string()), get_pretty_tx_attr::<Ethereum>(&txs[0], "value"));
    }

    #[test]
    fn test_pretty_block_attr() {
        let json = serde_json::json!(
        {
            "baseFeePerGas": "0x7",
            "miner": "0x0000000000000000000000000000000000000001",
            "number": "0x1b4",
            "hash": "0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331",
            "parentHash": "0x9646252be9520f6e71339a8df9c55e4d7619deeb018d2a3f2d21fc165dde5eb5",
            "mixHash": "0x1010101010101010101010101010101010101010101010101010101010101010",
            "nonce": "0x0000000000000000",
            "sealFields": [
              "0xe04d296d2460cfb8472af2c5fd05b5a214109c25688d3704aed5484f9a7792f2",
              "0x0000000000000042"
            ],
            "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
            "logsBloom":  "0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331",
            "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
            "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
            "stateRoot": "0xd5855eb08b3387c0af375e9cdb6acfc05eb8f519e419b874b6ff2ffda7ed1dff",
            "difficulty": "0x1",
            "totalDifficulty": "0x27f07",
            "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000",
            "size": "0x27f07",
            "gasLimit": "0x9f759",
            "minGasPrice": "0x9f759",
            "gasUsed": "0x9f759",
            "timestamp": "0x54e34e8e",
            "transactions": [],
            "uncles": [],
          }
        );

        let block: <Ethereum as Network>::BlockResponse = serde_json::from_value(json).unwrap();

        assert_eq!(None, get_pretty_block_attr::<Ethereum>(&block, ""));
        assert_eq!(
            Some("7".to_string()),
            get_pretty_block_attr::<Ethereum>(&block, "baseFeePerGas")
        );
        assert_eq!(Some("1".to_string()), get_pretty_block_attr::<Ethereum>(&block, "difficulty"));
        assert_eq!(
            Some("0x0000000000000000000000000000000000000000000000000000000000000000".to_string()),
            get_pretty_block_attr::<Ethereum>(&block, "extraData")
        );
        assert_eq!(
            Some("653145".to_string()),
            get_pretty_block_attr::<Ethereum>(&block, "gasLimit")
        );
        assert_eq!(
            Some("653145".to_string()),
            get_pretty_block_attr::<Ethereum>(&block, "gasUsed")
        );
        assert_eq!(
            Some("0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331".to_string()),
            get_pretty_block_attr::<Ethereum>(&block, "hash")
        );
        assert_eq!(Some("0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331".to_string()), get_pretty_block_attr::<Ethereum>(&block, "logsBloom"));
        assert_eq!(
            Some("0x0000000000000000000000000000000000000001".to_string()),
            get_pretty_block_attr::<Ethereum>(&block, "miner")
        );
        assert_eq!(
            Some("0x1010101010101010101010101010101010101010101010101010101010101010".to_string()),
            get_pretty_block_attr::<Ethereum>(&block, "mixHash")
        );
        assert_eq!(
            Some("0x0000000000000000".to_string()),
            get_pretty_block_attr::<Ethereum>(&block, "nonce")
        );
        assert_eq!(Some("436".to_string()), get_pretty_block_attr::<Ethereum>(&block, "number"));
        assert_eq!(
            Some("0x9646252be9520f6e71339a8df9c55e4d7619deeb018d2a3f2d21fc165dde5eb5".to_string()),
            get_pretty_block_attr::<Ethereum>(&block, "parentHash")
        );
        assert_eq!(
            Some("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".to_string()),
            get_pretty_block_attr::<Ethereum>(&block, "transactionsRoot")
        );
        assert_eq!(
            Some("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".to_string()),
            get_pretty_block_attr::<Ethereum>(&block, "receiptsRoot")
        );
        assert_eq!(
            Some("0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347".to_string()),
            get_pretty_block_attr::<Ethereum>(&block, "sha3Uncles")
        );
        assert_eq!(Some("163591".to_string()), get_pretty_block_attr::<Ethereum>(&block, "size"));
        assert_eq!(
            Some("0xd5855eb08b3387c0af375e9cdb6acfc05eb8f519e419b874b6ff2ffda7ed1dff".to_string()),
            get_pretty_block_attr::<Ethereum>(&block, "stateRoot")
        );
        assert_eq!(
            Some("1424182926".to_string()),
            get_pretty_block_attr::<Ethereum>(&block, "timestamp")
        );
        assert_eq!(
            Some("163591".to_string()),
            get_pretty_block_attr::<Ethereum>(&block, "totalDifficulty")
        );

        let pretty = pretty_generic_header_response(block.header());
        assert!(pretty.contains("difficulty           1"), "{pretty}");
        assert!(pretty.contains("totalDifficulty      163591"), "{pretty}");
    }

    #[test]
    fn test_receipt_other_fields_alignment() {
        let receipt_json = serde_json::json!(
        {
          "status": "0x1",
          "cumulativeGasUsed": "0x74e483",
          "logs": [],
          "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
          "type": "0x2",
          "transactionHash": "0x91181b0dca3b29aa136eeb2f536be5ce7b0aebc949be1c44b5509093c516097d",
          "transactionIndex": "0x10",
          "blockHash": "0x54bafb12e8cea9bb355fbf03a4ac49e42a2a1a80fa6cf4364b342e2de6432b5d",
          "blockNumber": "0x7b1ab93",
          "gasUsed": "0xc222",
          "effectiveGasPrice": "0x18961",
          "from": "0x2d815240a61731c75fa01b2793e1d3ed09f289d0",
          "to": "0x4200000000000000000000000000000000000000",
          "contractAddress": null,
          "l1BaseFeeScalar": "0x146b",
          "l1BlobBaseFee": "0x6a83078",
          "l1BlobBaseFeeScalar": "0xf79c5",
          "l1Fee": "0x51a9af7fd3",
          "l1GasPrice": "0x972fe4acc",
          "l1GasUsed": "0x640"
        });

        let receipt: AnyTransactionReceipt = serde_json::from_value(receipt_json).unwrap();
        let formatted = receipt.pretty();

        let expected = r#"
blockHash            0x54bafb12e8cea9bb355fbf03a4ac49e42a2a1a80fa6cf4364b342e2de6432b5d
blockNumber          129084307
contractAddress      
cumulativeGasUsed    7660675
effectiveGasPrice    100705
from                 0x2D815240A61731c75Fa01b2793E1D3eD09F289d0
gasUsed              49698
logs                 []
logsBloom            0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
root                 
status               1 (success)
transactionHash      0x91181b0dca3b29aa136eeb2f536be5ce7b0aebc949be1c44b5509093c516097d
transactionIndex     16
type                 2
blobGasPrice         
blobGasUsed          
to                   0x4200000000000000000000000000000000000000
l1BaseFeeScalar      5227
l1BlobBaseFee        111685752
l1BlobBaseFeeScalar  1014213
l1Fee                350739202003
l1GasPrice           40583973580
l1GasUsed            1600
"#;

        assert_eq!(formatted.trim(), expected.trim());
    }

    #[test]
    fn test_uifmt_for_signed_authorization() {
        let inner = Authorization {
            chain_id: U256::from(1),
            address: "0x000000000000000000000000000000000000dead".parse::<Address>().unwrap(),
            nonce: 42,
        };
        let signed_authorization =
            SignedAuthorization::new_unchecked(inner, 1, U256::from(20), U256::from(30));

        assert_eq!(
            signed_authorization.pretty(),
            r#"{recoveredAuthority: 0xf3eaBD0de6Ca1aE7fC4D81FfD6C9a40e5D5D7e30, signedAuthority: {"chainId":"0x1","address":"0x000000000000000000000000000000000000dead","nonce":"0x2a","yParity":"0x1","r":"0x14","s":"0x1e"}}"#
        );
    }

    #[test]
    fn can_pretty_print_tempo_tx() {
        let s = r#"{
            "type":"0x76",
            "chainId":"0xa5bd",
            "feeToken":"0x20c0000000000000000000000000000000000001",
            "maxPriorityFeePerGas":"0x0",
            "maxFeePerGas":"0x2cb417800",
            "gas":"0x2d178",
            "calls":[
                {
                    "data":null,
                    "input":"0x095ea7b3000000000000000000000000dec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000989680",
                    "to":"0x20c0000000000000000000000000000000000000",
                    "value":"0x0"
                },
                {
                    "data":null,
                    "input":"0xf8856c0f00000000000000000000000020c000000000000000000000000000000000000000000000000000000000000020c00000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000989680000000000000000000000000000000000000000000000000000000000097d330",
                    "to":"0xdec0000000000000000000000000000000000000",
                    "value":"0x0"
                }
            ],
            "accessList":[],
            "nonceKey":"0x0",
            "nonce":"0x0",
            "feePayerSignature":null,
            "validBefore":null,
            "validAfter":null,
            "keyAuthorization":null,
            "aaAuthorizationList":[],
            "signature":{
                "pubKeyX":"0xaacc80b21e45fb11f349424dce3a2f23547f60c0ff2f8bcaede2a247545ce8dd",
                "pubKeyY":"0x87abf0dbb7a5c9507efae2e43833356651b45ac576c2e61cec4e9c0f41fcbf6e",
                "r":"0xcfd45c3b19745a42f80b134dcb02a8ba099a0e4e7be1984da54734aa81d8f29f",
                "s":"0x74bb9170ae6d25bd510c83fe35895ee5712efe13980a5edc8094c534e23af85e",
                "type":"webAuthn",
                "webauthnData":"0x7b98b7a8e6c68d7eac741a52e6fdae0560ce3c16ef5427ad46d7a54d0ed86dd41d000000007b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a2238453071464a7a50585167546e645473643649456659457776323173516e626966374c4741776e4b43626b222c226f726967696e223a2268747470733a2f2f74656d706f2d6465782e76657263656c2e617070222c2263726f73734f726967696e223a66616c73657d"
            },
            "hash":"0x6d6d8c102064e6dee44abad2024a8b1d37959230baab80e70efbf9b0c739c4fd",
            "blockHash":"0xc82b23589ceef5341ed307d33554714db6f9eefd4187a9ca8abc910a325b4689",
            "blockNumber":"0x321fde",
            "transactionIndex":"0x0",
            "from":"0x566ff0f4a6114f8072ecdc8a7a8a13d8d0c6b45f",
            "gasPrice":"0x2540be400"
        }"#;

        let tx: Transaction<TempoTxEnvelope> = serde_json::from_str(s).unwrap();

        assert_eq!(
            tx.pretty().trim(),
            r#"
blockHash            0xc82b23589ceef5341ed307d33554714db6f9eefd4187a9ca8abc910a325b4689
blockNumber          3284958
from                 0x566Ff0f4a6114F8072ecDC8A7A8A13d8d0C6B45F
transactionIndex     0
effectiveGasPrice    10000000000
hash                 0x6d6d8c102064e6dee44abad2024a8b1d37959230baab80e70efbf9b0c739c4fd
type                 118
chainId              42429
feeToken             0x20C0000000000000000000000000000000000001
maxPriorityFeePerGas 0
maxFeePerGas         12000000000
gasLimit             184696
calls                [
	to: 0x20C0000000000000000000000000000000000000, value: 0, input: 0x095ea7b3000000000000000000000000dec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000989680
	to: 0xDEc0000000000000000000000000000000000000, value: 0, input: 0xf8856c0f00000000000000000000000020c000000000000000000000000000000000000000000000000000000000000020c00000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000989680000000000000000000000000000000000000000000000000000000000097d330
]
accessList           []
nonceKey             0
nonce                0
feePayerSignature    
validBefore          
validAfter           
tempoSignature       {"type":"webAuthn","r":"0xcfd45c3b19745a42f80b134dcb02a8ba099a0e4e7be1984da54734aa81d8f29f","s":"0x74bb9170ae6d25bd510c83fe35895ee5712efe13980a5edc8094c534e23af85e","pubKeyX":"0xaacc80b21e45fb11f349424dce3a2f23547f60c0ff2f8bcaede2a247545ce8dd","pubKeyY":"0x87abf0dbb7a5c9507efae2e43833356651b45ac576c2e61cec4e9c0f41fcbf6e","webauthnData":"0x7b98b7a8e6c68d7eac741a52e6fdae0560ce3c16ef5427ad46d7a54d0ed86dd41d000000007b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a2238453071464a7a50585167546e645473643649456659457776323173516e626966374c4741776e4b43626b222c226f726967696e223a2268747470733a2f2f74656d706f2d6465782e76657263656c2e617070222c2263726f73734f726967696e223a66616c73657d"}
"#
                .trim()
        );
    }

    #[test]
    fn can_pretty_print_tempo_receipt() {
        let s = r#"{"type":"0x76","status":"0x1","cumulativeGasUsed":"0x176d7f4","logs":[{"address":"0x20c0000000000000000000000000000000000000","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x000000000000000000000000a70ab0448e66cd77995bfbba5c5b64b41a85f3fd","0x0000000000000000000000000000000000000000000000000000000000000001"],"data":"0x00000000000000000000000000000000000000000000000000000000000003e8","blockHash":"0x860f788b251ece768e63b0d3906d156f652d843848b71c7fe81faacd49139d66","blockNumber":"0x69a1d7","blockTimestamp":"0x69a5d790","transactionHash":"0x04548a0ea27e2cccc1479af3c2ff02da4d4d3ea46af8e8d7edaa49f6ea27073f","transactionIndex":"0x63","logIndex":"0xb8","removed":false},{"address":"0x20c0000000000000000000000000000000000003","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x000000000000000000000000a70ab0448e66cd77995bfbba5c5b64b41a85f3fd","0x000000000000000000000000feec000000000000000000000000000000000000"],"data":"0x0000000000000000000000000000000000000000000000000000000000000417","blockHash":"0x860f788b251ece768e63b0d3906d156f652d843848b71c7fe81faacd49139d66","blockNumber":"0x69a1d7","blockTimestamp":"0x69a5d790","transactionHash":"0x04548a0ea27e2cccc1479af3c2ff02da4d4d3ea46af8e8d7edaa49f6ea27073f","transactionIndex":"0x63","logIndex":"0xb9","removed":false}],"logsBloom":"0x00000000000000000000000000000000000000000000010000000000000000000000000000000000000000000100000000000000000000000000000000040008000004200000000000000008000000000000000000040000000000000400000000000002000000000000000000000000000000000000000000000010000000000000000000000000000000000020000000000000800000000000000000000000000020000000000000000000000000000000000400000000000000000000000000000002000000000000000400000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000","transactionHash":"0x04548a0ea27e2cccc1479af3c2ff02da4d4d3ea46af8e8d7edaa49f6ea27073f","transactionIndex":"0x63","blockHash":"0x860f788b251ece768e63b0d3906d156f652d843848b71c7fe81faacd49139d66","blockNumber":"0x69a1d7","gasUsed":"0xcc6a","effectiveGasPrice":"0x4a817c802","from":"0xa70ab0448e66cd77995bfbba5c5b64b41a85f3fd","to":"0x20c0000000000000000000000000000000000000","contractAddress":null,"feeToken":"0x20c0000000000000000000000000000000000003","feePayer":"0xa70ab0448e66cd77995bfbba5c5b64b41a85f3fd"}"#;

        let tx: TempoTransactionReceipt = serde_json::from_str(s).unwrap();

        assert_eq!(
            tx.pretty().trim(),
            r#"
blockHash            0x860f788b251ece768e63b0d3906d156f652d843848b71c7fe81faacd49139d66
blockNumber          6922711
contractAddress      
cumulativeGasUsed    24565748
effectiveGasPrice    20000000002
from                 0xa70ab0448e66cD77995bfBBa5c5b64B41a85F3fd
gasUsed              52330
logs                 [{"address":"0x20c0000000000000000000000000000000000000","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x000000000000000000000000a70ab0448e66cd77995bfbba5c5b64b41a85f3fd","0x0000000000000000000000000000000000000000000000000000000000000001"],"data":"0x00000000000000000000000000000000000000000000000000000000000003e8","blockHash":"0x860f788b251ece768e63b0d3906d156f652d843848b71c7fe81faacd49139d66","blockNumber":"0x69a1d7","blockTimestamp":"0x69a5d790","transactionHash":"0x04548a0ea27e2cccc1479af3c2ff02da4d4d3ea46af8e8d7edaa49f6ea27073f","transactionIndex":"0x63","logIndex":"0xb8","removed":false},{"address":"0x20c0000000000000000000000000000000000003","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x000000000000000000000000a70ab0448e66cd77995bfbba5c5b64b41a85f3fd","0x000000000000000000000000feec000000000000000000000000000000000000"],"data":"0x0000000000000000000000000000000000000000000000000000000000000417","blockHash":"0x860f788b251ece768e63b0d3906d156f652d843848b71c7fe81faacd49139d66","blockNumber":"0x69a1d7","blockTimestamp":"0x69a5d790","transactionHash":"0x04548a0ea27e2cccc1479af3c2ff02da4d4d3ea46af8e8d7edaa49f6ea27073f","transactionIndex":"0x63","logIndex":"0xb9","removed":false}]
logsBloom            0x00000000000000000000000000000000000000000000010000000000000000000000000000000000000000000100000000000000000000000000000000040008000004200000000000000008000000000000000000040000000000000400000000000002000000000000000000000000000000000000000000000010000000000000000000000000000000000020000000000000800000000000000000000000000020000000000000000000000000000000000400000000000000000000000000000002000000000000000400000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000
root                 
status               true
transactionHash      0x04548a0ea27e2cccc1479af3c2ff02da4d4d3ea46af8e8d7edaa49f6ea27073f
transactionIndex     99
type                 118
feePayer             0xa70ab0448e66cD77995bfBBa5c5b64B41a85F3fd
feeToken             0x20C0000000000000000000000000000000000003
to                   0x20C0000000000000000000000000000000000000
"#
                .trim()
        );
    }

    #[test]
    fn test_ethereum_receipt_uifmt() {
        let s = r#"{"type":"0x2","status":"0x1","cumulativeGasUsed":"0x5208","logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","transactionHash":"0x1234567890123456789012345678901234567890123456789012345678901234","transactionIndex":"0x0","blockHash":"0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd","blockNumber":"0x1","gasUsed":"0x5208","effectiveGasPrice":"0x3b9aca00","from":"0x1234567890123456789012345678901234567890","to":"0x0987654321098765432109876543210987654321","contractAddress":null}"#;
        let receipt: TransactionReceipt = serde_json::from_str(s).unwrap();

        let pretty_output = receipt.pretty();

        assert!(pretty_output.contains("blockHash"));
        assert!(pretty_output.contains("blockNumber"));
        assert!(pretty_output.contains("status"));
        assert!(pretty_output.contains("gasUsed"));
        assert!(pretty_output.contains("transactionHash"));
        assert!(pretty_output.contains("type"));
        assert!(
            pretty_output
                .contains("0x1234567890123456789012345678901234567890123456789012345678901234")
        );
        assert!(pretty_output.contains("1 (success)"));
        assert!(pretty_output.contains("0x0987654321098765432109876543210987654321"));
    }

    #[test]
    fn test_get_pretty_receipt_attr() {
        let receipt_json = serde_json::json!({
            "type": "0x2",
            "status": "0x1",
            "cumulativeGasUsed": "0x5208",
            "logs": [],
            "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
            "transactionHash": "0x1234567890123456789012345678901234567890123456789012345678901234",
            "transactionIndex": "0x0",
            "blockHash": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd",
            "blockNumber": "0x1",
            "gasUsed": "0x5208",
            "effectiveGasPrice": "0x3b9aca00",
            "from": "0x1234567890123456789012345678901234567890",
            "to": "0x0987654321098765432109876543210987654321",
            "contractAddress": null
        });

        let receipt: <Ethereum as Network>::ReceiptResponse =
            serde_json::from_value(receipt_json).unwrap();

        // Test basic receipt attributes
        assert_eq!(
            Some("0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd".to_string()),
            get_pretty_receipt_attr::<Ethereum>(&receipt, "blockHash")
        );
        assert_eq!(
            Some("1".to_string()),
            get_pretty_receipt_attr::<Ethereum>(&receipt, "blockNumber")
        );
        assert_eq!(
            Some("0x1234567890123456789012345678901234567890123456789012345678901234".to_string()),
            get_pretty_receipt_attr::<Ethereum>(&receipt, "transactionHash")
        );
        assert_eq!(
            Some("21000".to_string()),
            get_pretty_receipt_attr::<Ethereum>(&receipt, "gasUsed")
        );
        assert_eq!(
            Some("true".to_string()),
            get_pretty_receipt_attr::<Ethereum>(&receipt, "status")
        );
        assert_eq!(
            Some("EIP-1559".to_string()),
            get_pretty_receipt_attr::<Ethereum>(&receipt, "type")
        );
        assert_eq!(Some("[]".to_string()), get_pretty_receipt_attr::<Ethereum>(&receipt, "logs"));
        assert!(get_pretty_receipt_attr::<Ethereum>(&receipt, "logsBloom").is_some());
    }
}
