Skip to main content

anvil_core/eth/transaction/
mod.rs

1//! Transaction related types
2use alloy_consensus::{
3    Receipt, ReceiptEnvelope, ReceiptWithBloom, Signed, Transaction, TxEip1559, TxEip2930,
4    TxEnvelope, TxLegacy, TxReceipt, Typed2718,
5    transaction::{
6        Recovered, TxEip7702,
7        eip4844::{TxEip4844, TxEip4844Variant, TxEip4844WithSidecar},
8    },
9};
10use alloy_eips::eip2718::{Decodable2718, Eip2718Error, Encodable2718};
11use alloy_network::{AnyReceiptEnvelope, AnyRpcTransaction, AnyTransactionReceipt, AnyTxEnvelope};
12use alloy_primitives::{Address, B256, Bloom, Bytes, Log, Signature, TxHash, TxKind, U64, U256};
13use alloy_rlp::{Decodable, Encodable, Header, length_of_length};
14use alloy_rpc_types::{
15    AccessList, ConversionError, Transaction as RpcTransaction, TransactionReceipt,
16    request::TransactionRequest, trace::otterscan::OtsReceipt,
17};
18use alloy_serde::{OtherFields, WithOtherFields};
19use bytes::BufMut;
20use foundry_evm::traces::CallTraceNode;
21use op_alloy_consensus::{DEPOSIT_TX_TYPE_ID, TxDeposit};
22use op_revm::{OpTransaction, transaction::deposit::DepositTransactionParts};
23use revm::{context::TxEnv, interpreter::InstructionResult};
24use serde::{Deserialize, Serialize};
25use std::ops::{Deref, Mul};
26
27/// Converts a [TransactionRequest] into a [TypedTransactionRequest].
28/// Should be removed once the call builder abstraction for providers is in place.
29pub fn transaction_request_to_typed(
30    tx: WithOtherFields<TransactionRequest>,
31) -> Option<TypedTransactionRequest> {
32    let WithOtherFields::<TransactionRequest> {
33        inner:
34            TransactionRequest {
35                from,
36                to,
37                gas_price,
38                max_fee_per_gas,
39                max_priority_fee_per_gas,
40                max_fee_per_blob_gas,
41                blob_versioned_hashes,
42                gas,
43                value,
44                input,
45                nonce,
46                access_list,
47                sidecar,
48                transaction_type,
49                authorization_list,
50                chain_id: _,
51            },
52        other,
53    } = tx;
54
55    // Special case: OP-stack deposit tx
56    if transaction_type == Some(0x7E) || has_optimism_fields(&other) {
57        let mint = other.get_deserialized::<U256>("mint")?.map(|m| m.to::<u128>()).ok()?;
58
59        return Some(TypedTransactionRequest::Deposit(TxDeposit {
60            from: from.unwrap_or_default(),
61            source_hash: other.get_deserialized::<B256>("sourceHash")?.ok()?,
62            to: to.unwrap_or_default(),
63            mint,
64            value: value.unwrap_or_default(),
65            gas_limit: gas.unwrap_or_default(),
66            is_system_transaction: other.get_deserialized::<bool>("isSystemTx")?.ok()?,
67            input: input.into_input().unwrap_or_default(),
68        }));
69    }
70
71    // EIP7702
72    if transaction_type == Some(4) || authorization_list.is_some() {
73        return Some(TypedTransactionRequest::EIP7702(TxEip7702 {
74            nonce: nonce.unwrap_or_default(),
75            max_fee_per_gas: max_fee_per_gas.unwrap_or_default(),
76            max_priority_fee_per_gas: max_priority_fee_per_gas.unwrap_or_default(),
77            gas_limit: gas.unwrap_or_default(),
78            value: value.unwrap_or(U256::ZERO),
79            input: input.into_input().unwrap_or_default(),
80            // requires to
81            to: to?.into_to()?,
82            chain_id: 0,
83            access_list: access_list.unwrap_or_default(),
84            authorization_list: authorization_list.unwrap_or_default(),
85        }));
86    }
87
88    match (
89        transaction_type,
90        gas_price,
91        max_fee_per_gas,
92        max_priority_fee_per_gas,
93        access_list.as_ref(),
94        max_fee_per_blob_gas,
95        blob_versioned_hashes.as_ref(),
96        sidecar.as_ref(),
97        to,
98    ) {
99        // legacy transaction
100        (Some(0), _, None, None, None, None, None, None, _)
101        | (None, Some(_), None, None, None, None, None, None, _) => {
102            Some(TypedTransactionRequest::Legacy(TxLegacy {
103                nonce: nonce.unwrap_or_default(),
104                gas_price: gas_price.unwrap_or_default(),
105                gas_limit: gas.unwrap_or_default(),
106                value: value.unwrap_or(U256::ZERO),
107                input: input.into_input().unwrap_or_default(),
108                to: to.unwrap_or_default(),
109                chain_id: None,
110            }))
111        }
112        // EIP2930
113        (Some(1), _, None, None, _, None, None, None, _)
114        | (None, _, None, None, Some(_), None, None, None, _) => {
115            Some(TypedTransactionRequest::EIP2930(TxEip2930 {
116                nonce: nonce.unwrap_or_default(),
117                gas_price: gas_price.unwrap_or_default(),
118                gas_limit: gas.unwrap_or_default(),
119                value: value.unwrap_or(U256::ZERO),
120                input: input.into_input().unwrap_or_default(),
121                to: to.unwrap_or_default(),
122                chain_id: 0,
123                access_list: access_list.unwrap_or_default(),
124            }))
125        }
126        // EIP1559
127        (Some(2), None, _, _, _, _, None, None, _)
128        | (None, None, Some(_), _, _, _, None, None, _)
129        | (None, None, _, Some(_), _, _, None, None, _)
130        | (None, None, None, None, None, _, None, None, _) => {
131            // Empty fields fall back to the canonical transaction schema.
132            Some(TypedTransactionRequest::EIP1559(TxEip1559 {
133                nonce: nonce.unwrap_or_default(),
134                max_fee_per_gas: max_fee_per_gas.unwrap_or_default(),
135                max_priority_fee_per_gas: max_priority_fee_per_gas.unwrap_or_default(),
136                gas_limit: gas.unwrap_or_default(),
137                value: value.unwrap_or(U256::ZERO),
138                input: input.into_input().unwrap_or_default(),
139                to: to.unwrap_or_default(),
140                chain_id: 0,
141                access_list: access_list.unwrap_or_default(),
142            }))
143        }
144        // EIP4844
145        (Some(3), None, _, _, _, _, Some(_), _, to) => {
146            let tx = TxEip4844 {
147                nonce: nonce.unwrap_or_default(),
148                max_fee_per_gas: max_fee_per_gas.unwrap_or_default(),
149                max_priority_fee_per_gas: max_priority_fee_per_gas.unwrap_or_default(),
150                max_fee_per_blob_gas: max_fee_per_blob_gas.unwrap_or_default(),
151                gas_limit: gas.unwrap_or_default(),
152                value: value.unwrap_or(U256::ZERO),
153                input: input.into_input().unwrap_or_default(),
154                to: match to.unwrap_or(TxKind::Create) {
155                    TxKind::Call(to) => to,
156                    TxKind::Create => Address::ZERO,
157                },
158                chain_id: 0,
159                access_list: access_list.unwrap_or_default(),
160                blob_versioned_hashes: blob_versioned_hashes.unwrap_or_default(),
161            };
162
163            if let Some(sidecar) = sidecar {
164                Some(TypedTransactionRequest::EIP4844(TxEip4844Variant::TxEip4844WithSidecar(
165                    TxEip4844WithSidecar::from_tx_and_sidecar(tx, sidecar),
166                )))
167            } else {
168                Some(TypedTransactionRequest::EIP4844(TxEip4844Variant::TxEip4844(tx)))
169            }
170        }
171        _ => None,
172    }
173}
174
175pub fn has_optimism_fields(other: &OtherFields) -> bool {
176    other.contains_key("sourceHash")
177        && other.contains_key("mint")
178        && other.contains_key("isSystemTx")
179}
180
181#[derive(Clone, Debug, PartialEq, Eq)]
182pub enum TypedTransactionRequest {
183    Legacy(TxLegacy),
184    EIP2930(TxEip2930),
185    EIP1559(TxEip1559),
186    EIP7702(TxEip7702),
187    EIP4844(TxEip4844Variant),
188    Deposit(TxDeposit),
189}
190
191/// A wrapper for [TypedTransaction] that allows impersonating accounts.
192///
193/// This is a helper that carries the `impersonated` sender so that the right hash
194/// [TypedTransaction::impersonated_hash] can be created.
195#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
196pub struct MaybeImpersonatedTransaction {
197    pub transaction: TypedTransaction,
198    pub impersonated_sender: Option<Address>,
199}
200
201impl MaybeImpersonatedTransaction {
202    /// Creates a new wrapper for the given transaction
203    pub fn new(transaction: TypedTransaction) -> Self {
204        Self { transaction, impersonated_sender: None }
205    }
206
207    /// Creates a new impersonated transaction wrapper using the given sender
208    pub fn impersonated(transaction: TypedTransaction, impersonated_sender: Address) -> Self {
209        Self { transaction, impersonated_sender: Some(impersonated_sender) }
210    }
211
212    /// Recovers the Ethereum address which was used to sign the transaction.
213    pub fn recover(&self) -> Result<Address, alloy_primitives::SignatureError> {
214        if let Some(sender) = self.impersonated_sender {
215            return Ok(sender);
216        }
217        self.transaction.recover()
218    }
219
220    /// Returns whether the transaction is impersonated
221    pub fn is_impersonated(&self) -> bool {
222        self.impersonated_sender.is_some()
223    }
224
225    /// Returns the hash of the transaction
226    pub fn hash(&self) -> B256 {
227        if let Some(sender) = self.impersonated_sender {
228            return self.transaction.impersonated_hash(sender);
229        }
230        self.transaction.hash()
231    }
232}
233
234impl Encodable for MaybeImpersonatedTransaction {
235    fn encode(&self, out: &mut dyn bytes::BufMut) {
236        self.transaction.encode(out)
237    }
238}
239
240impl From<MaybeImpersonatedTransaction> for TypedTransaction {
241    fn from(value: MaybeImpersonatedTransaction) -> Self {
242        value.transaction
243    }
244}
245
246impl From<TypedTransaction> for MaybeImpersonatedTransaction {
247    fn from(value: TypedTransaction) -> Self {
248        Self::new(value)
249    }
250}
251
252impl Decodable for MaybeImpersonatedTransaction {
253    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
254        TypedTransaction::decode(buf).map(Self::new)
255    }
256}
257
258impl AsRef<TypedTransaction> for MaybeImpersonatedTransaction {
259    fn as_ref(&self) -> &TypedTransaction {
260        &self.transaction
261    }
262}
263
264impl Deref for MaybeImpersonatedTransaction {
265    type Target = TypedTransaction;
266
267    fn deref(&self) -> &Self::Target {
268        &self.transaction
269    }
270}
271
272impl From<MaybeImpersonatedTransaction> for RpcTransaction {
273    fn from(value: MaybeImpersonatedTransaction) -> Self {
274        let hash = value.hash();
275        let sender = value.recover().unwrap_or_default();
276        to_alloy_transaction_with_hash_and_sender(value.transaction, hash, sender)
277    }
278}
279
280pub fn to_alloy_transaction_with_hash_and_sender(
281    transaction: TypedTransaction,
282    hash: B256,
283    from: Address,
284) -> RpcTransaction {
285    match transaction {
286        TypedTransaction::Legacy(t) => {
287            let (tx, sig, _) = t.into_parts();
288            RpcTransaction {
289                block_hash: None,
290                block_number: None,
291                transaction_index: None,
292                effective_gas_price: None,
293                inner: Recovered::new_unchecked(
294                    TxEnvelope::Legacy(Signed::new_unchecked(tx, sig, hash)),
295                    from,
296                ),
297            }
298        }
299        TypedTransaction::EIP2930(t) => {
300            let (tx, sig, _) = t.into_parts();
301            RpcTransaction {
302                block_hash: None,
303                block_number: None,
304                transaction_index: None,
305                effective_gas_price: None,
306                inner: Recovered::new_unchecked(
307                    TxEnvelope::Eip2930(Signed::new_unchecked(tx, sig, hash)),
308                    from,
309                ),
310            }
311        }
312        TypedTransaction::EIP1559(t) => {
313            let (tx, sig, _) = t.into_parts();
314            RpcTransaction {
315                block_hash: None,
316                block_number: None,
317                transaction_index: None,
318                effective_gas_price: None,
319                inner: Recovered::new_unchecked(
320                    TxEnvelope::Eip1559(Signed::new_unchecked(tx, sig, hash)),
321                    from,
322                ),
323            }
324        }
325        TypedTransaction::EIP4844(t) => {
326            let (tx, sig, _) = t.into_parts();
327            RpcTransaction {
328                block_hash: None,
329                block_number: None,
330                transaction_index: None,
331                effective_gas_price: None,
332                inner: Recovered::new_unchecked(
333                    TxEnvelope::Eip4844(Signed::new_unchecked(tx, sig, hash)),
334                    from,
335                ),
336            }
337        }
338        TypedTransaction::EIP7702(t) => {
339            let (tx, sig, _) = t.into_parts();
340            RpcTransaction {
341                block_hash: None,
342                block_number: None,
343                transaction_index: None,
344                effective_gas_price: None,
345                inner: Recovered::new_unchecked(
346                    TxEnvelope::Eip7702(Signed::new_unchecked(tx, sig, hash)),
347                    from,
348                ),
349            }
350        }
351        TypedTransaction::Deposit(_t) => {
352            unreachable!("cannot reach here, handled in `transaction_build` ")
353        }
354    }
355}
356
357/// Queued transaction
358#[derive(Clone, Debug, PartialEq, Eq)]
359pub struct PendingTransaction {
360    /// The actual transaction
361    pub transaction: MaybeImpersonatedTransaction,
362    /// the recovered sender of this transaction
363    sender: Address,
364    /// hash of `transaction`, so it can easily be reused with encoding and hashing again
365    hash: TxHash,
366}
367
368impl PendingTransaction {
369    pub fn new(transaction: TypedTransaction) -> Result<Self, alloy_primitives::SignatureError> {
370        let sender = transaction.recover()?;
371        let hash = transaction.hash();
372        Ok(Self { transaction: MaybeImpersonatedTransaction::new(transaction), sender, hash })
373    }
374
375    pub fn with_impersonated(transaction: TypedTransaction, sender: Address) -> Self {
376        let hash = transaction.impersonated_hash(sender);
377        Self {
378            transaction: MaybeImpersonatedTransaction::impersonated(transaction, sender),
379            sender,
380            hash,
381        }
382    }
383
384    pub fn nonce(&self) -> u64 {
385        self.transaction.nonce()
386    }
387
388    pub fn hash(&self) -> &TxHash {
389        &self.hash
390    }
391
392    pub fn sender(&self) -> &Address {
393        &self.sender
394    }
395
396    /// Converts the [PendingTransaction] into the [TxEnv] context that [`revm`](foundry_evm)
397    /// expects.
398    ///
399    /// Base [`TxEnv`] is encapsulated in the [`op_revm::OpTransaction`]
400    pub fn to_revm_tx_env(&self) -> OpTransaction<TxEnv> {
401        fn transact_to(kind: &TxKind) -> TxKind {
402            match kind {
403                TxKind::Call(c) => TxKind::Call(*c),
404                TxKind::Create => TxKind::Create,
405            }
406        }
407
408        let caller = *self.sender();
409        match &self.transaction.transaction {
410            TypedTransaction::Legacy(tx) => {
411                let chain_id = tx.tx().chain_id;
412                let TxLegacy { nonce, gas_price, gas_limit, value, to, input, .. } = tx.tx();
413                OpTransaction::new(TxEnv {
414                    caller,
415                    kind: transact_to(to),
416                    data: input.clone(),
417                    chain_id,
418                    nonce: *nonce,
419                    value: (*value),
420                    gas_price: *gas_price,
421                    gas_priority_fee: None,
422                    gas_limit: *gas_limit,
423                    access_list: vec![].into(),
424                    tx_type: 0,
425                    ..Default::default()
426                })
427            }
428            TypedTransaction::EIP2930(tx) => {
429                let TxEip2930 {
430                    chain_id,
431                    nonce,
432                    gas_price,
433                    gas_limit,
434                    to,
435                    value,
436                    input,
437                    access_list,
438                    ..
439                } = tx.tx();
440                OpTransaction::new(TxEnv {
441                    caller,
442                    kind: transact_to(to),
443                    data: input.clone(),
444                    chain_id: Some(*chain_id),
445                    nonce: *nonce,
446                    value: *value,
447                    gas_price: *gas_price,
448                    gas_priority_fee: None,
449                    gas_limit: *gas_limit,
450                    access_list: access_list.clone(),
451                    tx_type: 1,
452                    ..Default::default()
453                })
454            }
455            TypedTransaction::EIP1559(tx) => {
456                let TxEip1559 {
457                    chain_id,
458                    nonce,
459                    max_priority_fee_per_gas,
460                    max_fee_per_gas,
461                    gas_limit,
462                    to,
463                    value,
464                    input,
465                    access_list,
466                    ..
467                } = tx.tx();
468                OpTransaction::new(TxEnv {
469                    caller,
470                    kind: transact_to(to),
471                    data: input.clone(),
472                    chain_id: Some(*chain_id),
473                    nonce: *nonce,
474                    value: *value,
475                    gas_price: *max_fee_per_gas,
476                    gas_priority_fee: Some(*max_priority_fee_per_gas),
477                    gas_limit: *gas_limit,
478                    access_list: access_list.clone(),
479                    tx_type: 2,
480                    ..Default::default()
481                })
482            }
483            TypedTransaction::EIP4844(tx) => {
484                let TxEip4844 {
485                    chain_id,
486                    nonce,
487                    max_fee_per_blob_gas,
488                    max_fee_per_gas,
489                    max_priority_fee_per_gas,
490                    gas_limit,
491                    to,
492                    value,
493                    input,
494                    access_list,
495                    blob_versioned_hashes,
496                    ..
497                } = tx.tx().tx();
498                OpTransaction::new(TxEnv {
499                    caller,
500                    kind: TxKind::Call(*to),
501                    data: input.clone(),
502                    chain_id: Some(*chain_id),
503                    nonce: *nonce,
504                    value: *value,
505                    gas_price: *max_fee_per_gas,
506                    gas_priority_fee: Some(*max_priority_fee_per_gas),
507                    max_fee_per_blob_gas: *max_fee_per_blob_gas,
508                    blob_hashes: blob_versioned_hashes.clone(),
509                    gas_limit: *gas_limit,
510                    access_list: access_list.clone(),
511                    tx_type: 3,
512                    ..Default::default()
513                })
514            }
515            TypedTransaction::EIP7702(tx) => {
516                let TxEip7702 {
517                    chain_id,
518                    nonce,
519                    gas_limit,
520                    max_fee_per_gas,
521                    max_priority_fee_per_gas,
522                    to,
523                    value,
524                    access_list,
525                    authorization_list,
526                    input,
527                } = tx.tx();
528
529                let mut tx = TxEnv {
530                    caller,
531                    kind: TxKind::Call(*to),
532                    data: input.clone(),
533                    chain_id: Some(*chain_id),
534                    nonce: *nonce,
535                    value: *value,
536                    gas_price: *max_fee_per_gas,
537                    gas_priority_fee: Some(*max_priority_fee_per_gas),
538                    gas_limit: *gas_limit,
539                    access_list: access_list.clone(),
540                    tx_type: 4,
541                    ..Default::default()
542                };
543                tx.set_signed_authorization(authorization_list.clone());
544
545                OpTransaction::new(tx)
546            }
547            TypedTransaction::Deposit(tx) => {
548                let chain_id = tx.chain_id();
549                let TxDeposit {
550                    source_hash,
551                    to,
552                    mint,
553                    value,
554                    gas_limit,
555                    is_system_transaction,
556                    input,
557                    ..
558                } = tx;
559
560                let base = TxEnv {
561                    caller,
562                    kind: transact_to(to),
563                    data: input.clone(),
564                    chain_id,
565                    nonce: 0,
566                    value: *value,
567                    gas_price: 0,
568                    gas_priority_fee: None,
569                    gas_limit: { *gas_limit },
570                    access_list: vec![].into(),
571                    tx_type: DEPOSIT_TX_TYPE_ID,
572                    ..Default::default()
573                };
574
575                let deposit = DepositTransactionParts {
576                    source_hash: *source_hash,
577                    mint: Some(*mint),
578                    is_system_transaction: *is_system_transaction,
579                };
580
581                OpTransaction { base, deposit, enveloped_tx: None }
582            }
583        }
584    }
585}
586
587/// Container type for signed, typed transactions.
588#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
589pub enum TypedTransaction {
590    /// Legacy transaction type
591    Legacy(Signed<TxLegacy>),
592    /// EIP-2930 transaction
593    EIP2930(Signed<TxEip2930>),
594    /// EIP-1559 transaction
595    EIP1559(Signed<TxEip1559>),
596    /// EIP-4844 transaction
597    EIP4844(Signed<TxEip4844Variant>),
598    /// EIP-7702 transaction
599    EIP7702(Signed<TxEip7702>),
600    /// op-stack deposit transaction
601    Deposit(TxDeposit),
602}
603
604impl TryFrom<AnyRpcTransaction> for TypedTransaction {
605    type Error = ConversionError;
606
607    fn try_from(value: AnyRpcTransaction) -> Result<Self, Self::Error> {
608        let WithOtherFields { inner, .. } = value.0;
609        let from = inner.inner.signer();
610        match inner.inner.into_inner() {
611            AnyTxEnvelope::Ethereum(tx) => match tx {
612                TxEnvelope::Legacy(tx) => Ok(Self::Legacy(tx)),
613                TxEnvelope::Eip2930(tx) => Ok(Self::EIP2930(tx)),
614                TxEnvelope::Eip1559(tx) => Ok(Self::EIP1559(tx)),
615                TxEnvelope::Eip4844(tx) => Ok(Self::EIP4844(tx)),
616                TxEnvelope::Eip7702(tx) => Ok(Self::EIP7702(tx)),
617            },
618            AnyTxEnvelope::Unknown(mut tx) => {
619                // Try to convert to deposit transaction
620                if tx.ty() == DEPOSIT_TX_TYPE_ID {
621                    tx.inner.fields.insert("from".to_string(), serde_json::to_value(from).unwrap());
622                    let deposit_tx =
623                        tx.inner.fields.deserialize_into::<TxDeposit>().map_err(|e| {
624                            ConversionError::Custom(format!(
625                                "Failed to deserialize deposit tx: {e}"
626                            ))
627                        })?;
628
629                    return Ok(Self::Deposit(deposit_tx));
630                };
631
632                Err(ConversionError::Custom("UnknownTxType".to_string()))
633            }
634        }
635    }
636}
637
638impl TypedTransaction {
639    /// Returns true if the transaction uses dynamic fees: EIP1559, EIP4844 or EIP7702
640    pub fn is_dynamic_fee(&self) -> bool {
641        matches!(self, Self::EIP1559(_) | Self::EIP4844(_) | Self::EIP7702(_))
642    }
643
644    pub fn gas_price(&self) -> u128 {
645        match self {
646            Self::Legacy(tx) => tx.tx().gas_price,
647            Self::EIP2930(tx) => tx.tx().gas_price,
648            Self::EIP1559(tx) => tx.tx().max_fee_per_gas,
649            Self::EIP4844(tx) => tx.tx().tx().max_fee_per_gas,
650            Self::EIP7702(tx) => tx.tx().max_fee_per_gas,
651            Self::Deposit(_) => 0,
652        }
653    }
654
655    pub fn gas_limit(&self) -> u64 {
656        match self {
657            Self::Legacy(tx) => tx.tx().gas_limit,
658            Self::EIP2930(tx) => tx.tx().gas_limit,
659            Self::EIP1559(tx) => tx.tx().gas_limit,
660            Self::EIP4844(tx) => tx.tx().tx().gas_limit,
661            Self::EIP7702(tx) => tx.tx().gas_limit,
662            Self::Deposit(tx) => tx.gas_limit,
663        }
664    }
665
666    pub fn value(&self) -> U256 {
667        U256::from(match self {
668            Self::Legacy(tx) => tx.tx().value,
669            Self::EIP2930(tx) => tx.tx().value,
670            Self::EIP1559(tx) => tx.tx().value,
671            Self::EIP4844(tx) => tx.tx().tx().value,
672            Self::EIP7702(tx) => tx.tx().value,
673            Self::Deposit(tx) => tx.value,
674        })
675    }
676
677    pub fn data(&self) -> &Bytes {
678        match self {
679            Self::Legacy(tx) => &tx.tx().input,
680            Self::EIP2930(tx) => &tx.tx().input,
681            Self::EIP1559(tx) => &tx.tx().input,
682            Self::EIP4844(tx) => &tx.tx().tx().input,
683            Self::EIP7702(tx) => &tx.tx().input,
684            Self::Deposit(tx) => &tx.input,
685        }
686    }
687
688    /// Returns the transaction type
689    pub fn r#type(&self) -> Option<u8> {
690        match self {
691            Self::Legacy(_) => None,
692            Self::EIP2930(_) => Some(1),
693            Self::EIP1559(_) => Some(2),
694            Self::EIP4844(_) => Some(3),
695            Self::EIP7702(_) => Some(4),
696            Self::Deposit(_) => Some(0x7E),
697        }
698    }
699
700    /// Max cost of the transaction
701    /// It is the gas limit multiplied by the gas price,
702    /// and if the transaction is EIP-4844, the result of (total blob gas cost * max fee per blob
703    /// gas) is also added
704    pub fn max_cost(&self) -> u128 {
705        let mut max_cost = (self.gas_limit() as u128).saturating_mul(self.gas_price());
706
707        if self.is_eip4844() {
708            max_cost = max_cost.saturating_add(
709                self.blob_gas()
710                    .map(|g| g as u128)
711                    .unwrap_or(0)
712                    .mul(self.max_fee_per_blob_gas().unwrap_or(0)),
713            )
714        }
715
716        max_cost
717    }
718
719    pub fn blob_gas(&self) -> Option<u64> {
720        match self {
721            Self::EIP4844(tx) => Some(tx.tx().tx().blob_gas()),
722            _ => None,
723        }
724    }
725
726    pub fn sidecar(&self) -> Option<&TxEip4844WithSidecar> {
727        match self {
728            Self::EIP4844(signed_variant) => match signed_variant.tx() {
729                TxEip4844Variant::TxEip4844WithSidecar(with_sidecar) => Some(with_sidecar),
730                _ => None,
731            },
732            _ => None,
733        }
734    }
735
736    pub fn max_fee_per_blob_gas(&self) -> Option<u128> {
737        match self {
738            Self::EIP4844(tx) => Some(tx.tx().tx().max_fee_per_blob_gas),
739            _ => None,
740        }
741    }
742
743    /// Returns a helper type that contains commonly used values as fields
744    pub fn essentials(&self) -> TransactionEssentials {
745        match self {
746            Self::Legacy(t) => TransactionEssentials {
747                kind: t.tx().to,
748                input: t.tx().input.clone(),
749                nonce: t.tx().nonce,
750                gas_limit: t.tx().gas_limit,
751                gas_price: Some(t.tx().gas_price),
752                max_fee_per_gas: None,
753                max_priority_fee_per_gas: None,
754                max_fee_per_blob_gas: None,
755                blob_versioned_hashes: None,
756                value: t.tx().value,
757                chain_id: t.tx().chain_id,
758                access_list: Default::default(),
759            },
760            Self::EIP2930(t) => TransactionEssentials {
761                kind: t.tx().to,
762                input: t.tx().input.clone(),
763                nonce: t.tx().nonce,
764                gas_limit: t.tx().gas_limit,
765                gas_price: Some(t.tx().gas_price),
766                max_fee_per_gas: None,
767                max_priority_fee_per_gas: None,
768                max_fee_per_blob_gas: None,
769                blob_versioned_hashes: None,
770                value: t.tx().value,
771                chain_id: Some(t.tx().chain_id),
772                access_list: t.tx().access_list.clone(),
773            },
774            Self::EIP1559(t) => TransactionEssentials {
775                kind: t.tx().to,
776                input: t.tx().input.clone(),
777                nonce: t.tx().nonce,
778                gas_limit: t.tx().gas_limit,
779                gas_price: None,
780                max_fee_per_gas: Some(t.tx().max_fee_per_gas),
781                max_priority_fee_per_gas: Some(t.tx().max_priority_fee_per_gas),
782                max_fee_per_blob_gas: None,
783                blob_versioned_hashes: None,
784                value: t.tx().value,
785                chain_id: Some(t.tx().chain_id),
786                access_list: t.tx().access_list.clone(),
787            },
788            Self::EIP4844(t) => TransactionEssentials {
789                kind: TxKind::Call(t.tx().tx().to),
790                input: t.tx().tx().input.clone(),
791                nonce: t.tx().tx().nonce,
792                gas_limit: t.tx().tx().gas_limit,
793                gas_price: Some(t.tx().tx().max_fee_per_blob_gas),
794                max_fee_per_gas: Some(t.tx().tx().max_fee_per_gas),
795                max_priority_fee_per_gas: Some(t.tx().tx().max_priority_fee_per_gas),
796                max_fee_per_blob_gas: Some(t.tx().tx().max_fee_per_blob_gas),
797                blob_versioned_hashes: Some(t.tx().tx().blob_versioned_hashes.clone()),
798                value: t.tx().tx().value,
799                chain_id: Some(t.tx().tx().chain_id),
800                access_list: t.tx().tx().access_list.clone(),
801            },
802            Self::EIP7702(t) => TransactionEssentials {
803                kind: TxKind::Call(t.tx().to),
804                input: t.tx().input.clone(),
805                nonce: t.tx().nonce,
806                gas_limit: t.tx().gas_limit,
807                gas_price: Some(t.tx().max_fee_per_gas),
808                max_fee_per_gas: Some(t.tx().max_fee_per_gas),
809                max_priority_fee_per_gas: Some(t.tx().max_priority_fee_per_gas),
810                max_fee_per_blob_gas: None,
811                blob_versioned_hashes: None,
812                value: t.tx().value,
813                chain_id: Some(t.tx().chain_id),
814                access_list: t.tx().access_list.clone(),
815            },
816            Self::Deposit(t) => TransactionEssentials {
817                kind: t.to,
818                input: t.input.clone(),
819                nonce: 0,
820                gas_limit: t.gas_limit,
821                gas_price: Some(0),
822                max_fee_per_gas: None,
823                max_priority_fee_per_gas: None,
824                max_fee_per_blob_gas: None,
825                blob_versioned_hashes: None,
826                value: t.value,
827                chain_id: t.chain_id(),
828                access_list: Default::default(),
829            },
830        }
831    }
832
833    pub fn nonce(&self) -> u64 {
834        match self {
835            Self::Legacy(t) => t.tx().nonce,
836            Self::EIP2930(t) => t.tx().nonce,
837            Self::EIP1559(t) => t.tx().nonce,
838            Self::EIP4844(t) => t.tx().tx().nonce,
839            Self::EIP7702(t) => t.tx().nonce,
840            Self::Deposit(_t) => 0,
841        }
842    }
843
844    pub fn chain_id(&self) -> Option<u64> {
845        match self {
846            Self::Legacy(t) => t.tx().chain_id,
847            Self::EIP2930(t) => Some(t.tx().chain_id),
848            Self::EIP1559(t) => Some(t.tx().chain_id),
849            Self::EIP4844(t) => Some(t.tx().tx().chain_id),
850            Self::EIP7702(t) => Some(t.tx().chain_id),
851            Self::Deposit(t) => t.chain_id(),
852        }
853    }
854
855    pub fn as_legacy(&self) -> Option<&Signed<TxLegacy>> {
856        match self {
857            Self::Legacy(tx) => Some(tx),
858            _ => None,
859        }
860    }
861
862    /// Returns true whether this tx is a legacy transaction
863    pub fn is_legacy(&self) -> bool {
864        matches!(self, Self::Legacy(_))
865    }
866
867    /// Returns true whether this tx is a EIP1559 transaction
868    pub fn is_eip1559(&self) -> bool {
869        matches!(self, Self::EIP1559(_))
870    }
871
872    /// Returns true whether this tx is a EIP2930 transaction
873    pub fn is_eip2930(&self) -> bool {
874        matches!(self, Self::EIP2930(_))
875    }
876
877    /// Returns true whether this tx is a EIP4844 transaction
878    pub fn is_eip4844(&self) -> bool {
879        matches!(self, Self::EIP4844(_))
880    }
881
882    /// Returns the hash of the transaction.
883    ///
884    /// Note: If this transaction has the Impersonated signature then this returns a modified unique
885    /// hash. This allows us to treat impersonated transactions as unique.
886    pub fn hash(&self) -> B256 {
887        match self {
888            Self::Legacy(t) => *t.hash(),
889            Self::EIP2930(t) => *t.hash(),
890            Self::EIP1559(t) => *t.hash(),
891            Self::EIP4844(t) => *t.hash(),
892            Self::EIP7702(t) => *t.hash(),
893            Self::Deposit(t) => t.tx_hash(),
894        }
895    }
896
897    /// Returns the hash if the transaction is impersonated (using a fake signature)
898    ///
899    /// This appends the `address` before hashing it
900    pub fn impersonated_hash(&self, sender: Address) -> B256 {
901        let mut buffer = Vec::new();
902        Encodable::encode(self, &mut buffer);
903        buffer.extend_from_slice(sender.as_ref());
904        B256::from_slice(alloy_primitives::utils::keccak256(&buffer).as_slice())
905    }
906
907    /// Recovers the Ethereum address which was used to sign the transaction.
908    pub fn recover(&self) -> Result<Address, alloy_primitives::SignatureError> {
909        match self {
910            Self::Legacy(tx) => tx.recover_signer(),
911            Self::EIP2930(tx) => tx.recover_signer(),
912            Self::EIP1559(tx) => tx.recover_signer(),
913            Self::EIP4844(tx) => tx.recover_signer(),
914            Self::EIP7702(tx) => tx.recover_signer(),
915            Self::Deposit(tx) => Ok(tx.from),
916        }
917    }
918
919    /// Returns what kind of transaction this is
920    pub fn kind(&self) -> TxKind {
921        match self {
922            Self::Legacy(tx) => tx.tx().to,
923            Self::EIP2930(tx) => tx.tx().to,
924            Self::EIP1559(tx) => tx.tx().to,
925            Self::EIP4844(tx) => TxKind::Call(tx.tx().tx().to),
926            Self::EIP7702(tx) => TxKind::Call(tx.tx().to),
927            Self::Deposit(tx) => tx.to,
928        }
929    }
930
931    /// Returns the callee if this transaction is a call
932    pub fn to(&self) -> Option<Address> {
933        self.kind().to().copied()
934    }
935
936    /// Returns the Signature of the transaction
937    pub fn signature(&self) -> Signature {
938        match self {
939            Self::Legacy(tx) => *tx.signature(),
940            Self::EIP2930(tx) => *tx.signature(),
941            Self::EIP1559(tx) => *tx.signature(),
942            Self::EIP4844(tx) => *tx.signature(),
943            Self::EIP7702(tx) => *tx.signature(),
944            Self::Deposit(_) => Signature::from_scalars_and_parity(
945                B256::with_last_byte(1),
946                B256::with_last_byte(1),
947                false,
948            ),
949        }
950    }
951}
952
953impl Encodable for TypedTransaction {
954    fn encode(&self, out: &mut dyn bytes::BufMut) {
955        if !self.is_legacy() {
956            Header { list: false, payload_length: self.encode_2718_len() }.encode(out);
957        }
958
959        self.encode_2718(out);
960    }
961}
962
963impl Decodable for TypedTransaction {
964    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
965        let mut h_decode_copy = *buf;
966        let header = alloy_rlp::Header::decode(&mut h_decode_copy)?;
967
968        // Legacy TX
969        if header.list {
970            return Ok(TxEnvelope::decode(buf)?.into());
971        }
972
973        // Check byte after header
974        let ty = *h_decode_copy.first().ok_or(alloy_rlp::Error::Custom("empty slice"))?;
975
976        if ty != 0x7E {
977            Ok(TxEnvelope::decode(buf)?.into())
978        } else {
979            Ok(Self::Deposit(TxDeposit::decode_2718(buf)?))
980        }
981    }
982}
983
984impl Typed2718 for TypedTransaction {
985    fn ty(&self) -> u8 {
986        self.r#type().unwrap_or(0)
987    }
988}
989
990impl Encodable2718 for TypedTransaction {
991    fn encode_2718_len(&self) -> usize {
992        match self {
993            Self::Legacy(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(),
994            Self::EIP2930(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(),
995            Self::EIP1559(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(),
996            Self::EIP4844(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(),
997            Self::EIP7702(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(),
998            Self::Deposit(tx) => 1 + tx.length(),
999        }
1000    }
1001
1002    fn encode_2718(&self, out: &mut dyn BufMut) {
1003        match self {
1004            Self::Legacy(tx) => TxEnvelope::from(tx.clone()).encode_2718(out),
1005            Self::EIP2930(tx) => TxEnvelope::from(tx.clone()).encode_2718(out),
1006            Self::EIP1559(tx) => TxEnvelope::from(tx.clone()).encode_2718(out),
1007            Self::EIP4844(tx) => TxEnvelope::from(tx.clone()).encode_2718(out),
1008            Self::EIP7702(tx) => TxEnvelope::from(tx.clone()).encode_2718(out),
1009            Self::Deposit(tx) => {
1010                tx.encode_2718(out);
1011            }
1012        }
1013    }
1014}
1015
1016impl Decodable2718 for TypedTransaction {
1017    fn typed_decode(ty: u8, buf: &mut &[u8]) -> Result<Self, Eip2718Error> {
1018        if ty == 0x7E {
1019            return Ok(Self::Deposit(TxDeposit::decode(buf)?));
1020        }
1021        match TxEnvelope::typed_decode(ty, buf)? {
1022            TxEnvelope::Eip2930(tx) => Ok(Self::EIP2930(tx)),
1023            TxEnvelope::Eip1559(tx) => Ok(Self::EIP1559(tx)),
1024            TxEnvelope::Eip4844(tx) => Ok(Self::EIP4844(tx)),
1025            TxEnvelope::Eip7702(tx) => Ok(Self::EIP7702(tx)),
1026            _ => unreachable!(),
1027        }
1028    }
1029
1030    fn fallback_decode(buf: &mut &[u8]) -> Result<Self, Eip2718Error> {
1031        match TxEnvelope::fallback_decode(buf)? {
1032            TxEnvelope::Legacy(tx) => Ok(Self::Legacy(tx)),
1033            _ => unreachable!(),
1034        }
1035    }
1036}
1037
1038impl From<TxEnvelope> for TypedTransaction {
1039    fn from(value: TxEnvelope) -> Self {
1040        match value {
1041            TxEnvelope::Legacy(tx) => Self::Legacy(tx),
1042            TxEnvelope::Eip2930(tx) => Self::EIP2930(tx),
1043            TxEnvelope::Eip1559(tx) => Self::EIP1559(tx),
1044            TxEnvelope::Eip4844(tx) => Self::EIP4844(tx),
1045            _ => unreachable!(),
1046        }
1047    }
1048}
1049
1050#[derive(Clone, Debug, PartialEq, Eq)]
1051pub struct TransactionEssentials {
1052    pub kind: TxKind,
1053    pub input: Bytes,
1054    pub nonce: u64,
1055    pub gas_limit: u64,
1056    pub gas_price: Option<u128>,
1057    pub max_fee_per_gas: Option<u128>,
1058    pub max_priority_fee_per_gas: Option<u128>,
1059    pub max_fee_per_blob_gas: Option<u128>,
1060    pub blob_versioned_hashes: Option<Vec<B256>>,
1061    pub value: U256,
1062    pub chain_id: Option<u64>,
1063    pub access_list: AccessList,
1064}
1065
1066/// Represents all relevant information of an executed transaction
1067#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
1068pub struct TransactionInfo {
1069    pub transaction_hash: B256,
1070    pub transaction_index: u64,
1071    pub from: Address,
1072    pub to: Option<Address>,
1073    pub contract_address: Option<Address>,
1074    pub traces: Vec<CallTraceNode>,
1075    pub exit: InstructionResult,
1076    pub out: Option<Bytes>,
1077    pub nonce: u64,
1078    pub gas_used: u64,
1079}
1080
1081#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
1082#[serde(rename_all = "camelCase")]
1083pub struct DepositReceipt<T = Receipt<alloy_primitives::Log>> {
1084    #[serde(flatten)]
1085    pub inner: ReceiptWithBloom<T>,
1086    #[serde(default, with = "alloy_serde::quantity::opt")]
1087    pub deposit_nonce: Option<u64>,
1088    #[serde(default, with = "alloy_serde::quantity::opt")]
1089    pub deposit_receipt_version: Option<u64>,
1090}
1091
1092impl DepositReceipt {
1093    fn payload_len(&self) -> usize {
1094        self.inner.receipt.status.length()
1095            + self.inner.receipt.cumulative_gas_used.length()
1096            + self.inner.logs_bloom.length()
1097            + self.inner.receipt.logs.length()
1098            + self.deposit_nonce.map_or(0, |n| n.length())
1099            + self.deposit_receipt_version.map_or(0, |n| n.length())
1100    }
1101
1102    /// Returns the rlp header for the receipt payload.
1103    fn receipt_rlp_header(&self) -> alloy_rlp::Header {
1104        alloy_rlp::Header { list: true, payload_length: self.payload_len() }
1105    }
1106
1107    /// Encodes the receipt data.
1108    fn encode_fields(&self, out: &mut dyn BufMut) {
1109        self.receipt_rlp_header().encode(out);
1110        self.inner.status().encode(out);
1111        self.inner.receipt.cumulative_gas_used.encode(out);
1112        self.inner.logs_bloom.encode(out);
1113        self.inner.receipt.logs.encode(out);
1114        if let Some(n) = self.deposit_nonce {
1115            n.encode(out);
1116        }
1117        if let Some(n) = self.deposit_receipt_version {
1118            n.encode(out);
1119        }
1120    }
1121
1122    /// Decodes the receipt payload
1123    fn decode_receipt(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1124        let b: &mut &[u8] = &mut &**buf;
1125        let rlp_head = alloy_rlp::Header::decode(b)?;
1126        if !rlp_head.list {
1127            return Err(alloy_rlp::Error::UnexpectedString);
1128        }
1129        let started_len = b.len();
1130        let remaining = |b: &[u8]| rlp_head.payload_length - (started_len - b.len()) > 0;
1131
1132        let status = Decodable::decode(b)?;
1133        let cumulative_gas_used = Decodable::decode(b)?;
1134        let logs_bloom = Decodable::decode(b)?;
1135        let logs: Vec<Log> = Decodable::decode(b)?;
1136        let deposit_nonce = remaining(b).then(|| alloy_rlp::Decodable::decode(b)).transpose()?;
1137        let deposit_nonce_version =
1138            remaining(b).then(|| alloy_rlp::Decodable::decode(b)).transpose()?;
1139
1140        let this = Self {
1141            inner: ReceiptWithBloom {
1142                receipt: Receipt { status, cumulative_gas_used, logs },
1143                logs_bloom,
1144            },
1145            deposit_nonce,
1146            deposit_receipt_version: deposit_nonce_version,
1147        };
1148
1149        let consumed = started_len - b.len();
1150        if consumed != rlp_head.payload_length {
1151            return Err(alloy_rlp::Error::ListLengthMismatch {
1152                expected: rlp_head.payload_length,
1153                got: consumed,
1154            });
1155        }
1156
1157        *buf = *b;
1158        Ok(this)
1159    }
1160}
1161
1162impl alloy_rlp::Encodable for DepositReceipt {
1163    fn encode(&self, out: &mut dyn BufMut) {
1164        self.encode_fields(out);
1165    }
1166
1167    fn length(&self) -> usize {
1168        let payload_length = self.payload_len();
1169        payload_length + length_of_length(payload_length)
1170    }
1171}
1172
1173impl alloy_rlp::Decodable for DepositReceipt {
1174    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1175        Self::decode_receipt(buf)
1176    }
1177}
1178
1179#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1180#[serde(tag = "type")]
1181pub enum TypedReceipt<T = Receipt<alloy_primitives::Log>> {
1182    #[serde(rename = "0x0", alias = "0x00")]
1183    Legacy(ReceiptWithBloom<T>),
1184    #[serde(rename = "0x1", alias = "0x01")]
1185    EIP2930(ReceiptWithBloom<T>),
1186    #[serde(rename = "0x2", alias = "0x02")]
1187    EIP1559(ReceiptWithBloom<T>),
1188    #[serde(rename = "0x3", alias = "0x03")]
1189    EIP4844(ReceiptWithBloom<T>),
1190    #[serde(rename = "0x4", alias = "0x04")]
1191    EIP7702(ReceiptWithBloom<T>),
1192    #[serde(rename = "0x7E", alias = "0x7e")]
1193    Deposit(DepositReceipt<T>),
1194}
1195
1196impl<T> TypedReceipt<T> {
1197    pub fn as_receipt_with_bloom(&self) -> &ReceiptWithBloom<T> {
1198        match self {
1199            Self::Legacy(r)
1200            | Self::EIP1559(r)
1201            | Self::EIP2930(r)
1202            | Self::EIP4844(r)
1203            | Self::EIP7702(r) => r,
1204            Self::Deposit(r) => &r.inner,
1205        }
1206    }
1207}
1208
1209impl<T> From<TypedReceipt<T>> for ReceiptWithBloom<T> {
1210    fn from(value: TypedReceipt<T>) -> Self {
1211        match value {
1212            TypedReceipt::Legacy(r)
1213            | TypedReceipt::EIP1559(r)
1214            | TypedReceipt::EIP2930(r)
1215            | TypedReceipt::EIP4844(r)
1216            | TypedReceipt::EIP7702(r) => r,
1217            TypedReceipt::Deposit(r) => r.inner,
1218        }
1219    }
1220}
1221
1222impl From<TypedReceipt<Receipt<alloy_rpc_types::Log>>> for OtsReceipt {
1223    fn from(value: TypedReceipt<Receipt<alloy_rpc_types::Log>>) -> Self {
1224        let r#type = match value {
1225            TypedReceipt::Legacy(_) => 0x00,
1226            TypedReceipt::EIP2930(_) => 0x01,
1227            TypedReceipt::EIP1559(_) => 0x02,
1228            TypedReceipt::EIP4844(_) => 0x03,
1229            TypedReceipt::EIP7702(_) => 0x04,
1230            TypedReceipt::Deposit(_) => 0x7E,
1231        } as u8;
1232        let receipt = ReceiptWithBloom::<Receipt<alloy_rpc_types::Log>>::from(value);
1233        let status = receipt.status();
1234        let cumulative_gas_used = receipt.cumulative_gas_used();
1235        let logs = receipt.logs().to_vec();
1236        let logs_bloom = receipt.logs_bloom;
1237
1238        Self { status, cumulative_gas_used, logs: Some(logs), logs_bloom: Some(logs_bloom), r#type }
1239    }
1240}
1241
1242impl TypedReceipt {
1243    pub fn cumulative_gas_used(&self) -> u64 {
1244        self.as_receipt_with_bloom().cumulative_gas_used()
1245    }
1246
1247    pub fn logs_bloom(&self) -> &Bloom {
1248        &self.as_receipt_with_bloom().logs_bloom
1249    }
1250
1251    pub fn logs(&self) -> &[Log] {
1252        self.as_receipt_with_bloom().logs()
1253    }
1254}
1255
1256impl From<ReceiptEnvelope<alloy_rpc_types::Log>> for TypedReceipt<Receipt<alloy_rpc_types::Log>> {
1257    fn from(value: ReceiptEnvelope<alloy_rpc_types::Log>) -> Self {
1258        match value {
1259            ReceiptEnvelope::Legacy(r) => Self::Legacy(r),
1260            ReceiptEnvelope::Eip2930(r) => Self::EIP2930(r),
1261            ReceiptEnvelope::Eip1559(r) => Self::EIP1559(r),
1262            ReceiptEnvelope::Eip4844(r) => Self::EIP4844(r),
1263            _ => unreachable!(),
1264        }
1265    }
1266}
1267
1268impl Encodable for TypedReceipt {
1269    fn encode(&self, out: &mut dyn bytes::BufMut) {
1270        match self {
1271            Self::Legacy(r) => r.encode(out),
1272            receipt => {
1273                let payload_len = match receipt {
1274                    Self::EIP2930(r) => r.length() + 1,
1275                    Self::EIP1559(r) => r.length() + 1,
1276                    Self::EIP4844(r) => r.length() + 1,
1277                    Self::Deposit(r) => r.length() + 1,
1278                    _ => unreachable!("receipt already matched"),
1279                };
1280
1281                match receipt {
1282                    Self::EIP2930(r) => {
1283                        Header { list: true, payload_length: payload_len }.encode(out);
1284                        1u8.encode(out);
1285                        r.encode(out);
1286                    }
1287                    Self::EIP1559(r) => {
1288                        Header { list: true, payload_length: payload_len }.encode(out);
1289                        2u8.encode(out);
1290                        r.encode(out);
1291                    }
1292                    Self::EIP4844(r) => {
1293                        Header { list: true, payload_length: payload_len }.encode(out);
1294                        3u8.encode(out);
1295                        r.encode(out);
1296                    }
1297                    Self::Deposit(r) => {
1298                        Header { list: true, payload_length: payload_len }.encode(out);
1299                        0x7Eu8.encode(out);
1300                        r.encode(out);
1301                    }
1302                    _ => unreachable!("receipt already matched"),
1303                }
1304            }
1305        }
1306    }
1307}
1308
1309impl Decodable for TypedReceipt {
1310    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1311        use bytes::Buf;
1312        use std::cmp::Ordering;
1313
1314        // a receipt is either encoded as a string (non legacy) or a list (legacy).
1315        // We should not consume the buffer if we are decoding a legacy receipt, so let's
1316        // check if the first byte is between 0x80 and 0xbf.
1317        let rlp_type = *buf
1318            .first()
1319            .ok_or(alloy_rlp::Error::Custom("cannot decode a receipt from empty bytes"))?;
1320
1321        match rlp_type.cmp(&alloy_rlp::EMPTY_LIST_CODE) {
1322            Ordering::Less => {
1323                // strip out the string header
1324                let _header = Header::decode(buf)?;
1325                let receipt_type = *buf.first().ok_or(alloy_rlp::Error::Custom(
1326                    "typed receipt cannot be decoded from an empty slice",
1327                ))?;
1328                if receipt_type == 0x01 {
1329                    buf.advance(1);
1330                    <ReceiptWithBloom as Decodable>::decode(buf).map(TypedReceipt::EIP2930)
1331                } else if receipt_type == 0x02 {
1332                    buf.advance(1);
1333                    <ReceiptWithBloom as Decodable>::decode(buf).map(TypedReceipt::EIP1559)
1334                } else if receipt_type == 0x03 {
1335                    buf.advance(1);
1336                    <ReceiptWithBloom as Decodable>::decode(buf).map(TypedReceipt::EIP4844)
1337                } else if receipt_type == 0x7E {
1338                    buf.advance(1);
1339                    <DepositReceipt as Decodable>::decode(buf).map(TypedReceipt::Deposit)
1340                } else {
1341                    Err(alloy_rlp::Error::Custom("invalid receipt type"))
1342                }
1343            }
1344            Ordering::Equal => {
1345                Err(alloy_rlp::Error::Custom("an empty list is not a valid receipt encoding"))
1346            }
1347            Ordering::Greater => {
1348                <ReceiptWithBloom as Decodable>::decode(buf).map(TypedReceipt::Legacy)
1349            }
1350        }
1351    }
1352}
1353
1354impl Typed2718 for TypedReceipt {
1355    fn ty(&self) -> u8 {
1356        match self {
1357            Self::Legacy(_) => alloy_consensus::constants::LEGACY_TX_TYPE_ID,
1358            Self::EIP2930(_) => alloy_consensus::constants::EIP2930_TX_TYPE_ID,
1359            Self::EIP1559(_) => alloy_consensus::constants::EIP1559_TX_TYPE_ID,
1360            Self::EIP4844(_) => alloy_consensus::constants::EIP4844_TX_TYPE_ID,
1361            Self::EIP7702(_) => alloy_consensus::constants::EIP7702_TX_TYPE_ID,
1362            Self::Deposit(_) => DEPOSIT_TX_TYPE_ID,
1363        }
1364    }
1365}
1366
1367impl Encodable2718 for TypedReceipt {
1368    fn encode_2718_len(&self) -> usize {
1369        match self {
1370            Self::Legacy(r) => ReceiptEnvelope::Legacy(r.clone()).encode_2718_len(),
1371            Self::EIP2930(r) => ReceiptEnvelope::Eip2930(r.clone()).encode_2718_len(),
1372            Self::EIP1559(r) => ReceiptEnvelope::Eip1559(r.clone()).encode_2718_len(),
1373            Self::EIP4844(r) => ReceiptEnvelope::Eip4844(r.clone()).encode_2718_len(),
1374            Self::EIP7702(r) => 1 + r.length(),
1375            Self::Deposit(r) => 1 + r.length(),
1376        }
1377    }
1378
1379    fn encode_2718(&self, out: &mut dyn BufMut) {
1380        if let Some(ty) = self.type_flag() {
1381            out.put_u8(ty);
1382        }
1383        match self {
1384            Self::Legacy(r)
1385            | Self::EIP2930(r)
1386            | Self::EIP1559(r)
1387            | Self::EIP4844(r)
1388            | Self::EIP7702(r) => r.encode(out),
1389            Self::Deposit(r) => r.encode(out),
1390        }
1391    }
1392}
1393
1394impl Decodable2718 for TypedReceipt {
1395    fn typed_decode(ty: u8, buf: &mut &[u8]) -> Result<Self, Eip2718Error> {
1396        if ty == 0x7E {
1397            return Ok(Self::Deposit(DepositReceipt::decode(buf)?));
1398        }
1399        match ReceiptEnvelope::typed_decode(ty, buf)? {
1400            ReceiptEnvelope::Eip2930(tx) => Ok(Self::EIP2930(tx)),
1401            ReceiptEnvelope::Eip1559(tx) => Ok(Self::EIP1559(tx)),
1402            ReceiptEnvelope::Eip4844(tx) => Ok(Self::EIP4844(tx)),
1403            _ => unreachable!(),
1404        }
1405    }
1406
1407    fn fallback_decode(buf: &mut &[u8]) -> Result<Self, Eip2718Error> {
1408        match ReceiptEnvelope::fallback_decode(buf)? {
1409            ReceiptEnvelope::Legacy(tx) => Ok(Self::Legacy(tx)),
1410            _ => unreachable!(),
1411        }
1412    }
1413}
1414
1415pub type ReceiptResponse = TransactionReceipt<TypedReceipt<Receipt<alloy_rpc_types::Log>>>;
1416
1417pub fn convert_to_anvil_receipt(receipt: AnyTransactionReceipt) -> Option<ReceiptResponse> {
1418    let WithOtherFields {
1419        inner:
1420            TransactionReceipt {
1421                transaction_hash,
1422                transaction_index,
1423                block_hash,
1424                block_number,
1425                gas_used,
1426                contract_address,
1427                effective_gas_price,
1428                from,
1429                to,
1430                blob_gas_price,
1431                blob_gas_used,
1432                inner: AnyReceiptEnvelope { inner: receipt_with_bloom, r#type },
1433            },
1434        other,
1435    } = receipt;
1436
1437    Some(TransactionReceipt {
1438        transaction_hash,
1439        transaction_index,
1440        block_hash,
1441        block_number,
1442        gas_used,
1443        contract_address,
1444        effective_gas_price,
1445        from,
1446        to,
1447        blob_gas_price,
1448        blob_gas_used,
1449        inner: match r#type {
1450            0x00 => TypedReceipt::Legacy(receipt_with_bloom),
1451            0x01 => TypedReceipt::EIP2930(receipt_with_bloom),
1452            0x02 => TypedReceipt::EIP1559(receipt_with_bloom),
1453            0x03 => TypedReceipt::EIP4844(receipt_with_bloom),
1454            0x04 => TypedReceipt::EIP7702(receipt_with_bloom),
1455            0x7E => TypedReceipt::Deposit(DepositReceipt {
1456                inner: receipt_with_bloom,
1457                deposit_nonce: other
1458                    .get_deserialized::<U64>("depositNonce")
1459                    .transpose()
1460                    .ok()?
1461                    .map(|v| v.to()),
1462                deposit_receipt_version: other
1463                    .get_deserialized::<U64>("depositReceiptVersion")
1464                    .transpose()
1465                    .ok()?
1466                    .map(|v| v.to()),
1467            }),
1468            _ => return None,
1469        },
1470    })
1471}
1472
1473#[cfg(test)]
1474mod tests {
1475    use super::*;
1476    use alloy_primitives::{LogData, b256, hex};
1477    use std::str::FromStr;
1478
1479    // <https://github.com/foundry-rs/foundry/issues/10852>
1480    #[test]
1481    fn test_receipt_convert() {
1482        let s = r#"{"type":"0x4","status":"0x1","cumulativeGasUsed":"0x903fd1","logs":[{"address":"0x0000d9fcd47bf761e7287d8ee09917d7e2100000","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000000000000000000000000000000000000000000000","0x000000000000000000000000234ce51365b9c417171b6dad280f49143e1b0547"],"data":"0x00000000000000000000000000000000000000000000032139b42c3431700000","blockHash":"0xd26b59c1d8b5bfa9362d19eb0da3819dfe0b367987a71f6d30908dd45e0d7a60","blockNumber":"0x159663e","blockTimestamp":"0x68411f7b","transactionHash":"0x17a6af73d1317e69cfc3cac9221bd98261d40f24815850a44dbfbf96652ae52a","transactionIndex":"0x22","logIndex":"0x158","removed":false}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000008100000000000000000000000000000000000000000000000020000200000000000000800000000800000000000000010000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000","transactionHash":"0x17a6af73d1317e69cfc3cac9221bd98261d40f24815850a44dbfbf96652ae52a","transactionIndex":"0x22","blockHash":"0xd26b59c1d8b5bfa9362d19eb0da3819dfe0b367987a71f6d30908dd45e0d7a60","blockNumber":"0x159663e","gasUsed":"0x28ee7","effectiveGasPrice":"0x4bf02090","from":"0x234ce51365b9c417171b6dad280f49143e1b0547","to":"0x234ce51365b9c417171b6dad280f49143e1b0547","contractAddress":null}"#;
1483        let receipt: AnyTransactionReceipt = serde_json::from_str(s).unwrap();
1484        let _converted = convert_to_anvil_receipt(receipt).unwrap();
1485    }
1486
1487    #[test]
1488    fn test_decode_call() {
1489        let bytes_first = &mut &hex::decode("f86b02843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba00eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5aea03a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18").unwrap()[..];
1490        let decoded = TypedTransaction::decode(&mut &bytes_first[..]).unwrap();
1491
1492        let tx = TxLegacy {
1493            nonce: 2u64,
1494            gas_price: 1000000000u128,
1495            gas_limit: 100000,
1496            to: TxKind::Call(Address::from_slice(
1497                &hex::decode("d3e8763675e4c425df46cc3b5c0f6cbdac396046").unwrap()[..],
1498            )),
1499            value: U256::from(1000000000000000u64),
1500            input: Bytes::default(),
1501            chain_id: Some(4),
1502        };
1503
1504        let signature = Signature::from_str("0eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5ae3a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca182b").unwrap();
1505
1506        let tx = TypedTransaction::Legacy(Signed::new_unchecked(
1507            tx,
1508            signature,
1509            b256!("0xa517b206d2223278f860ea017d3626cacad4f52ff51030dc9a96b432f17f8d34"),
1510        ));
1511
1512        assert_eq!(tx, decoded);
1513    }
1514
1515    #[test]
1516    fn test_decode_create_goerli() {
1517        // test that an example create tx from goerli decodes properly
1518        let tx_bytes =
1519              hex::decode("02f901ee05228459682f008459682f11830209bf8080b90195608060405234801561001057600080fd5b50610175806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80630c49c36c14610030575b600080fd5b61003861004e565b604051610045919061011d565b60405180910390f35b60606020600052600f6020527f68656c6c6f2073746174656d696e64000000000000000000000000000000000060405260406000f35b600081519050919050565b600082825260208201905092915050565b60005b838110156100be5780820151818401526020810190506100a3565b838111156100cd576000848401525b50505050565b6000601f19601f8301169050919050565b60006100ef82610084565b6100f9818561008f565b93506101098185602086016100a0565b610112816100d3565b840191505092915050565b6000602082019050818103600083015261013781846100e4565b90509291505056fea264697066735822122051449585839a4ea5ac23cae4552ef8a96b64ff59d0668f76bfac3796b2bdbb3664736f6c63430008090033c080a0136ebffaa8fc8b9fda9124de9ccb0b1f64e90fbd44251b4c4ac2501e60b104f9a07eb2999eec6d185ef57e91ed099afb0a926c5b536f0155dd67e537c7476e1471")
1520                  .unwrap();
1521        let _decoded = TypedTransaction::decode(&mut &tx_bytes[..]).unwrap();
1522    }
1523
1524    #[test]
1525    fn can_recover_sender() {
1526        // random mainnet tx: https://etherscan.io/tx/0x86718885c4b4218c6af87d3d0b0d83e3cc465df2a05c048aa4db9f1a6f9de91f
1527        let bytes = hex::decode("02f872018307910d808507204d2cb1827d0094388c818ca8b9251b393131c08a736a67ccb19297880320d04823e2701c80c001a0cf024f4815304df2867a1a74e9d2707b6abda0337d2d54a4438d453f4160f190a07ac0e6b3bc9395b5b9c8b9e6d77204a236577a5b18467b9175c01de4faa208d9").unwrap();
1528
1529        let Ok(TypedTransaction::EIP1559(tx)) = TypedTransaction::decode(&mut &bytes[..]) else {
1530            panic!("decoding TypedTransaction failed");
1531        };
1532
1533        assert_eq!(
1534            tx.hash(),
1535            &"0x86718885c4b4218c6af87d3d0b0d83e3cc465df2a05c048aa4db9f1a6f9de91f"
1536                .parse::<B256>()
1537                .unwrap()
1538        );
1539        assert_eq!(
1540            tx.recover_signer().unwrap(),
1541            "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5".parse::<Address>().unwrap()
1542        );
1543    }
1544
1545    // Test vector from https://sepolia.etherscan.io/tx/0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0
1546    // Blobscan: https://sepolia.blobscan.com/tx/0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0
1547    #[test]
1548    fn test_decode_live_4844_tx() {
1549        use alloy_primitives::{address, b256};
1550
1551        // https://sepolia.etherscan.io/getRawTx?tx=0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0
1552        let raw_tx = alloy_primitives::hex::decode("0x03f9011d83aa36a7820fa28477359400852e90edd0008252089411e9ca82a3a762b4b5bd264d4173a242e7a770648080c08504a817c800f8a5a0012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921aa00152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4a0013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7a001148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1a0011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e654901a0c8de4cced43169f9aa3d36506363b2d2c44f6c49fc1fd91ea114c86f3757077ea01e11fdd0d1934eda0492606ee0bb80a7bf8f35cc5f86ec60fe5031ba48bfd544").unwrap();
1553        let res = TypedTransaction::decode(&mut raw_tx.as_slice()).unwrap();
1554        assert_eq!(res.r#type(), Some(3));
1555
1556        let tx = match res {
1557            TypedTransaction::EIP4844(tx) => tx,
1558            _ => unreachable!(),
1559        };
1560
1561        assert_eq!(tx.tx().tx().to, address!("0x11E9CA82A3a762b4B5bd264d4173a242e7a77064"));
1562
1563        assert_eq!(
1564            tx.tx().tx().blob_versioned_hashes,
1565            vec![
1566                b256!("0x012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921a"),
1567                b256!("0x0152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4"),
1568                b256!("0x013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7"),
1569                b256!("0x01148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1"),
1570                b256!("0x011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e6549")
1571            ]
1572        );
1573
1574        let from = tx.recover_signer().unwrap();
1575        assert_eq!(from, address!("0xA83C816D4f9b2783761a22BA6FADB0eB0606D7B2"));
1576    }
1577
1578    #[test]
1579    fn test_decode_encode_deposit_tx() {
1580        // https://sepolia-optimism.etherscan.io/tx/0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7
1581        let tx_hash: TxHash = "0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7"
1582            .parse::<TxHash>()
1583            .unwrap();
1584
1585        // https://sepolia-optimism.etherscan.io/getRawTx?tx=0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7
1586        let raw_tx = alloy_primitives::hex::decode(
1587            "7ef861a0dfd7ae78bf3c414cfaa77f13c0205c82eb9365e217b2daa3448c3156b69b27ac94778f2146f48179643473b82931c4cd7b8f153efd94778f2146f48179643473b82931c4cd7b8f153efd872386f26fc10000872386f26fc10000830186a08080",
1588        )
1589        .unwrap();
1590        let dep_tx = TypedTransaction::decode(&mut raw_tx.as_slice()).unwrap();
1591
1592        let mut encoded = Vec::new();
1593        dep_tx.encode_2718(&mut encoded);
1594
1595        assert_eq!(raw_tx, encoded);
1596
1597        assert_eq!(tx_hash, dep_tx.hash());
1598    }
1599
1600    #[test]
1601    fn can_recover_sender_not_normalized() {
1602        let bytes = hex::decode("f85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804").unwrap();
1603
1604        let Ok(TypedTransaction::Legacy(tx)) = TypedTransaction::decode(&mut &bytes[..]) else {
1605            panic!("decoding TypedTransaction failed");
1606        };
1607
1608        assert_eq!(tx.tx().input, Bytes::from(b""));
1609        assert_eq!(tx.tx().gas_price, 1);
1610        assert_eq!(tx.tx().gas_limit, 21000);
1611        assert_eq!(tx.tx().nonce, 0);
1612        if let TxKind::Call(to) = tx.tx().to {
1613            assert_eq!(
1614                to,
1615                "0x095e7baea6a6c7c4c2dfeb977efac326af552d87".parse::<Address>().unwrap()
1616            );
1617        } else {
1618            panic!("expected a call transaction");
1619        }
1620        assert_eq!(tx.tx().value, U256::from(0x0au64));
1621        assert_eq!(
1622            tx.recover_signer().unwrap(),
1623            "0f65fe9276bc9a24ae7083ae28e2660ef72df99e".parse::<Address>().unwrap()
1624        );
1625    }
1626
1627    #[test]
1628    fn encode_legacy_receipt() {
1629        let expected = hex::decode("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff").unwrap();
1630
1631        let mut data = vec![];
1632        let receipt = TypedReceipt::Legacy(ReceiptWithBloom {
1633            receipt: Receipt {
1634                status: false.into(),
1635                cumulative_gas_used: 0x1,
1636                logs: vec![Log {
1637                    address: Address::from_str("0000000000000000000000000000000000000011").unwrap(),
1638                    data: LogData::new_unchecked(
1639                        vec![
1640                            B256::from_str(
1641                                "000000000000000000000000000000000000000000000000000000000000dead",
1642                            )
1643                            .unwrap(),
1644                            B256::from_str(
1645                                "000000000000000000000000000000000000000000000000000000000000beef",
1646                            )
1647                            .unwrap(),
1648                        ],
1649                        Bytes::from_str("0100ff").unwrap(),
1650                    ),
1651                }],
1652            },
1653            logs_bloom: [0; 256].into(),
1654        });
1655
1656        receipt.encode(&mut data);
1657
1658        // check that the rlp length equals the length of the expected rlp
1659        assert_eq!(receipt.length(), expected.len());
1660        assert_eq!(data, expected);
1661    }
1662
1663    #[test]
1664    fn decode_legacy_receipt() {
1665        let data = hex::decode("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff").unwrap();
1666
1667        let expected = TypedReceipt::Legacy(ReceiptWithBloom {
1668            receipt: Receipt {
1669                status: false.into(),
1670                cumulative_gas_used: 0x1,
1671                logs: vec![Log {
1672                    address: Address::from_str("0000000000000000000000000000000000000011").unwrap(),
1673                    data: LogData::new_unchecked(
1674                        vec![
1675                            B256::from_str(
1676                                "000000000000000000000000000000000000000000000000000000000000dead",
1677                            )
1678                            .unwrap(),
1679                            B256::from_str(
1680                                "000000000000000000000000000000000000000000000000000000000000beef",
1681                            )
1682                            .unwrap(),
1683                        ],
1684                        Bytes::from_str("0100ff").unwrap(),
1685                    ),
1686                }],
1687            },
1688            logs_bloom: [0; 256].into(),
1689        });
1690
1691        let receipt = TypedReceipt::decode(&mut &data[..]).unwrap();
1692
1693        assert_eq!(receipt, expected);
1694    }
1695
1696    #[test]
1697    fn deser_to_type_tx() {
1698        let tx = r#"
1699        {
1700            "EIP1559": {
1701                "chainId": "0x7a69",
1702                "nonce": "0x0",
1703                "gas": "0x5209",
1704                "maxFeePerGas": "0x77359401",
1705                "maxPriorityFeePerGas": "0x1",
1706                "to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
1707                "value": "0x0",
1708                "accessList": [],
1709                "input": "0x",
1710                "r": "0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0",
1711                "s": "0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd",
1712                "yParity": "0x0",
1713                "hash": "0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515"
1714            }
1715        }"#;
1716
1717        let _typed_tx: TypedTransaction = serde_json::from_str(tx).unwrap();
1718    }
1719}