Skip to main content

foundry_common_fmt/
ui.rs

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