Skip to main content

foundry_common_fmt/
ui.rs

1//! Helper trait and functions to format Ethereum types.
2
3use std::num::NonZeroU64;
4
5use alloy_consensus::{
6    BlockHeader, Eip658Value, Signed, Transaction as TxTrait, TxEip1559, TxEip2930,
7    TxEip4844Variant, TxEip7702, TxEnvelope, TxLegacy, TxReceipt, Typed2718,
8    transaction::TxHashRef,
9};
10use alloy_network::{
11    AnyRpcBlock, AnyRpcHeader, AnyRpcTransaction, AnyTransactionReceipt, AnyTxEnvelope,
12    BlockResponse, Network, ReceiptResponse, primitives::HeaderResponse,
13};
14use alloy_primitives::{
15    Address, Bloom, Bytes, FixedBytes, I256, Signature, U8, U64, U256, Uint, hex,
16};
17use alloy_rpc_types::{
18    AccessListItem, Block, BlockTransactions, Header, Log, Transaction, TransactionReceipt,
19};
20use alloy_serde::{OtherFields, WithOtherFields};
21#[cfg(feature = "optimism")]
22use op_alloy_consensus::{OpTxEnvelope, TxDeposit, TxPostExec};
23use revm::context_interface::transaction::SignedAuthorization;
24use serde::Deserialize;
25use tempo_alloy::{
26    primitives::{
27        AASigned, TempoSignature, TempoTransaction, TempoTxEnvelope,
28        transaction::{Call, PrimitiveSignature},
29    },
30    rpc::{TempoHeaderResponse, TempoTransactionReceipt},
31};
32
33/// length of the name column for pretty formatting `{:>20}{value}`
34const NAME_COLUMN_LEN: usize = 20usize;
35
36/// Helper trait to format Ethereum types.
37///
38/// # Examples
39///
40/// ```
41/// use foundry_common_fmt::UIfmt;
42///
43/// let boolean: bool = true;
44/// let string = boolean.pretty();
45/// ```
46pub trait UIfmt {
47    /// Return a prettified string version of the value
48    fn pretty(&self) -> String;
49}
50
51impl<T: UIfmt> UIfmt for &T {
52    fn pretty(&self) -> String {
53        (*self).pretty()
54    }
55}
56
57impl<T: UIfmt> UIfmt for Option<T> {
58    fn pretty(&self) -> String {
59        if let Some(inner) = self { inner.pretty() } else { String::new() }
60    }
61}
62
63impl<T: UIfmt> UIfmt for [T] {
64    fn pretty(&self) -> String {
65        if self.is_empty() {
66            "[]".to_string()
67        } else {
68            let mut s = String::with_capacity(self.len() * 64);
69            s.push_str("[\n");
70            for item in self {
71                for line in item.pretty().lines() {
72                    s.push('\t');
73                    s.push_str(line);
74                    s.push('\n');
75                }
76            }
77            s.push(']');
78            s
79        }
80    }
81}
82
83impl UIfmt for String {
84    fn pretty(&self) -> String {
85        self.clone()
86    }
87}
88
89impl UIfmt for u64 {
90    fn pretty(&self) -> String {
91        self.to_string()
92    }
93}
94
95impl UIfmt for NonZeroU64 {
96    fn pretty(&self) -> String {
97        self.get().pretty()
98    }
99}
100
101impl UIfmt for u128 {
102    fn pretty(&self) -> String {
103        self.to_string()
104    }
105}
106
107impl UIfmt for bool {
108    fn pretty(&self) -> String {
109        self.to_string()
110    }
111}
112
113impl<const BITS: usize, const LIMBS: usize> UIfmt for Uint<BITS, LIMBS> {
114    fn pretty(&self) -> String {
115        self.to_string()
116    }
117}
118
119impl UIfmt for I256 {
120    fn pretty(&self) -> String {
121        self.to_string()
122    }
123}
124
125impl UIfmt for Address {
126    fn pretty(&self) -> String {
127        self.to_string()
128    }
129}
130
131impl UIfmt for Bloom {
132    fn pretty(&self) -> String {
133        self.to_string()
134    }
135}
136
137impl UIfmt for Vec<u8> {
138    fn pretty(&self) -> String {
139        self[..].pretty()
140    }
141}
142
143impl UIfmt for Bytes {
144    fn pretty(&self) -> String {
145        self[..].pretty()
146    }
147}
148
149impl<const N: usize> UIfmt for [u8; N] {
150    fn pretty(&self) -> String {
151        self[..].pretty()
152    }
153}
154
155impl<const N: usize> UIfmt for FixedBytes<N> {
156    fn pretty(&self) -> String {
157        self[..].pretty()
158    }
159}
160
161impl UIfmt for [u8] {
162    fn pretty(&self) -> String {
163        hex::encode_prefixed(self)
164    }
165}
166
167impl UIfmt for Eip658Value {
168    fn pretty(&self) -> String {
169        match self {
170            Self::Eip658(status) => if *status { "1 (success)" } else { "0 (failed)" }.to_string(),
171            Self::PostState(state) => state.pretty(),
172        }
173    }
174}
175
176impl UIfmt for Signature {
177    fn pretty(&self) -> String {
178        format!("[r: {}, s: {}, y_parity: {}]", self.r(), self.s(), self.v())
179    }
180}
181
182/// Pretty-prints the common fields of any `TransactionReceipt<T>`.
183fn pretty_receipt<T: TxReceipt<Log = Log>>(receipt: &TransactionReceipt<T>, tx_type: u8) -> String {
184    let mut pretty = format!(
185        "
186blockHash            {}
187blockNumber          {}
188contractAddress      {}
189cumulativeGasUsed    {}
190effectiveGasPrice    {}
191from                 {}
192gasUsed              {}
193logs                 {}
194logsBloom            {}
195root                 {}
196status               {}
197transactionHash      {}
198transactionIndex     {}
199type                 {}
200blobGasPrice         {}
201blobGasUsed          {}",
202        receipt.block_hash.pretty(),
203        receipt.block_number.pretty(),
204        receipt.contract_address.pretty(),
205        receipt.inner.cumulative_gas_used().pretty(),
206        receipt.effective_gas_price.pretty(),
207        receipt.from.pretty(),
208        receipt.gas_used.pretty(),
209        serde_json::to_string(receipt.inner.logs()).unwrap(),
210        receipt.inner.bloom().pretty(),
211        receipt.state_root().pretty(),
212        receipt.inner.status_or_post_state().pretty(),
213        receipt.transaction_hash.pretty(),
214        receipt.transaction_index.pretty(),
215        tx_type,
216        receipt.blob_gas_price.pretty(),
217        receipt.blob_gas_used.pretty()
218    );
219
220    if let Some(to) = receipt.to {
221        pretty.push_str(&format!("\nto                   {}", to.pretty()));
222    }
223
224    pretty
225}
226
227impl UIfmt for TransactionReceipt {
228    fn pretty(&self) -> String {
229        pretty_receipt(self, self.transaction_type() as u8)
230    }
231}
232
233impl UIfmt for AnyTransactionReceipt {
234    fn pretty(&self) -> String {
235        let mut pretty = pretty_receipt(&self.inner, self.inner.inner.r#type);
236        pretty.push_str(&self.other.pretty());
237        pretty
238    }
239}
240
241impl UIfmt for Log {
242    fn pretty(&self) -> String {
243        format!(
244            "
245address: {}
246blockHash: {}
247blockNumber: {}
248data: {}
249logIndex: {}
250removed: {}
251topics: {}
252transactionHash: {}
253transactionIndex: {}",
254            self.address().pretty(),
255            self.block_hash.pretty(),
256            self.block_number.pretty(),
257            self.data().data.pretty(),
258            self.log_index.pretty(),
259            self.removed.pretty(),
260            self.topics().pretty(),
261            self.transaction_hash.pretty(),
262            self.transaction_index.pretty(),
263        )
264    }
265}
266
267impl<T: UIfmt, H: HeaderResponse + UIfmtHeaderExt> UIfmt for Block<T, H> {
268    fn pretty(&self) -> String {
269        format!(
270            "
271{}
272transactions:        {}",
273            pretty_generic_header_response(&self.header),
274            self.transactions.pretty()
275        )
276    }
277}
278
279impl<T: UIfmt> UIfmt for BlockTransactions<T> {
280    fn pretty(&self) -> String {
281        match self {
282            Self::Hashes(hashes) => hashes.pretty(),
283            Self::Full(transactions) => transactions.pretty(),
284            Self::Uncle => String::new(),
285        }
286    }
287}
288
289impl UIfmt for OtherFields {
290    fn pretty(&self) -> String {
291        let mut s = String::with_capacity(self.len() * 30);
292        if !self.is_empty() {
293            s.push('\n');
294        }
295        for (key, value) in self {
296            let val = EthValue::from(value.clone()).pretty();
297            let offset = NAME_COLUMN_LEN.saturating_sub(key.len());
298            s.push_str(key);
299            s.extend(std::iter::repeat_n(' ', offset + 1));
300            s.push_str(&val);
301            s.push('\n');
302        }
303        s
304    }
305}
306
307impl UIfmt for AccessListItem {
308    fn pretty(&self) -> String {
309        let mut s = String::with_capacity(42 + self.storage_keys.len() * 66);
310        s.push_str(self.address.pretty().as_str());
311        s.push_str(" => ");
312        s.push_str(self.storage_keys.pretty().as_str());
313        s
314    }
315}
316
317impl UIfmt for TxLegacy {
318    fn pretty(&self) -> String {
319        format!(
320            "
321chainId              {}
322nonce                {}
323gasPrice             {}
324gasLimit             {}
325to                   {}
326value                {}
327input                {}",
328            self.chain_id.pretty(),
329            self.nonce.pretty(),
330            self.gas_price.pretty(),
331            self.gas_limit.pretty(),
332            self.to().pretty(),
333            self.value.pretty(),
334            self.input.pretty(),
335        )
336    }
337}
338
339impl UIfmt for TxEip2930 {
340    fn pretty(&self) -> String {
341        format!(
342            "
343chainId              {}
344nonce                {}
345gasPrice             {}
346gasLimit             {}
347to                   {}
348value                {}
349accessList           {}
350input                {}",
351            self.chain_id.pretty(),
352            self.nonce.pretty(),
353            self.gas_price.pretty(),
354            self.gas_limit.pretty(),
355            self.to().pretty(),
356            self.value.pretty(),
357            self.access_list.pretty(),
358            self.input.pretty(),
359        )
360    }
361}
362
363impl UIfmt for TxEip1559 {
364    fn pretty(&self) -> String {
365        format!(
366            "
367chainId              {}
368nonce                {}
369gasLimit             {}
370maxFeePerGas         {}
371maxPriorityFeePerGas {}
372to                   {}
373value                {}
374accessList           {}
375input                {}",
376            self.chain_id.pretty(),
377            self.nonce.pretty(),
378            self.gas_limit.pretty(),
379            self.max_fee_per_gas.pretty(),
380            self.max_priority_fee_per_gas.pretty(),
381            self.to().pretty(),
382            self.value.pretty(),
383            self.access_list.pretty(),
384            self.input.pretty(),
385        )
386    }
387}
388
389impl UIfmt for TxEip4844Variant {
390    fn pretty(&self) -> String {
391        use alloy_consensus::TxEip4844;
392        let tx: &TxEip4844 = match self {
393            Self::TxEip4844(tx) => tx,
394            Self::TxEip4844WithSidecar(tx) => tx.tx(),
395        };
396        format!(
397            "
398chainId              {}
399nonce                {}
400gasLimit             {}
401maxFeePerGas         {}
402maxPriorityFeePerGas {}
403to                   {}
404value                {}
405accessList           {}
406blobVersionedHashes  {}
407maxFeePerBlobGas     {}
408input                {}",
409            tx.chain_id.pretty(),
410            tx.nonce.pretty(),
411            tx.gas_limit.pretty(),
412            tx.max_fee_per_gas.pretty(),
413            tx.max_priority_fee_per_gas.pretty(),
414            tx.to.pretty(),
415            tx.value.pretty(),
416            tx.access_list.pretty(),
417            tx.blob_versioned_hashes.pretty(),
418            tx.max_fee_per_blob_gas.pretty(),
419            tx.input.pretty(),
420        )
421    }
422}
423
424impl UIfmt for TxEip7702 {
425    fn pretty(&self) -> String {
426        format!(
427            "
428chainId              {}
429nonce                {}
430gasLimit             {}
431maxFeePerGas         {}
432maxPriorityFeePerGas {}
433to                   {}
434value                {}
435accessList           {}
436authorizationList    {}
437input                {}",
438            self.chain_id.pretty(),
439            self.nonce.pretty(),
440            self.gas_limit.pretty(),
441            self.max_fee_per_gas.pretty(),
442            self.max_priority_fee_per_gas.pretty(),
443            self.to.pretty(),
444            self.value.pretty(),
445            self.access_list.pretty(),
446            self.authorization_list.pretty(),
447            self.input.pretty(),
448        )
449    }
450}
451
452#[cfg(feature = "optimism")]
453impl UIfmt for TxDeposit {
454    fn pretty(&self) -> String {
455        format!(
456            "
457sourceHash           {}
458from                 {}
459to                   {}
460mint                 {}
461value                {}
462gasLimit             {}
463isSystemTransaction  {}
464input                {}",
465            self.source_hash.pretty(),
466            self.from.pretty(),
467            self.to().pretty(),
468            self.mint.pretty(),
469            self.value.pretty(),
470            self.gas_limit.pretty(),
471            self.is_system_transaction,
472            self.input.pretty(),
473        )
474    }
475}
476
477#[cfg(feature = "optimism")]
478impl UIfmt for TxPostExec {
479    fn pretty(&self) -> String {
480        format!(
481            "
482blockNumber          {}
483gasRefundEntries     {:?}
484input                {}",
485            self.payload.block_number.pretty(),
486            self.payload.gas_refund_entries,
487            self.input.pretty(),
488        )
489    }
490}
491
492impl UIfmt for Call {
493    fn pretty(&self) -> String {
494        format!(
495            "to: {}, value: {}, input: {}",
496            self.to.into_to().pretty(),
497            self.value.pretty(),
498            self.input.pretty(),
499        )
500    }
501}
502
503impl UIfmt for TempoTransaction {
504    fn pretty(&self) -> String {
505        format!(
506            "
507chainId              {}
508feeToken             {}
509maxPriorityFeePerGas {}
510maxFeePerGas         {}
511gasLimit             {}
512calls                {}
513accessList           {}
514nonceKey             {}
515nonce                {}
516feePayerSignature    {}
517validBefore          {}
518validAfter           {}",
519            self.chain_id.pretty(),
520            self.fee_token.pretty(),
521            self.max_priority_fee_per_gas.pretty(),
522            self.max_fee_per_gas.pretty(),
523            self.gas_limit.pretty(),
524            self.calls.pretty(),
525            self.access_list.pretty(),
526            self.nonce_key.pretty(),
527            self.nonce.pretty(),
528            self.fee_payer_signature.pretty(),
529            self.valid_before.pretty(),
530            self.valid_after.pretty(),
531        )
532    }
533}
534
535impl UIfmt for TempoSignature {
536    fn pretty(&self) -> String {
537        serde_json::to_string(self).unwrap_or_default()
538    }
539}
540
541impl UIfmt for AASigned {
542    fn pretty(&self) -> String {
543        format!(
544            "
545hash                 {}
546type                 {}
547{}
548tempoSignature       {}",
549            self.hash().pretty(),
550            self.tx().ty(),
551            self.tx().pretty().trim_start(),
552            self.signature().pretty(),
553        )
554    }
555}
556
557impl<T: UIfmt + Typed2718> UIfmt for Signed<T>
558where
559    Self: TxHashRef,
560{
561    fn pretty(&self) -> String {
562        format!(
563            "
564hash                 {}
565type                 {}
566{}
567r                    {}
568s                    {}
569yParity              {}",
570            self.tx_hash().pretty(),
571            self.ty(),
572            self.tx().pretty().trim_start(),
573            FixedBytes::from(self.signature().r()).pretty(),
574            FixedBytes::from(self.signature().s()).pretty(),
575            (if self.signature().v() { 1u64 } else { 0 }).pretty(),
576        )
577    }
578}
579
580impl UIfmt for TxEnvelope {
581    fn pretty(&self) -> String {
582        match self {
583            Self::Legacy(tx) => tx.pretty(),
584            Self::Eip2930(tx) => tx.pretty(),
585            Self::Eip1559(tx) => tx.pretty(),
586            Self::Eip4844(tx) => tx.pretty(),
587            Self::Eip7702(tx) => tx.pretty(),
588        }
589    }
590}
591
592impl UIfmt for AnyTxEnvelope {
593    fn pretty(&self) -> String {
594        match self {
595            Self::Ethereum(envelop) => envelop.pretty(),
596            Self::Unknown(tx) => {
597                format!(
598                    "
599hash                 {}
600type               {:#x}
601{}
602                    ",
603                    tx.hash.pretty(),
604                    tx.ty(),
605                    tx.inner.fields.pretty().trim_start(),
606                )
607            }
608        }
609    }
610}
611
612#[cfg(feature = "optimism")]
613impl UIfmt for OpTxEnvelope {
614    fn pretty(&self) -> String {
615        match self {
616            Self::Legacy(tx) => tx.pretty(),
617            Self::Eip2930(tx) => tx.pretty(),
618            Self::Eip1559(tx) => tx.pretty(),
619            Self::Eip7702(tx) => tx.pretty(),
620            Self::Deposit(tx) => tx.pretty(),
621            Self::PostExec(tx) => tx.pretty(),
622        }
623    }
624}
625
626impl UIfmt for TempoTxEnvelope {
627    fn pretty(&self) -> String {
628        match self {
629            Self::Legacy(tx) => tx.pretty(),
630            Self::Eip2930(tx) => tx.pretty(),
631            Self::Eip1559(tx) => tx.pretty(),
632            Self::Eip7702(tx) => tx.pretty(),
633            Self::AA(tx) => tx.pretty(),
634        }
635    }
636}
637
638impl<T: UIfmt> UIfmt for Transaction<T> {
639    fn pretty(&self) -> String {
640        format!(
641            "
642blockHash            {}
643blockNumber          {}
644from                 {}
645transactionIndex     {}
646effectiveGasPrice    {}
647{}",
648            self.block_hash.pretty(),
649            self.block_number.pretty(),
650            self.inner.signer().pretty(),
651            self.transaction_index.pretty(),
652            self.effective_gas_price.pretty(),
653            self.inner.inner().pretty().trim_start(),
654        )
655    }
656}
657
658#[cfg(feature = "optimism")]
659impl<T: UIfmt> UIfmt for op_alloy_rpc_types::Transaction<T> {
660    fn pretty(&self) -> String {
661        format!(
662            "
663depositNonce         {}
664depositReceiptVersion {}
665{}",
666            self.deposit_nonce.pretty(),
667            self.deposit_receipt_version.pretty(),
668            self.inner.pretty().trim_start(),
669        )
670    }
671}
672
673impl UIfmt for AnyRpcBlock {
674    fn pretty(&self) -> String {
675        self.0.pretty()
676    }
677}
678
679impl UIfmt for AnyRpcTransaction {
680    fn pretty(&self) -> String {
681        self.0.pretty()
682    }
683}
684
685impl<T: UIfmt> UIfmt for WithOtherFields<T> {
686    fn pretty(&self) -> String {
687        format!("{}{}", self.inner.pretty(), self.other.pretty())
688    }
689}
690
691/// Various numerical ethereum types used for pretty printing
692#[derive(Clone, Debug, Deserialize)]
693#[serde(untagged)]
694#[expect(missing_docs)]
695pub enum EthValue {
696    U64(U64),
697    Address(Address),
698    U256(U256),
699    U64Array(Vec<U64>),
700    U256Array(Vec<U256>),
701    Other(serde_json::Value),
702}
703
704impl From<serde_json::Value> for EthValue {
705    fn from(val: serde_json::Value) -> Self {
706        serde_json::from_value(val).expect("infallible")
707    }
708}
709
710impl UIfmt for EthValue {
711    fn pretty(&self) -> String {
712        match self {
713            Self::U64(num) => num.pretty(),
714            Self::U256(num) => num.pretty(),
715            Self::Address(addr) => addr.pretty(),
716            Self::U64Array(arr) => arr.pretty(),
717            Self::U256Array(arr) => arr.pretty(),
718            Self::Other(val) => val.to_string().trim_matches('"').to_string(),
719        }
720    }
721}
722
723impl UIfmt for SignedAuthorization {
724    fn pretty(&self) -> String {
725        let signed_authorization = serde_json::to_string(self).unwrap_or("<invalid>".to_string());
726
727        match self.recover_authority() {
728            Ok(authority) => format!(
729                "{{recoveredAuthority: {authority}, signedAuthority: {signed_authorization}}}",
730            ),
731            Err(e) => format!(
732                "{{recoveredAuthority: <error: {e}>, signedAuthority: {signed_authorization}}}",
733            ),
734        }
735    }
736}
737
738pub trait UIfmtHeaderExt {
739    fn size_pretty(&self) -> String;
740    fn total_difficulty_pretty(&self) -> String;
741}
742
743impl UIfmtHeaderExt for Header {
744    fn size_pretty(&self) -> String {
745        self.size.pretty()
746    }
747
748    fn total_difficulty_pretty(&self) -> String {
749        self.total_difficulty.unwrap_or_else(|| self.difficulty()).pretty()
750    }
751}
752
753impl UIfmtHeaderExt for AnyRpcHeader {
754    fn size_pretty(&self) -> String {
755        self.size.pretty()
756    }
757
758    fn total_difficulty_pretty(&self) -> String {
759        self.total_difficulty.unwrap_or_else(|| self.difficulty()).pretty()
760    }
761}
762
763impl UIfmtHeaderExt for TempoHeaderResponse {
764    fn size_pretty(&self) -> String {
765        self.inner.size.pretty()
766    }
767
768    fn total_difficulty_pretty(&self) -> String {
769        self.inner.total_difficulty.unwrap_or_else(|| self.inner.difficulty()).pretty()
770    }
771}
772
773pub trait UIfmtSignatureExt {
774    fn signature_pretty(&self) -> Option<(String, String, String)>;
775}
776
777impl UIfmtSignatureExt for TxEnvelope {
778    fn signature_pretty(&self) -> Option<(String, String, String)> {
779        let sig = self.signature();
780        Some((
781            FixedBytes::from(sig.r()).pretty(),
782            FixedBytes::from(sig.s()).pretty(),
783            U8::from_le_slice(&sig.as_bytes()[64..]).pretty(),
784        ))
785    }
786}
787
788impl UIfmtSignatureExt for AnyTxEnvelope {
789    fn signature_pretty(&self) -> Option<(String, String, String)> {
790        self.as_envelope().and_then(|envelope| envelope.signature_pretty())
791    }
792}
793
794#[cfg(feature = "optimism")]
795impl UIfmtSignatureExt for OpTxEnvelope {
796    fn signature_pretty(&self) -> Option<(String, String, String)> {
797        self.signature().map(|sig| {
798            (
799                FixedBytes::from(sig.r()).pretty(),
800                FixedBytes::from(sig.s()).pretty(),
801                U8::from_le_slice(&sig.as_bytes()[64..]).pretty(),
802            )
803        })
804    }
805}
806
807impl UIfmtSignatureExt for TempoTxEnvelope {
808    fn signature_pretty(&self) -> Option<(String, String, String)> {
809        let sig = match self {
810            Self::Legacy(tx) => Some(tx.signature()),
811            Self::Eip2930(tx) => Some(tx.signature()),
812            Self::Eip1559(tx) => Some(tx.signature()),
813            Self::Eip7702(tx) => Some(tx.signature()),
814            Self::AA(tempo_tx) => {
815                if let TempoSignature::Primitive(PrimitiveSignature::Secp256k1(sig)) =
816                    tempo_tx.signature()
817                {
818                    Some(sig)
819                } else {
820                    None
821                }
822            }
823        }?;
824        Some((
825            FixedBytes::from(sig.r()).pretty(),
826            FixedBytes::from(sig.s()).pretty(),
827            U8::from_le_slice(&sig.as_bytes()[64..]).pretty(),
828        ))
829    }
830}
831
832pub trait UIfmtReceiptExt {
833    fn logs_pretty(&self) -> String;
834    fn logs_bloom_pretty(&self) -> String;
835    fn tx_type_pretty(&self) -> String;
836}
837
838fn receipt_logs_pretty<T: TxReceipt<Log = Log>>(receipt: &TransactionReceipt<T>) -> String {
839    serde_json::to_string(receipt.inner.logs()).unwrap_or_default()
840}
841
842fn receipt_logs_bloom_pretty<T: TxReceipt<Log = Log>>(receipt: &TransactionReceipt<T>) -> String {
843    receipt.inner.bloom().pretty()
844}
845
846impl UIfmtReceiptExt for TransactionReceipt {
847    fn logs_pretty(&self) -> String {
848        receipt_logs_pretty(self)
849    }
850
851    fn logs_bloom_pretty(&self) -> String {
852        receipt_logs_bloom_pretty(self)
853    }
854
855    fn tx_type_pretty(&self) -> String {
856        self.transaction_type().to_string()
857    }
858}
859
860impl UIfmtReceiptExt for AnyTransactionReceipt {
861    fn logs_pretty(&self) -> String {
862        receipt_logs_pretty(&self.inner)
863    }
864
865    fn logs_bloom_pretty(&self) -> String {
866        receipt_logs_bloom_pretty(&self.inner)
867    }
868
869    fn tx_type_pretty(&self) -> String {
870        self.inner.inner.r#type.to_string()
871    }
872}
873
874impl UIfmt for TempoTransactionReceipt {
875    fn pretty(&self) -> String {
876        let receipt = &self.inner;
877
878        let mut pretty = format!(
879            "
880blockHash            {}
881blockNumber          {}
882contractAddress      {}
883cumulativeGasUsed    {}
884effectiveGasPrice    {}
885from                 {}
886gasUsed              {}
887logs                 {}
888logsBloom            {}
889root                 {}
890status               {}
891transactionHash      {}
892transactionIndex     {}
893type                 {}
894feePayer             {}
895feeToken             {}",
896            receipt.block_hash().pretty(),
897            receipt.block_number().pretty(),
898            receipt.contract_address().pretty(),
899            receipt.cumulative_gas_used().pretty(),
900            receipt.effective_gas_price().pretty(),
901            receipt.from().pretty(),
902            receipt.gas_used().pretty(),
903            serde_json::to_string(receipt.inner.logs()).unwrap(),
904            receipt.inner.logs_bloom.pretty(),
905            self.state_root().pretty(),
906            receipt.status().pretty(),
907            receipt.transaction_hash().pretty(),
908            receipt.transaction_index().pretty(),
909            receipt.inner.receipt.tx_type as u8,
910            self.fee_payer.pretty(),
911            self.fee_token.pretty(),
912        );
913
914        if let Some(to) = receipt.to() {
915            pretty.push_str(&format!("\nto                   {}", to.pretty()));
916        }
917
918        pretty
919    }
920}
921
922impl UIfmtReceiptExt for TempoTransactionReceipt {
923    fn logs_pretty(&self) -> String {
924        serde_json::to_string(self.inner.inner.logs()).unwrap_or_default()
925    }
926
927    fn logs_bloom_pretty(&self) -> String {
928        self.inner.inner.logs_bloom.pretty()
929    }
930
931    fn tx_type_pretty(&self) -> String {
932        (self.inner.inner.receipt.tx_type as u8).to_string()
933    }
934}
935
936/// Returns the `UiFmt::pretty()` formatted attribute of the transactions
937pub fn get_pretty_tx_attr<N>(transaction: &N::TransactionResponse, attr: &str) -> Option<String>
938where
939    N: Network,
940    N::TxEnvelope: UIfmtSignatureExt,
941{
942    let (r, s, v) = transaction.as_ref().signature_pretty().unwrap_or_default();
943    match attr {
944        "blockHash" | "block_hash" => {
945            Some(alloy_network::TransactionResponse::block_hash(transaction).pretty())
946        }
947        "blockNumber" | "block_number" => {
948            Some(alloy_network::TransactionResponse::block_number(transaction).pretty())
949        }
950        "from" => Some(alloy_network::TransactionResponse::from(transaction).pretty()),
951        "gas" => Some(TxTrait::gas_limit(transaction).pretty()),
952        "gasPrice" | "gas_price" => Some(TxTrait::max_fee_per_gas(transaction).pretty()),
953        "hash" => Some(alloy_network::TransactionResponse::tx_hash(transaction).pretty()),
954        "input" => Some(TxTrait::input(transaction).pretty()),
955        "nonce" => Some(TxTrait::nonce(transaction).to_string()),
956        "s" => Some(s),
957        "r" => Some(r),
958        "to" => Some(TxTrait::to(transaction).pretty()),
959        "transactionIndex" | "transaction_index" => {
960            Some(alloy_network::TransactionResponse::transaction_index(transaction).pretty())
961        }
962        "v" => Some(v),
963        "value" => Some(TxTrait::value(transaction).pretty()),
964        _ => None,
965    }
966}
967
968pub fn get_pretty_block_attr<N>(block: &N::BlockResponse, attr: &str) -> Option<String>
969where
970    N: Network,
971    N::BlockResponse: BlockResponse<Header = N::HeaderResponse>,
972    N::HeaderResponse: UIfmtHeaderExt,
973{
974    match attr {
975        "baseFeePerGas" | "base_fee_per_gas" => Some(block.header().base_fee_per_gas().pretty()),
976        "difficulty" => Some(block.header().difficulty().pretty()),
977        "extraData" | "extra_data" => Some(block.header().extra_data().pretty()),
978        "gasLimit" | "gas_limit" => Some(block.header().gas_limit().pretty()),
979        "gasUsed" | "gas_used" => Some(block.header().gas_used().pretty()),
980        "hash" => Some(block.header().hash().pretty()),
981        "logsBloom" | "logs_bloom" => Some(block.header().logs_bloom().pretty()),
982        "miner" | "author" => Some(block.header().beneficiary().pretty()),
983        "mixHash" | "mix_hash" => Some(block.header().mix_hash().pretty()),
984        "nonce" => Some(block.header().nonce().pretty()),
985        "number" => Some(block.header().number().pretty()),
986        "parentHash" | "parent_hash" => Some(block.header().parent_hash().pretty()),
987        "transactionsRoot" | "transactions_root" => {
988            Some(block.header().transactions_root().pretty())
989        }
990        "receiptsRoot" | "receipts_root" => Some(block.header().receipts_root().pretty()),
991        "sha3Uncles" | "sha_3_uncles" => Some(block.header().ommers_hash().pretty()),
992        "size" => Some(block.header().size_pretty()),
993        "stateRoot" | "state_root" => Some(block.header().state_root().pretty()),
994        "timestamp" => Some(block.header().timestamp().pretty()),
995        "totalDifficulty" | "total_difficulty" => Some(block.header().total_difficulty_pretty()),
996        "blobGasUsed" | "blob_gas_used" => Some(block.header().blob_gas_used().pretty()),
997        "excessBlobGas" | "excess_blob_gas" => Some(block.header().excess_blob_gas().pretty()),
998        "requestsHash" | "requests_hash" => Some(block.header().requests_hash().pretty()),
999        other => {
1000            if let Some(value) = block.other_fields().and_then(|fields| fields.get(other)) {
1001                let val = EthValue::from(value.clone());
1002                return Some(val.pretty());
1003            }
1004            None
1005        }
1006    }
1007}
1008
1009pub fn get_pretty_receipt_attr<N>(receipt: &N::ReceiptResponse, attr: &str) -> Option<String>
1010where
1011    N: Network,
1012    N::ReceiptResponse: ReceiptResponse + UIfmtReceiptExt,
1013{
1014    match attr {
1015        "blockHash" | "block_hash" => Some(receipt.block_hash().pretty()),
1016        "blockNumber" | "block_number" => Some(receipt.block_number().pretty()),
1017        "contractAddress" | "contract_address" => Some(receipt.contract_address().pretty()),
1018        "cumulativeGasUsed" | "cumulative_gas_used" => Some(receipt.cumulative_gas_used().pretty()),
1019        "effectiveGasPrice" | "effective_gas_price" => Some(receipt.effective_gas_price().pretty()),
1020        "from" => Some(receipt.from().pretty()),
1021        "gasUsed" | "gas_used" => Some(receipt.gas_used().pretty()),
1022        "logs" => Some(receipt.logs_pretty()),
1023        "logsBloom" | "logs_bloom" => Some(receipt.logs_bloom_pretty()),
1024        "root" | "stateRoot" | "state_root" => Some(receipt.state_root().pretty()),
1025        "status" | "statusCode" | "status_code" => Some(receipt.status().pretty()),
1026        "transactionHash" | "transaction_hash" => Some(receipt.transaction_hash().pretty()),
1027        "transactionIndex" | "transaction_index" => Some(receipt.transaction_index().pretty()),
1028        "to" => Some(receipt.to().pretty()),
1029        "type" | "transaction_type" => Some(receipt.tx_type_pretty()),
1030        "blobGasPrice" | "blob_gas_price" => Some(receipt.blob_gas_price().pretty()),
1031        "blobGasUsed" | "blob_gas_used" => Some(receipt.blob_gas_used().pretty()),
1032        _ => None,
1033    }
1034}
1035
1036fn pretty_generic_header_response<H: HeaderResponse + UIfmtHeaderExt>(header: &H) -> String {
1037    format!(
1038        "
1039baseFeePerGas        {}
1040difficulty           {}
1041extraData            {}
1042gasLimit             {}
1043gasUsed              {}
1044hash                 {}
1045logsBloom            {}
1046miner                {}
1047mixHash              {}
1048nonce                {}
1049number               {}
1050parentHash           {}
1051parentBeaconRoot     {}
1052transactionsRoot     {}
1053receiptsRoot         {}
1054sha3Uncles           {}
1055size                 {}
1056stateRoot            {}
1057timestamp            {} ({})
1058withdrawalsRoot      {}
1059totalDifficulty      {}
1060blobGasUsed          {}
1061excessBlobGas        {}
1062requestsHash         {}",
1063        header.base_fee_per_gas().pretty(),
1064        header.difficulty().pretty(),
1065        header.extra_data().pretty(),
1066        header.gas_limit().pretty(),
1067        header.gas_used().pretty(),
1068        header.hash().pretty(),
1069        header.logs_bloom().pretty(),
1070        header.beneficiary().pretty(),
1071        header.mix_hash().pretty(),
1072        header.nonce().pretty(),
1073        header.number().pretty(),
1074        header.parent_hash().pretty(),
1075        header.parent_beacon_block_root().pretty(),
1076        header.transactions_root().pretty(),
1077        header.receipts_root().pretty(),
1078        header.ommers_hash().pretty(),
1079        header.size_pretty(),
1080        header.state_root().pretty(),
1081        header.timestamp().pretty(),
1082        fmt_timestamp(header.timestamp()),
1083        header.withdrawals_root().pretty(),
1084        header.total_difficulty_pretty(),
1085        header.blob_gas_used().pretty(),
1086        header.excess_blob_gas().pretty(),
1087        header.requests_hash().pretty(),
1088    )
1089}
1090
1091/// Formats the timestamp to string
1092///
1093/// Assumes timestamp is seconds, but handles millis if it is too large
1094fn fmt_timestamp(timestamp: u64) -> String {
1095    // Tue Jan 19 2038 03:14:07 GMT+0000
1096    if timestamp > 2147483647 {
1097        // assume this is in millis, incorrectly set to millis by a node
1098        chrono::DateTime::from_timestamp_millis(timestamp as i64)
1099            .expect("block timestamp in range")
1100            .to_rfc3339()
1101    } else {
1102        // assume this is still in seconds
1103        chrono::DateTime::from_timestamp(timestamp as i64, 0)
1104            .expect("block timestamp in range")
1105            .to_rfc2822()
1106    }
1107}
1108
1109#[cfg(test)]
1110mod tests {
1111    use super::*;
1112    use alloy_network::Ethereum;
1113    use alloy_primitives::B256;
1114    use alloy_rpc_types::Authorization;
1115    use similar_asserts::assert_eq;
1116    use std::str::FromStr;
1117
1118    #[test]
1119    fn format_date_time() {
1120        // Fri Aug 29 2025 08:05:38 GMT+0000
1121        let timestamp = 1756454738u64;
1122
1123        let datetime = fmt_timestamp(timestamp);
1124        assert_eq!(datetime, "Fri, 29 Aug 2025 08:05:38 +0000");
1125        let datetime = fmt_timestamp(timestamp * 1000);
1126        assert_eq!(datetime, "2025-08-29T08:05:38+00:00");
1127    }
1128
1129    #[test]
1130    fn can_format_bytes32() {
1131        let val = hex::decode("7465737400000000000000000000000000000000000000000000000000000000")
1132            .unwrap();
1133        let mut b32 = [0u8; 32];
1134        b32.copy_from_slice(&val);
1135
1136        assert_eq!(
1137            b32.pretty(),
1138            "0x7465737400000000000000000000000000000000000000000000000000000000"
1139        );
1140        let b: Bytes = val.into();
1141        assert_eq!(b.pretty(), b32.pretty());
1142    }
1143
1144    #[cfg(feature = "optimism")]
1145    #[test]
1146    fn can_pretty_print_optimism_tx() {
1147        let s = r#"
1148        {
1149        "blockHash": "0x02b853cf50bc1c335b70790f93d5a390a35a166bea9c895e685cc866e4961cae",
1150        "blockNumber": "0x1b4",
1151        "from": "0x3b179DcfC5fAa677044c27dCe958e4BC0ad696A6",
1152        "gas": "0x11cbbdc",
1153        "gasPrice": "0x0",
1154        "hash": "0x2642e960d3150244e298d52b5b0f024782253e6d0b2c9a01dd4858f7b4665a3f",
1155        "input": "0xd294f093",
1156        "nonce": "0xa2",
1157        "to": "0x4a16A42407AA491564643E1dfc1fd50af29794eF",
1158        "transactionIndex": "0x0",
1159        "value": "0x0",
1160        "v": "0x38",
1161        "r": "0x6fca94073a0cf3381978662d46cf890602d3e9ccf6a31e4b69e8ecbd995e2bee",
1162        "s": "0xe804161a2b56a37ca1f6f4c4b8bce926587afa0d9b1acc5165e6556c959d583",
1163        "depositNonce": "",
1164        "depositReceiptVersion": "0x1"
1165    }
1166        "#;
1167
1168        let tx: op_alloy_rpc_types::Transaction<OpTxEnvelope> = serde_json::from_str(s).unwrap();
1169        assert_eq!(
1170            tx.pretty().trim(),
1171            r"
1172depositNonce         
1173depositReceiptVersion 1
1174blockHash            0x02b853cf50bc1c335b70790f93d5a390a35a166bea9c895e685cc866e4961cae
1175blockNumber          436
1176from                 0x3b179DcfC5fAa677044c27dCe958e4BC0ad696A6
1177transactionIndex     0
1178effectiveGasPrice    0
1179hash                 0x2642e960d3150244e298d52b5b0f024782253e6d0b2c9a01dd4858f7b4665a3f
1180type                 0
1181chainId              10
1182nonce                162
1183gasPrice             0
1184gasLimit             18660316
1185to                   0x4a16A42407AA491564643E1dfc1fd50af29794eF
1186value                0
1187input                0xd294f093
1188r                    0x6fca94073a0cf3381978662d46cf890602d3e9ccf6a31e4b69e8ecbd995e2bee
1189s                    0x0e804161a2b56a37ca1f6f4c4b8bce926587afa0d9b1acc5165e6556c959d583
1190yParity              1
1191"
1192            .trim()
1193        );
1194    }
1195
1196    #[cfg(feature = "optimism")]
1197    #[test]
1198    fn can_pretty_print_optimism_tx_through_any() {
1199        let s = r#"
1200        {
1201        "blockHash": "0x02b853cf50bc1c335b70790f93d5a390a35a166bea9c895e685cc866e4961cae",
1202        "blockNumber": "0x1b4",
1203        "from": "0x3b179DcfC5fAa677044c27dCe958e4BC0ad696A6",
1204        "gas": "0x11cbbdc",
1205        "gasPrice": "0x0",
1206        "hash": "0x2642e960d3150244e298d52b5b0f024782253e6d0b2c9a01dd4858f7b4665a3f",
1207        "input": "0xd294f093",
1208        "nonce": "0xa2",
1209        "to": "0x4a16A42407AA491564643E1dfc1fd50af29794eF",
1210        "transactionIndex": "0x0",
1211        "value": "0x0",
1212        "v": "0x38",
1213        "r": "0x6fca94073a0cf3381978662d46cf890602d3e9ccf6a31e4b69e8ecbd995e2bee",
1214        "s": "0xe804161a2b56a37ca1f6f4c4b8bce926587afa0d9b1acc5165e6556c959d583",
1215        "queueOrigin": "sequencer",
1216        "txType": "",
1217        "l1TxOrigin": null,
1218        "l1BlockNumber": "0xc1a65c",
1219        "l1Timestamp": "0x60d34b60",
1220        "index": "0x1b3",
1221        "queueIndex": null,
1222        "rawTransaction": "0xf86681a28084011cbbdc944a16a42407aa491564643e1dfc1fd50af29794ef8084d294f09338a06fca94073a0cf3381978662d46cf890602d3e9ccf6a31e4b69e8ecbd995e2beea00e804161a2b56a37ca1f6f4c4b8bce926587afa0d9b1acc5165e6556c959d583"
1223    }
1224        "#;
1225
1226        let tx: WithOtherFields<Transaction<AnyTxEnvelope>> = serde_json::from_str(s).unwrap();
1227        assert_eq!(tx.pretty().trim(),
1228                   r"
1229blockHash            0x02b853cf50bc1c335b70790f93d5a390a35a166bea9c895e685cc866e4961cae
1230blockNumber          436
1231from                 0x3b179DcfC5fAa677044c27dCe958e4BC0ad696A6
1232transactionIndex     0
1233effectiveGasPrice    0
1234hash                 0x2642e960d3150244e298d52b5b0f024782253e6d0b2c9a01dd4858f7b4665a3f
1235type                 0
1236chainId              10
1237nonce                162
1238gasPrice             0
1239gasLimit             18660316
1240to                   0x4a16A42407AA491564643E1dfc1fd50af29794eF
1241value                0
1242input                0xd294f093
1243r                    0x6fca94073a0cf3381978662d46cf890602d3e9ccf6a31e4b69e8ecbd995e2bee
1244s                    0x0e804161a2b56a37ca1f6f4c4b8bce926587afa0d9b1acc5165e6556c959d583
1245yParity              1
1246index                435
1247l1BlockNumber        12691036
1248l1Timestamp          1624460128
1249l1TxOrigin           null
1250queueIndex           null
1251queueOrigin          sequencer
1252rawTransaction       0xf86681a28084011cbbdc944a16a42407aa491564643e1dfc1fd50af29794ef8084d294f09338a06fca94073a0cf3381978662d46cf890602d3e9ccf6a31e4b69e8ecbd995e2beea00e804161a2b56a37ca1f6f4c4b8bce926587afa0d9b1acc5165e6556c959d583
1253txType               0
1254".trim()
1255        );
1256    }
1257
1258    #[test]
1259    fn can_pretty_print_eip2930() {
1260        let s = r#"{
1261        "type": "0x1",
1262        "blockHash": "0x2b27fe2bbc8ce01ac7ae8bf74f793a197cf7edbe82727588811fa9a2c4776f81",
1263        "blockNumber": "0x12b1d",
1264        "from": "0x2b371c0262ceab27face32fbb5270ddc6aa01ba4",
1265        "gas": "0x6bdf",
1266        "gasPrice": "0x3b9aca00",
1267        "hash": "0xbddbb685774d8a3df036ed9fb920b48f876090a57e9e90ee60921e0510ef7090",
1268        "input": "0x9c0e3f7a0000000000000000000000000000000000000000000000000000000000000078000000000000000000000000000000000000000000000000000000000000002a",
1269        "nonce": "0x1c",
1270        "to": "0x8e730df7c70d33118d9e5f79ab81aed0be6f6635",
1271        "transactionIndex": "0x2",
1272        "value": "0x0",
1273        "v": "0x1",
1274        "r": "0x2a98c51c2782f664d3ce571fef0491b48f5ebbc5845fa513192e6e6b24ecdaa1",
1275        "s": "0x29b8e0c67aa9c11327e16556c591dc84a7aac2f6fc57c7f93901be8ee867aebc",
1276        "chainId": "0x66a",
1277        "accessList": [
1278            { "address": "0x2b371c0262ceab27face32fbb5270ddc6aa01ba4", "storageKeys": ["0x1122334455667788990011223344556677889900112233445566778899001122", "0x0000000000000000000000000000000000000000000000000000000000000000"] },
1279            { "address": "0x8e730df7c70d33118d9e5f79ab81aed0be6f6635", "storageKeys": [] }
1280        ]
1281      }
1282        "#;
1283        let tx: Transaction = serde_json::from_str(s).unwrap();
1284        assert_eq!(tx.pretty().trim(),
1285                   r"
1286blockHash            0x2b27fe2bbc8ce01ac7ae8bf74f793a197cf7edbe82727588811fa9a2c4776f81
1287blockNumber          76573
1288from                 0x2b371c0262CEAb27fAcE32FBB5270dDc6Aa01ba4
1289transactionIndex     2
1290effectiveGasPrice    1000000000
1291hash                 0xbddbb685774d8a3df036ed9fb920b48f876090a57e9e90ee60921e0510ef7090
1292type                 1
1293chainId              1642
1294nonce                28
1295gasPrice             1000000000
1296gasLimit             27615
1297to                   0x8E730Df7C70D33118D9e5F79ab81aEd0bE6F6635
1298value                0
1299accessList           [
1300	0x2b371c0262CEAb27fAcE32FBB5270dDc6Aa01ba4 => [
1301		0x1122334455667788990011223344556677889900112233445566778899001122
1302		0x0000000000000000000000000000000000000000000000000000000000000000
1303	]
1304	0x8E730Df7C70D33118D9e5F79ab81aEd0bE6F6635 => []
1305]
1306input                0x9c0e3f7a0000000000000000000000000000000000000000000000000000000000000078000000000000000000000000000000000000000000000000000000000000002a
1307r                    0x2a98c51c2782f664d3ce571fef0491b48f5ebbc5845fa513192e6e6b24ecdaa1
1308s                    0x29b8e0c67aa9c11327e16556c591dc84a7aac2f6fc57c7f93901be8ee867aebc
1309yParity              1
1310".trim()
1311        );
1312    }
1313
1314    #[test]
1315    fn can_pretty_print_eip1559() {
1316        let s = r#"{
1317        "type": "0x2",
1318        "blockHash": "0x61abbe5e22738de0462046f5a5d6c4cd6bc1f3a6398e4457d5e293590e721125",
1319        "blockNumber": "0x7647",
1320        "from": "0xbaadf00d42264eeb3fafe6799d0b56cf55df0f00",
1321        "gas": "0x186a0",
1322        "hash": "0xa7231d4da0576fade5d3b9481f4cd52459ec59b9bbdbf4f60d6cd726b2a3a244",
1323        "input": "0x48600055323160015500",
1324        "nonce": "0x12c",
1325        "to": null,
1326        "transactionIndex": "0x41",
1327        "value": "0x0",
1328        "v": "0x1",
1329        "yParity": "0x1",
1330        "r": "0x396864e5f9132327defdb1449504252e1fa6bce73feb8cd6f348a342b198af34",
1331        "s": "0x44dbba72e6d3304104848277143252ee43627c82f02d1ef8e404e1bf97c70158",
1332        "gasPrice": "0x4a817c800",
1333        "maxFeePerGas": "0x4a817c800",
1334        "maxPriorityFeePerGas": "0x4a817c800",
1335        "chainId": "0x66a",
1336        "accessList": [
1337          {
1338            "address": "0xc141a9a7463e6c4716d9fc0c056c054f46bb2993",
1339            "storageKeys": [
1340              "0x0000000000000000000000000000000000000000000000000000000000000000"
1341            ]
1342          }
1343        ]
1344      }
1345"#;
1346        let tx: Transaction = serde_json::from_str(s).unwrap();
1347        assert_eq!(
1348            tx.pretty().trim(),
1349            r"
1350blockHash            0x61abbe5e22738de0462046f5a5d6c4cd6bc1f3a6398e4457d5e293590e721125
1351blockNumber          30279
1352from                 0xBaaDF00d42264eEb3FAFe6799d0b56cf55DF0F00
1353transactionIndex     65
1354effectiveGasPrice    20000000000
1355hash                 0xa7231d4da0576fade5d3b9481f4cd52459ec59b9bbdbf4f60d6cd726b2a3a244
1356type                 2
1357chainId              1642
1358nonce                300
1359gasLimit             100000
1360maxFeePerGas         20000000000
1361maxPriorityFeePerGas 20000000000
1362to                   
1363value                0
1364accessList           [
1365	0xC141a9A7463e6C4716d9FC0C056C054F46Bb2993 => [
1366		0x0000000000000000000000000000000000000000000000000000000000000000
1367	]
1368]
1369input                0x48600055323160015500
1370r                    0x396864e5f9132327defdb1449504252e1fa6bce73feb8cd6f348a342b198af34
1371s                    0x44dbba72e6d3304104848277143252ee43627c82f02d1ef8e404e1bf97c70158
1372yParity              1
1373"
1374            .trim()
1375        );
1376    }
1377
1378    #[test]
1379    fn can_pretty_print_eip4884() {
1380        let s = r#"{
1381        "blockHash": "0xfc2715ff196e23ae613ed6f837abd9035329a720a1f4e8dce3b0694c867ba052",
1382        "blockNumber": "0x2a1cb",
1383        "from": "0xad01b55d7c3448b8899862eb335fbb17075d8de2",
1384        "gas": "0x5208",
1385        "gasPrice": "0x1d1a94a201c",
1386        "maxFeePerGas": "0x1d1a94a201c",
1387        "maxPriorityFeePerGas": "0x1d1a94a201c",
1388        "maxFeePerBlobGas": "0x3e8",
1389        "hash": "0x5ceec39b631763ae0b45a8fb55c373f38b8fab308336ca1dc90ecd2b3cf06d00",
1390        "input": "0x",
1391        "nonce": "0x1b483",
1392        "to": "0x000000000000000000000000000000000000f1c1",
1393        "transactionIndex": "0x0",
1394        "value": "0x0",
1395        "type": "0x3",
1396        "accessList": [],
1397        "chainId": "0x1a1f0ff42",
1398        "blobVersionedHashes": [
1399          "0x01a128c46fc61395706686d6284f83c6c86dfc15769b9363171ea9d8566e6e76"
1400        ],
1401        "v": "0x0",
1402        "r": "0x343c6239323a81ef61293cb4a4d37b6df47fbf68114adb5dd41581151a077da1",
1403        "s": "0x48c21f6872feaf181d37cc4f9bbb356d3f10b352ceb38d1c3b190d749f95a11b",
1404        "yParity": "0x0"
1405      }
1406"#;
1407        let tx: Transaction = serde_json::from_str(s).unwrap();
1408        assert_eq!(
1409            tx.pretty().trim(),
1410            r"
1411blockHash            0xfc2715ff196e23ae613ed6f837abd9035329a720a1f4e8dce3b0694c867ba052
1412blockNumber          172491
1413from                 0xAD01b55d7c3448B8899862eb335FBb17075d8DE2
1414transactionIndex     0
1415effectiveGasPrice    2000000000028
1416hash                 0x5ceec39b631763ae0b45a8fb55c373f38b8fab308336ca1dc90ecd2b3cf06d00
1417type                 3
1418chainId              7011893058
1419nonce                111747
1420gasLimit             21000
1421maxFeePerGas         2000000000028
1422maxPriorityFeePerGas 2000000000028
1423to                   0x000000000000000000000000000000000000f1C1
1424value                0
1425accessList           []
1426blobVersionedHashes  [
1427	0x01a128c46fc61395706686d6284f83c6c86dfc15769b9363171ea9d8566e6e76
1428]
1429maxFeePerBlobGas     1000
1430input                0x
1431r                    0x343c6239323a81ef61293cb4a4d37b6df47fbf68114adb5dd41581151a077da1
1432s                    0x48c21f6872feaf181d37cc4f9bbb356d3f10b352ceb38d1c3b190d749f95a11b
1433yParity              0
1434"
1435            .trim()
1436        );
1437    }
1438
1439    #[test]
1440    fn print_block_w_txs() {
1441        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":[]}"#;
1442        let block: Block = serde_json::from_str(block).unwrap();
1443        let output = "
1444blockHash            0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972
1445blockNumber          3
1446from                 0xFdCeDC3bFca10eCb0890337fbdD1977aba84807a
1447transactionIndex     0
1448effectiveGasPrice    20000000000
1449hash                 0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067
1450type                 0
1451chainId              1
1452nonce                2
1453gasPrice             20000000000
1454gasLimit             90000
1455to                   0xdca8ce283150AB773BCbeB8d38289bdB5661dE1e
1456value                0
1457input                0x
1458r                    0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88
1459s                    0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e
1460yParity              0"
1461            .to_string();
1462        let txs = match block.transactions() {
1463            BlockTransactions::Full(txs) => txs,
1464            _ => panic!("not full transactions"),
1465        };
1466        let generated = txs[0].pretty();
1467        assert_eq!(generated.as_str(), output.as_str());
1468    }
1469
1470    #[test]
1471    fn uifmt_option_u64() {
1472        assert_eq!(None::<U64>.pretty(), "");
1473        assert_eq!(U64::from(100).pretty(), "100");
1474        assert_eq!(Some(U64::from(100)).pretty(), "100");
1475    }
1476
1477    #[test]
1478    fn uifmt_option_h64() {
1479        assert_eq!(None::<B256>.pretty(), "");
1480        assert_eq!(
1481            B256::with_last_byte(100).pretty(),
1482            "0x0000000000000000000000000000000000000000000000000000000000000064",
1483        );
1484        assert_eq!(
1485            Some(B256::with_last_byte(100)).pretty(),
1486            "0x0000000000000000000000000000000000000000000000000000000000000064",
1487        );
1488    }
1489
1490    #[test]
1491    fn uifmt_option_bytes() {
1492        assert_eq!(None::<Bytes>.pretty(), "");
1493        assert_eq!(
1494            Bytes::from_str("0x0000000000000000000000000000000000000000000000000000000000000064")
1495                .unwrap()
1496                .pretty(),
1497            "0x0000000000000000000000000000000000000000000000000000000000000064",
1498        );
1499        assert_eq!(
1500            Some(
1501                Bytes::from_str(
1502                    "0x0000000000000000000000000000000000000000000000000000000000000064"
1503                )
1504                .unwrap()
1505            )
1506            .pretty(),
1507            "0x0000000000000000000000000000000000000000000000000000000000000064",
1508        );
1509    }
1510
1511    #[test]
1512    fn test_pretty_tx_attr() {
1513        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":[]}"#;
1514        let block: <Ethereum as Network>::BlockResponse = serde_json::from_str(block).unwrap();
1515        let txs = match block.transactions() {
1516            BlockTransactions::Full(txes) => txes,
1517            _ => panic!("not full transactions"),
1518        };
1519
1520        assert_eq!(None, get_pretty_tx_attr::<Ethereum>(&txs[0], ""));
1521        assert_eq!(Some("3".to_string()), get_pretty_tx_attr::<Ethereum>(&txs[0], "blockNumber"));
1522        assert_eq!(
1523            Some("0xFdCeDC3bFca10eCb0890337fbdD1977aba84807a".to_string()),
1524            get_pretty_tx_attr::<Ethereum>(&txs[0], "from")
1525        );
1526        assert_eq!(Some("90000".to_string()), get_pretty_tx_attr::<Ethereum>(&txs[0], "gas"));
1527        assert_eq!(
1528            Some("20000000000".to_string()),
1529            get_pretty_tx_attr::<Ethereum>(&txs[0], "gasPrice")
1530        );
1531        assert_eq!(
1532            Some("0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067".to_string()),
1533            get_pretty_tx_attr::<Ethereum>(&txs[0], "hash")
1534        );
1535        assert_eq!(Some("0x".to_string()), get_pretty_tx_attr::<Ethereum>(&txs[0], "input"));
1536        assert_eq!(Some("2".to_string()), get_pretty_tx_attr::<Ethereum>(&txs[0], "nonce"));
1537        assert_eq!(
1538            Some("0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88".to_string()),
1539            get_pretty_tx_attr::<Ethereum>(&txs[0], "r")
1540        );
1541        assert_eq!(
1542            Some("0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e".to_string()),
1543            get_pretty_tx_attr::<Ethereum>(&txs[0], "s")
1544        );
1545        assert_eq!(
1546            Some("0xdca8ce283150AB773BCbeB8d38289bdB5661dE1e".into()),
1547            get_pretty_tx_attr::<Ethereum>(&txs[0], "to")
1548        );
1549        assert_eq!(
1550            Some("0".to_string()),
1551            get_pretty_tx_attr::<Ethereum>(&txs[0], "transactionIndex")
1552        );
1553        assert_eq!(Some("27".to_string()), get_pretty_tx_attr::<Ethereum>(&txs[0], "v"));
1554        assert_eq!(Some("0".to_string()), get_pretty_tx_attr::<Ethereum>(&txs[0], "value"));
1555    }
1556
1557    #[test]
1558    fn test_pretty_block_attr() {
1559        let json = serde_json::json!(
1560        {
1561            "baseFeePerGas": "0x7",
1562            "miner": "0x0000000000000000000000000000000000000001",
1563            "number": "0x1b4",
1564            "hash": "0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331",
1565            "parentHash": "0x9646252be9520f6e71339a8df9c55e4d7619deeb018d2a3f2d21fc165dde5eb5",
1566            "mixHash": "0x1010101010101010101010101010101010101010101010101010101010101010",
1567            "nonce": "0x0000000000000000",
1568            "sealFields": [
1569              "0xe04d296d2460cfb8472af2c5fd05b5a214109c25688d3704aed5484f9a7792f2",
1570              "0x0000000000000042"
1571            ],
1572            "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
1573            "logsBloom":  "0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331",
1574            "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
1575            "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
1576            "stateRoot": "0xd5855eb08b3387c0af375e9cdb6acfc05eb8f519e419b874b6ff2ffda7ed1dff",
1577            "difficulty": "0x1",
1578            "totalDifficulty": "0x27f07",
1579            "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000",
1580            "size": "0x27f07",
1581            "gasLimit": "0x9f759",
1582            "minGasPrice": "0x9f759",
1583            "gasUsed": "0x9f759",
1584            "timestamp": "0x54e34e8e",
1585            "transactions": [],
1586            "uncles": [],
1587          }
1588        );
1589
1590        let block: <Ethereum as Network>::BlockResponse = serde_json::from_value(json).unwrap();
1591
1592        assert_eq!(None, get_pretty_block_attr::<Ethereum>(&block, ""));
1593        assert_eq!(
1594            Some("7".to_string()),
1595            get_pretty_block_attr::<Ethereum>(&block, "baseFeePerGas")
1596        );
1597        assert_eq!(Some("1".to_string()), get_pretty_block_attr::<Ethereum>(&block, "difficulty"));
1598        assert_eq!(
1599            Some("0x0000000000000000000000000000000000000000000000000000000000000000".to_string()),
1600            get_pretty_block_attr::<Ethereum>(&block, "extraData")
1601        );
1602        assert_eq!(
1603            Some("653145".to_string()),
1604            get_pretty_block_attr::<Ethereum>(&block, "gasLimit")
1605        );
1606        assert_eq!(
1607            Some("653145".to_string()),
1608            get_pretty_block_attr::<Ethereum>(&block, "gasUsed")
1609        );
1610        assert_eq!(
1611            Some("0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331".to_string()),
1612            get_pretty_block_attr::<Ethereum>(&block, "hash")
1613        );
1614        assert_eq!(Some("0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331".to_string()), get_pretty_block_attr::<Ethereum>(&block, "logsBloom"));
1615        assert_eq!(
1616            Some("0x0000000000000000000000000000000000000001".to_string()),
1617            get_pretty_block_attr::<Ethereum>(&block, "miner")
1618        );
1619        assert_eq!(
1620            Some("0x1010101010101010101010101010101010101010101010101010101010101010".to_string()),
1621            get_pretty_block_attr::<Ethereum>(&block, "mixHash")
1622        );
1623        assert_eq!(
1624            Some("0x0000000000000000".to_string()),
1625            get_pretty_block_attr::<Ethereum>(&block, "nonce")
1626        );
1627        assert_eq!(Some("436".to_string()), get_pretty_block_attr::<Ethereum>(&block, "number"));
1628        assert_eq!(
1629            Some("0x9646252be9520f6e71339a8df9c55e4d7619deeb018d2a3f2d21fc165dde5eb5".to_string()),
1630            get_pretty_block_attr::<Ethereum>(&block, "parentHash")
1631        );
1632        assert_eq!(
1633            Some("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".to_string()),
1634            get_pretty_block_attr::<Ethereum>(&block, "transactionsRoot")
1635        );
1636        assert_eq!(
1637            Some("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".to_string()),
1638            get_pretty_block_attr::<Ethereum>(&block, "receiptsRoot")
1639        );
1640        assert_eq!(
1641            Some("0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347".to_string()),
1642            get_pretty_block_attr::<Ethereum>(&block, "sha3Uncles")
1643        );
1644        assert_eq!(Some("163591".to_string()), get_pretty_block_attr::<Ethereum>(&block, "size"));
1645        assert_eq!(
1646            Some("0xd5855eb08b3387c0af375e9cdb6acfc05eb8f519e419b874b6ff2ffda7ed1dff".to_string()),
1647            get_pretty_block_attr::<Ethereum>(&block, "stateRoot")
1648        );
1649        assert_eq!(
1650            Some("1424182926".to_string()),
1651            get_pretty_block_attr::<Ethereum>(&block, "timestamp")
1652        );
1653        assert_eq!(
1654            Some("163591".to_string()),
1655            get_pretty_block_attr::<Ethereum>(&block, "totalDifficulty")
1656        );
1657
1658        let pretty = pretty_generic_header_response(block.header());
1659        assert!(pretty.contains("difficulty           1"), "{pretty}");
1660        assert!(pretty.contains("totalDifficulty      163591"), "{pretty}");
1661    }
1662
1663    #[test]
1664    fn test_receipt_other_fields_alignment() {
1665        let receipt_json = serde_json::json!(
1666        {
1667          "status": "0x1",
1668          "cumulativeGasUsed": "0x74e483",
1669          "logs": [],
1670          "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
1671          "type": "0x2",
1672          "transactionHash": "0x91181b0dca3b29aa136eeb2f536be5ce7b0aebc949be1c44b5509093c516097d",
1673          "transactionIndex": "0x10",
1674          "blockHash": "0x54bafb12e8cea9bb355fbf03a4ac49e42a2a1a80fa6cf4364b342e2de6432b5d",
1675          "blockNumber": "0x7b1ab93",
1676          "gasUsed": "0xc222",
1677          "effectiveGasPrice": "0x18961",
1678          "from": "0x2d815240a61731c75fa01b2793e1d3ed09f289d0",
1679          "to": "0x4200000000000000000000000000000000000000",
1680          "contractAddress": null,
1681          "l1BaseFeeScalar": "0x146b",
1682          "l1BlobBaseFee": "0x6a83078",
1683          "l1BlobBaseFeeScalar": "0xf79c5",
1684          "l1Fee": "0x51a9af7fd3",
1685          "l1GasPrice": "0x972fe4acc",
1686          "l1GasUsed": "0x640"
1687        });
1688
1689        let receipt: AnyTransactionReceipt = serde_json::from_value(receipt_json).unwrap();
1690        let formatted = receipt.pretty();
1691
1692        let expected = r#"
1693blockHash            0x54bafb12e8cea9bb355fbf03a4ac49e42a2a1a80fa6cf4364b342e2de6432b5d
1694blockNumber          129084307
1695contractAddress      
1696cumulativeGasUsed    7660675
1697effectiveGasPrice    100705
1698from                 0x2D815240A61731c75Fa01b2793E1D3eD09F289d0
1699gasUsed              49698
1700logs                 []
1701logsBloom            0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
1702root                 
1703status               1 (success)
1704transactionHash      0x91181b0dca3b29aa136eeb2f536be5ce7b0aebc949be1c44b5509093c516097d
1705transactionIndex     16
1706type                 2
1707blobGasPrice         
1708blobGasUsed          
1709to                   0x4200000000000000000000000000000000000000
1710l1BaseFeeScalar      5227
1711l1BlobBaseFee        111685752
1712l1BlobBaseFeeScalar  1014213
1713l1Fee                350739202003
1714l1GasPrice           40583973580
1715l1GasUsed            1600
1716"#;
1717
1718        assert_eq!(formatted.trim(), expected.trim());
1719    }
1720
1721    #[test]
1722    fn test_uifmt_for_signed_authorization() {
1723        let inner = Authorization {
1724            chain_id: U256::from(1),
1725            address: "0x000000000000000000000000000000000000dead".parse::<Address>().unwrap(),
1726            nonce: 42,
1727        };
1728        let signed_authorization =
1729            SignedAuthorization::new_unchecked(inner, 1, U256::from(20), U256::from(30));
1730
1731        assert_eq!(
1732            signed_authorization.pretty(),
1733            r#"{recoveredAuthority: 0xf3eaBD0de6Ca1aE7fC4D81FfD6C9a40e5D5D7e30, signedAuthority: {"chainId":"0x1","address":"0x000000000000000000000000000000000000dead","nonce":"0x2a","yParity":"0x1","r":"0x14","s":"0x1e"}}"#
1734        );
1735    }
1736
1737    #[test]
1738    fn can_pretty_print_tempo_tx() {
1739        let s = r#"{
1740            "type":"0x76",
1741            "chainId":"0xa5bd",
1742            "feeToken":"0x20c0000000000000000000000000000000000001",
1743            "maxPriorityFeePerGas":"0x0",
1744            "maxFeePerGas":"0x2cb417800",
1745            "gas":"0x2d178",
1746            "calls":[
1747                {
1748                    "data":null,
1749                    "input":"0x095ea7b3000000000000000000000000dec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000989680",
1750                    "to":"0x20c0000000000000000000000000000000000000",
1751                    "value":"0x0"
1752                },
1753                {
1754                    "data":null,
1755                    "input":"0xf8856c0f00000000000000000000000020c000000000000000000000000000000000000000000000000000000000000020c00000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000989680000000000000000000000000000000000000000000000000000000000097d330",
1756                    "to":"0xdec0000000000000000000000000000000000000",
1757                    "value":"0x0"
1758                }
1759            ],
1760            "accessList":[],
1761            "nonceKey":"0x0",
1762            "nonce":"0x0",
1763            "feePayerSignature":null,
1764            "validBefore":null,
1765            "validAfter":null,
1766            "keyAuthorization":null,
1767            "aaAuthorizationList":[],
1768            "signature":{
1769                "pubKeyX":"0xaacc80b21e45fb11f349424dce3a2f23547f60c0ff2f8bcaede2a247545ce8dd",
1770                "pubKeyY":"0x87abf0dbb7a5c9507efae2e43833356651b45ac576c2e61cec4e9c0f41fcbf6e",
1771                "r":"0xcfd45c3b19745a42f80b134dcb02a8ba099a0e4e7be1984da54734aa81d8f29f",
1772                "s":"0x74bb9170ae6d25bd510c83fe35895ee5712efe13980a5edc8094c534e23af85e",
1773                "type":"webAuthn",
1774                "webauthnData":"0x7b98b7a8e6c68d7eac741a52e6fdae0560ce3c16ef5427ad46d7a54d0ed86dd41d000000007b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a2238453071464a7a50585167546e645473643649456659457776323173516e626966374c4741776e4b43626b222c226f726967696e223a2268747470733a2f2f74656d706f2d6465782e76657263656c2e617070222c2263726f73734f726967696e223a66616c73657d"
1775            },
1776            "hash":"0x6d6d8c102064e6dee44abad2024a8b1d37959230baab80e70efbf9b0c739c4fd",
1777            "blockHash":"0xc82b23589ceef5341ed307d33554714db6f9eefd4187a9ca8abc910a325b4689",
1778            "blockNumber":"0x321fde",
1779            "transactionIndex":"0x0",
1780            "from":"0x566ff0f4a6114f8072ecdc8a7a8a13d8d0c6b45f",
1781            "gasPrice":"0x2540be400"
1782        }"#;
1783
1784        let tx: Transaction<TempoTxEnvelope> = serde_json::from_str(s).unwrap();
1785
1786        assert_eq!(
1787            tx.pretty().trim(),
1788            r#"
1789blockHash            0xc82b23589ceef5341ed307d33554714db6f9eefd4187a9ca8abc910a325b4689
1790blockNumber          3284958
1791from                 0x566Ff0f4a6114F8072ecDC8A7A8A13d8d0C6B45F
1792transactionIndex     0
1793effectiveGasPrice    10000000000
1794hash                 0x6d6d8c102064e6dee44abad2024a8b1d37959230baab80e70efbf9b0c739c4fd
1795type                 118
1796chainId              42429
1797feeToken             0x20C0000000000000000000000000000000000001
1798maxPriorityFeePerGas 0
1799maxFeePerGas         12000000000
1800gasLimit             184696
1801calls                [
1802	to: 0x20C0000000000000000000000000000000000000, value: 0, input: 0x095ea7b3000000000000000000000000dec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000989680
1803	to: 0xDEc0000000000000000000000000000000000000, value: 0, input: 0xf8856c0f00000000000000000000000020c000000000000000000000000000000000000000000000000000000000000020c00000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000989680000000000000000000000000000000000000000000000000000000000097d330
1804]
1805accessList           []
1806nonceKey             0
1807nonce                0
1808feePayerSignature    
1809validBefore          
1810validAfter           
1811tempoSignature       {"type":"webAuthn","r":"0xcfd45c3b19745a42f80b134dcb02a8ba099a0e4e7be1984da54734aa81d8f29f","s":"0x74bb9170ae6d25bd510c83fe35895ee5712efe13980a5edc8094c534e23af85e","pubKeyX":"0xaacc80b21e45fb11f349424dce3a2f23547f60c0ff2f8bcaede2a247545ce8dd","pubKeyY":"0x87abf0dbb7a5c9507efae2e43833356651b45ac576c2e61cec4e9c0f41fcbf6e","webauthnData":"0x7b98b7a8e6c68d7eac741a52e6fdae0560ce3c16ef5427ad46d7a54d0ed86dd41d000000007b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a2238453071464a7a50585167546e645473643649456659457776323173516e626966374c4741776e4b43626b222c226f726967696e223a2268747470733a2f2f74656d706f2d6465782e76657263656c2e617070222c2263726f73734f726967696e223a66616c73657d"}
1812"#
1813                .trim()
1814        );
1815    }
1816
1817    #[test]
1818    fn can_pretty_print_tempo_receipt() {
1819        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"}"#;
1820
1821        let tx: TempoTransactionReceipt = serde_json::from_str(s).unwrap();
1822
1823        assert_eq!(
1824            tx.pretty().trim(),
1825            r#"
1826blockHash            0x860f788b251ece768e63b0d3906d156f652d843848b71c7fe81faacd49139d66
1827blockNumber          6922711
1828contractAddress      
1829cumulativeGasUsed    24565748
1830effectiveGasPrice    20000000002
1831from                 0xa70ab0448e66cD77995bfBBa5c5b64B41a85F3fd
1832gasUsed              52330
1833logs                 [{"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}]
1834logsBloom            0x00000000000000000000000000000000000000000000010000000000000000000000000000000000000000000100000000000000000000000000000000040008000004200000000000000008000000000000000000040000000000000400000000000002000000000000000000000000000000000000000000000010000000000000000000000000000000000020000000000000800000000000000000000000000020000000000000000000000000000000000400000000000000000000000000000002000000000000000400000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000
1835root                 
1836status               true
1837transactionHash      0x04548a0ea27e2cccc1479af3c2ff02da4d4d3ea46af8e8d7edaa49f6ea27073f
1838transactionIndex     99
1839type                 118
1840feePayer             0xa70ab0448e66cD77995bfBBa5c5b64B41a85F3fd
1841feeToken             0x20C0000000000000000000000000000000000003
1842to                   0x20C0000000000000000000000000000000000000
1843"#
1844                .trim()
1845        );
1846    }
1847
1848    #[test]
1849    fn test_ethereum_receipt_uifmt() {
1850        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}"#;
1851        let receipt: TransactionReceipt = serde_json::from_str(s).unwrap();
1852
1853        let pretty_output = receipt.pretty();
1854
1855        assert!(pretty_output.contains("blockHash"));
1856        assert!(pretty_output.contains("blockNumber"));
1857        assert!(pretty_output.contains("status"));
1858        assert!(pretty_output.contains("gasUsed"));
1859        assert!(pretty_output.contains("transactionHash"));
1860        assert!(pretty_output.contains("type"));
1861        assert!(
1862            pretty_output
1863                .contains("0x1234567890123456789012345678901234567890123456789012345678901234")
1864        );
1865        assert!(pretty_output.contains("1 (success)"));
1866        assert!(pretty_output.contains("0x0987654321098765432109876543210987654321"));
1867    }
1868
1869    #[test]
1870    fn test_get_pretty_receipt_attr() {
1871        let receipt_json = serde_json::json!({
1872            "type": "0x2",
1873            "status": "0x1",
1874            "cumulativeGasUsed": "0x5208",
1875            "logs": [],
1876            "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
1877            "transactionHash": "0x1234567890123456789012345678901234567890123456789012345678901234",
1878            "transactionIndex": "0x0",
1879            "blockHash": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd",
1880            "blockNumber": "0x1",
1881            "gasUsed": "0x5208",
1882            "effectiveGasPrice": "0x3b9aca00",
1883            "from": "0x1234567890123456789012345678901234567890",
1884            "to": "0x0987654321098765432109876543210987654321",
1885            "contractAddress": null
1886        });
1887
1888        let receipt: <Ethereum as Network>::ReceiptResponse =
1889            serde_json::from_value(receipt_json).unwrap();
1890
1891        // Test basic receipt attributes
1892        assert_eq!(
1893            Some("0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd".to_string()),
1894            get_pretty_receipt_attr::<Ethereum>(&receipt, "blockHash")
1895        );
1896        assert_eq!(
1897            Some("1".to_string()),
1898            get_pretty_receipt_attr::<Ethereum>(&receipt, "blockNumber")
1899        );
1900        assert_eq!(
1901            Some("0x1234567890123456789012345678901234567890123456789012345678901234".to_string()),
1902            get_pretty_receipt_attr::<Ethereum>(&receipt, "transactionHash")
1903        );
1904        assert_eq!(
1905            Some("21000".to_string()),
1906            get_pretty_receipt_attr::<Ethereum>(&receipt, "gasUsed")
1907        );
1908        assert_eq!(
1909            Some("true".to_string()),
1910            get_pretty_receipt_attr::<Ethereum>(&receipt, "status")
1911        );
1912        assert_eq!(
1913            Some("EIP-1559".to_string()),
1914            get_pretty_receipt_attr::<Ethereum>(&receipt, "type")
1915        );
1916        assert_eq!(Some("[]".to_string()), get_pretty_receipt_attr::<Ethereum>(&receipt, "logs"));
1917        assert!(get_pretty_receipt_attr::<Ethereum>(&receipt, "logsBloom").is_some());
1918    }
1919}